/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.switchyard.as7.extension.cluster;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.naming.NamingException;
import javax.xml.namespace.QName;

import org.apache.catalina.Container;
import org.apache.catalina.Host;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.ContextConfig;
import org.apache.coyote.http11.Http11Protocol;
import org.apache.tomcat.InstanceManager;
import org.jboss.as.server.ServerEnvironment;
import org.jboss.as.web.deployment.WebCtxLoader;
import org.jboss.logging.Logger;
import org.switchyard.ServiceDomain;
import org.switchyard.as7.extension.ExtensionLogger;
import org.switchyard.as7.extension.ExtensionMessages;
import org.switchyard.as7.extension.util.ServerUtil;
import org.switchyard.component.sca.RemoteEndpointPublisher;
import org.switchyard.component.sca.SwitchYardRemotingServlet;

/**
 * Publishes standalone HTTP endpoint.
 */
public class RemoteEndpointListener implements RemoteEndpointPublisher {

    private static final String SERVER_TEMP_DIR = System.getProperty(ServerEnvironment.SERVER_TEMP_DIR);
    private static final String SERVLET_NAME = "SwitchYardRemotingServlet";
    
    private static Logger _log = Logger.getLogger(RemoteEndpointListener.class);
    
    private String _contextName;
    private StandardContext _serverContext;
    private Map<QName, ServiceDomain> _services = new ConcurrentHashMap<QName, ServiceDomain>();
    private boolean _disableRemoteTransaction = false;
    
    private boolean _started;

    /**
     * Constructor.
     */
    public RemoteEndpointListener() {
    }

    @Override
    public void init(String context) {
        _contextName = context;
    }

    @Override
    public synchronized void start() throws Exception {
        // If the remote listener is already started, just return.
        if (_started) {
            return;
        }
        
        Host host = ServerUtil.getDefaultHost().getHost();
        _serverContext = (StandardContext) host.findChild("/" + _contextName);
        if (_serverContext == null) {
            _serverContext = new StandardContext();
            _serverContext.setPath("/" + _contextName);
            File docBase = new File(SERVER_TEMP_DIR, _contextName);
            if (!docBase.exists()) {
                if (!docBase.mkdirs()) {
                    throw ExtensionMessages.MESSAGES.unableToCreateTempDirectory(docBase.getPath());
                }
            }
            _serverContext.setDocBase(docBase.getPath());
            _serverContext.addLifecycleListener(new ContextConfig());

            final Loader loader = new WebCtxLoader(Thread.currentThread().getContextClassLoader());
            loader.setContainer(host);
            _serverContext.setLoader(loader);
            _serverContext.setInstanceManager(new LocalInstanceManager());

            Wrapper wrapper = _serverContext.createWrapper();
            wrapper.setName(SERVLET_NAME);
            wrapper.setServletClass(SwitchYardRemotingServlet.class.getName());
            wrapper.setLoadOnStartup(1);
            _serverContext.addChild(wrapper);
            _serverContext.addServletMapping("/*", SERVLET_NAME);
            

            host.addChild(_serverContext);
            _serverContext.create();
            _serverContext.start();
            

            SwitchYardRemotingServlet remotingServlet = (SwitchYardRemotingServlet) wrapper.getServlet();
            remotingServlet.setEndpointPublisher(this);
            _log.info("Published Remote Service Endpoint " + _serverContext.getPath());
            
            _started = true;
        } else {
            throw ExtensionMessages.MESSAGES.contextAlreadyExists(_contextName);
        }
    }
    
    @Override
    public synchronized void stop() throws Exception {
        if (_serverContext != null) {
            // Destroy the web context unless if it is default
            if (!_serverContext.getPath().equals("/")) {
                try {
                    Container container = _serverContext.getParent();
                    container.removeChild(_serverContext);
                    _serverContext.stop();
                    _serverContext.destroy();
                    _log.info("Destroyed HTTP context " + _serverContext.getPath());
                } catch (Exception e) {
                    ExtensionLogger.ROOT_LOGGER.unableToDestroyWebContext(_contextName, e);
                }
            }
        }
    }

    /**
     * Return the address.
     * 
     * @return An address string
     */
    public String getAddress() {
        String hostAddress = null;
        Connector connector = ServerUtil.getDefaultConnector();
        if (connector.getProtocolHandler() instanceof Http11Protocol) {
            Http11Protocol protocol = (Http11Protocol) connector.getProtocolHandler();
            InetAddress address = protocol.getAddress();
            hostAddress = address.getHostAddress();
         } else {
             ExtensionLogger.ROOT_LOGGER.unableToDetermineHostAddress();
             hostAddress = ServerUtil.getDefaultHost().getHost().findAliases()[0];
         }
        
        return connector.getScheme() + "://" + hostAddress + ":" + connector.getPort() + "/" + _contextName;
    }

    private static class LocalInstanceManager implements InstanceManager {
        LocalInstanceManager() {
        }
        @Override
        public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
            return Class.forName(className).newInstance();
        }

        @Override
        public Object newInstance(String fqcn, ClassLoader classLoader) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
            return Class.forName(fqcn, false, classLoader).newInstance();
        }

        @Override
        public Object newInstance(Class<?> c) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException {
            return c.newInstance();
        }

        @Override
        public void newInstance(Object o) throws IllegalAccessException, InvocationTargetException, NamingException {
            throw new IllegalStateException();
        }

        @Override
        public void destroyInstance(Object o) throws IllegalAccessException, InvocationTargetException {
        }
    }

    @Override
    public void addService(QName serviceName, ServiceDomain domain) {
        _services.put(serviceName, domain);
    }

    @Override
    public void removeService(QName serviceName, ServiceDomain domain) {
        _services.remove(serviceName);
    }
    
    @Override
    public ServiceDomain getDomain(QName serviceName) {
        return _services.get(serviceName);
    }

    @Override
    public RemoteEndpointPublisher setDisableRemoteTransaction(boolean disable) {
        _disableRemoteTransaction = disable;
        return this;
    }

    @Override
    public boolean isDisableRemoteTransaction() {
        return _disableRemoteTransaction;
    }

}
