/**
 * Copyright (c) 2005-2012 EBM WebSourcing, 2012-2015 Linagora
 * 
 * This program/library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see <http://www.gnu.org/licenses/>
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.microkernel.admin;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.objectweb.fractal.api.control.IllegalLifeCycleException;
import org.objectweb.fractal.fraclet.annotation.annotations.FractalComponent;
import org.objectweb.fractal.fraclet.annotation.annotations.Interface;
import org.objectweb.fractal.fraclet.annotation.annotations.LifeCycle;
import org.objectweb.fractal.fraclet.annotation.annotations.Provides;
import org.objectweb.fractal.fraclet.annotation.annotations.Requires;
import org.objectweb.fractal.fraclet.annotation.annotations.type.Contingency;
import org.objectweb.fractal.fraclet.annotation.annotations.type.LifeCycleType;
import org.ow2.petals.basisapi.exception.PetalsException;
import org.ow2.petals.clientserverapi.configuration.ContainerConfiguration;
import org.ow2.petals.jmx.commons.PetalsAdminServiceConstants.Container;
import org.ow2.petals.jmx.commons.PetalsAdminServiceConstants.ContainerStates;
import org.ow2.petals.jmx.commons.PetalsAdminServiceConstants.Domain;
import org.ow2.petals.jmx.commons.PetalsAdminServiceConstants.SubDomain;
import org.ow2.petals.jmx.commons.PetalsAdminServiceConstants.Topology;
import org.ow2.petals.microkernel.api.admin.PetalsAdminInterface;
import org.ow2.petals.microkernel.api.communication.topology.TopologyService;
import org.ow2.petals.microkernel.api.configuration.ConfigurationService;
import org.ow2.petals.microkernel.api.configuration.DomainConfiguration;
import org.ow2.petals.microkernel.api.configuration.SubDomainConfiguration;
import org.ow2.petals.microkernel.api.jbi.management.DeploymentService;
import org.ow2.petals.microkernel.api.jbi.management.InstallationService;
import org.ow2.petals.microkernel.api.jbi.messaging.registry.EndpointRegistry;
import org.ow2.petals.microkernel.api.server.PetalsStopThread;
import org.ow2.petals.microkernel.api.util.LoggingUtil;

/**
 * This class is used for Petals global Administration.
 * @author Adrien Louis - EBM WebSourcing
 * @author Roland Naudin - EBM WebSourcing
 */
@FractalComponent
@Provides(interfaces = @Interface(name = "service", signature = PetalsAdminInterface.class))
public class PetalsAdminServiceImpl implements PetalsAdminInterface {

    /**
     * Logger wrapper
     */
    protected LoggingUtil log;

    /**
     * Deployment service fractal component
     */
    @Requires(name = "deployment")
    protected DeploymentService deployment;

    /**
     * Installation service fractal component
     */
    @Requires(name = "installation")
    protected InstallationService installation;

    /**
     * Topology service fractal component
     */
    @Requires(name = "topology", contingency = Contingency.OPTIONAL)
    protected TopologyService topologyService;

    /**
     * The Configuration service fractal component
     */
    @Requires(name = "configuration")
    private ConfigurationService configurationService;

    /**
     * The Registry service fractal component
     */
    @Requires(name = "endpoint")
    private EndpointRegistry registry;

    /**
     * The container configuration
     */
    private ContainerConfiguration containerConfiguration;

    /**
     * The sub-domain configuration
     */
    private SubDomainConfiguration subDomainConfiguration;

    /**
     * The domain configuration
     */
    private DomainConfiguration domainConfiguration;

    /**
     * The Petals Stop thread instance, which stops the PEtALS container
     */
    private PetalsStopThread petalsStopThread;

    /**
     * {@inheritDoc}
     */
    public Properties retrieveServerProperties() {
        final Properties properties = new Properties();
        final Map<String, String> propertiesMap = this.configurationService.getAllServerProperties();
        for (final Map.Entry<String, String> property : propertiesMap.entrySet()) {
            final String value = property.getValue();
            if (value != null) {
                properties.put(property.getKey(), value);
            } else {

                properties.put(property.getKey(), "");
            }
        }
        return properties;
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see
     * org.ow2.petals.kernel.admin.PetalsAdminServiceMBean#retrieveTopology()
     */
    public Set<Map<String, String>> retrieveTopology(final String securityPassPhrase)
            throws PetalsException {
        this.log.start();

        // Check security
        final boolean isSecurityOk;
        if (securityPassPhrase == null || securityPassPhrase.isEmpty()) {
            isSecurityOk = false;
        } else {
            final String containerTopologyPassPhares = this.containerConfiguration
                    .getTopologyPassPhrase();
            if (containerTopologyPassPhares == null || containerTopologyPassPhares.isEmpty()
                    || !containerTopologyPassPhares.equals(securityPassPhrase)) {
                isSecurityOk = false;
            } else {
                isSecurityOk = true;
            }
        }
        if (this.log.isDebugEnabled()) {
            if (isSecurityOk) {
                this.log.debug("Security validated. Sensible information will be returned.");
            } else {
                this.log.debug("Security not validated. Sensible information will NOT be returned.");
            }
        }

        Set<Map<String, String>> topologyMap = new HashSet<Map<String, String>>();
        topologyMap.add(this.getDomainMap());
        Set<SubDomainConfiguration> subdomainsConfiguration = this.getSubDomainsConfiguration();
        for (SubDomainConfiguration subdomain : subdomainsConfiguration) {
            topologyMap.add(this.getSubdomainMap(subdomain));
        }
        Set<ContainerConfiguration> containersConfiguration = this.getContainersConfiguration();
        for (ContainerConfiguration container : containersConfiguration) {
            Map<String, String> containerMap = this.getContainerMap(container, isSecurityOk);
            topologyMap.add(containerMap);
        }
        this.log.end();
        return topologyMap;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.ow2.petals.kernel.admin.PetalsAdminServiceMBean#shutdownContainer()
     */
    public void shutdownContainer() throws Exception {
        this.log.start();

        this.log.info("PEtALS is shutting down...");

        this.log.info("  1/3 Undeploy Service Assemblies...");

        try {
            this.deployment.shutdown();
        } catch (Exception e) {
            this.log.error("Can not shut down the Deployment service.", e);
        }

        this.log.info("  2/3 Uninstall Shared Libraries and Components...");

        try {
            this.installation.shutdown();
        } catch (Exception e) {
            this.log.error("Can not shut down the Installation service", e);
        }

        this.log.info("  3/3 Stopping the server...");

        // launch the stop of the Petals server in a different thread
        this.petalsStopThread.start();

        this.log.end();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ow2.petals.kernel.admin.PetalsAdminServiceMBean#stopContainer()
     */
    public void stopContainer() throws Exception {
        this.log.start();

        this.log.info("PEtALS is stopping...");
        // launch the stop of the Petals server in a different thread
        this.petalsStopThread.start();

        this.log.end();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.ow2.petals.kernel.admin.PetalsAdminServiceInterface#setPetalsStopThread
     * (org.ow2.petals.kernel.server.PetalsStopThread)
     */
    public void setPetalsStopThread(PetalsStopThread petalsStopThread) {
        this.petalsStopThread = petalsStopThread;
    }

    /**
     * @see org.objectweb.fractal.api.control.LifeCycleController#startFc()
     */
    @LifeCycle(on = LifeCycleType.START)
    protected void start() throws IllegalLifeCycleException {
        this.log = new LoggingUtil(java.util.logging.Logger.getLogger(Constants.FRACTAL_COMPONENT_LOGGER_NAME));
        this.log.start();

        this.containerConfiguration = this.configurationService.getContainerConfiguration();
        this.subDomainConfiguration = this.configurationService.getSubDomainConfiguration();
        this.domainConfiguration = this.configurationService.getDomainConfiguration();

        this.log.end();
    }

    /**
     * @see org.objectweb.fractal.api.control.LifeCycleController#stopFc()
     */
    @LifeCycle(on = LifeCycleType.STOP)
    protected void stop() throws IllegalLifeCycleException {
        this.log.call();
    }

    /**
     * Get the collection of all the container configurations of this domain.
     * 
     * @return Set of ContainerConfiguration
     * @throws PetalsException
     */
    private Set<ContainerConfiguration> getContainersConfiguration() throws PetalsException {
        this.log.start();

        Set<ContainerConfiguration> containers = null;

        if (this.topologyService == null) {
            // it is the standalone distribution
            containers = new HashSet<ContainerConfiguration>();
            containers.add(this.containerConfiguration);
        } else {
            containers = this.topologyService.getContainersConfiguration(null);
        }

        this.log.end();
        return containers;
    }

    /**
     * Get the collection of all the sub-domain configurations of this domain.
     * 
     * @return Set of Sub-domain
     * @throws PetalsException
     */
    private Set<SubDomainConfiguration> getSubDomainsConfiguration() throws PetalsException {
        this.log.start();

        Set<SubDomainConfiguration> subdomains = null;

        if (this.topologyService == null) {
            // it is the standalone distribution
            subdomains = new HashSet<SubDomainConfiguration>();
            subdomains.add(this.subDomainConfiguration);
        } else {
            subdomains = this.topologyService.getSubDomainsConfiguration();

        }

        this.log.end();
        return subdomains;
    }

    /**
     * Get the domain configuration in key/value form.
     * 
     * @return
     */
    private Map<String, String> getDomainMap() {
        this.log.start();
        Map<String, String> domainMap = new HashMap<String, String>();
        domainMap.put(Topology.TOPOLOGY_TYPE, Domain.TOPOLOGY_TYPE);
        domainMap.put(Domain.CONF_DOMAIN_NAME, this.domainConfiguration.getName());
        if (this.domainConfiguration.getDescription() != null) {
            domainMap
                    .put(Domain.CONF_DOMAIN_DESCRIPTION, this.domainConfiguration.getDescription());
        }
        if (DomainConfiguration.DomainMode.STATIC.equals(this.domainConfiguration.getMode())) {
            domainMap.put(Domain.CONF_DOMAIN_MODE, "static");
        } else {
            domainMap.put(Domain.CONF_DOMAIN_MODE, "standalone");
        }
        if (this.domainConfiguration.getJndiConfiguration() != null) {
            if (this.domainConfiguration.getJndiConfiguration().getJndiFactory() != null) {
                domainMap.put(Domain.CONF_JNDI_FACTORY, this.domainConfiguration
                        .getJndiConfiguration().getJndiFactory());
            }

            domainMap.put(Domain.CONF_JNDI_PROVIDER_URL, this.domainConfiguration
                    .getJndiConfiguration().getJndiProviderUrl().toString());
            domainMap.put(Domain.CONF_JNDI_SECURITY_PRINCIPAL, this.domainConfiguration
                    .getJndiConfiguration().getJndiSecurityPrincipal());
            domainMap.put(Domain.CONF_JNDI_SECURITY_CREDENTIALS, this.domainConfiguration
                    .getJndiConfiguration().getJndiSecurityCredentials());
            domainMap.put(Domain.CONF_JNDI_POOL_SIZE, Integer.toString(this.domainConfiguration
                    .getJndiConfiguration().getJndiPoolSize()));
            domainMap.put(Domain.CONF_JNDI_BATCH_SIZE, Integer.toString(this.domainConfiguration
                    .getJndiConfiguration().getJndiBatchSize()));
        }
        this.log.end();
        return domainMap;
    }

    /**
     * Get the sub-domain configuration in key/value form.
     * 
     * @param subdomain
     * @return
     */
    private Map<String, String> getSubdomainMap(SubDomainConfiguration subdomain) {
        this.log.start();
        Map<String, String> subdomainMap = new Hashtable<String, String>();
        subdomainMap.put(Topology.TOPOLOGY_TYPE, SubDomain.TOPOLOGY_TYPE);
        subdomainMap.put(SubDomain.CONF_SUBDOMAIN_NAME, subdomain.getName());
        if (subdomain.getDescription() != null) {
            subdomainMap.put(SubDomain.CONF_SUBDOMAIN_DESCRIPTION, subdomain.getDescription());
        }
        subdomainMap.put(SubDomain.CONF_NETWORK_TIME_SYNCHRONIZED, Boolean.toString(subdomain
                .isNetworkTimeSynchronized()));

        this.log.end();
        return subdomainMap;
    }

    /**
     * Get the container configuration in key/value form.
     * 
     * @param container
     * @param isSecurityOk
     *            if <code>true</code>, sensible information are returned.
     *            Otherwise, sensible information are skipped.
     * @return
     */
    private Map<String, String> getContainerMap(ContainerConfiguration container,
            final boolean isSecurityOk) {
        this.log.start();

        Map<String, String> containerMap = new Hashtable<String, String>();
        containerMap.put(Topology.TOPOLOGY_TYPE, Container.TOPOLOGY_TYPE);
        containerMap.put(Container.CONF_CONTAINER_NAME, container.getName());
        containerMap.put(Container.CONF_NODE_TYPE, container.getRegistryMode().toString());
        if (container.getDescription() != null) {
            containerMap.put(Container.CONF_CONTAINER_DESCRIPTION, container
                    .getDescription());
        }
        containerMap.put(Container.CONF_SUBDOMAIN_NAME, container.getSubdomainName());
        containerMap.put(Container.CONF_HOST, container.getHost());

        if (isSecurityOk) {
            containerMap.put(Container.CONF_USER, container.getUser());
            containerMap.put(Container.CONF_PWD, container.getPassword());
        }

        containerMap.put(Container.CONF_JMX_RMI_PORT, Integer.toString(container
                .getJmxRMIConnectorPort()));

        if(container.getWebservicePort() != 0) {
            containerMap.put(Container.CONF_WEBSERVICE_HTTP_PORT, Integer.toString(container
                .getWebservicePort()));
        }
        containerMap.put(Container.CONF_WEBSERVICE_URL_PREFIX, container
                .getWebservicePrefix());

        if (container.getTCPPort() != 0) {
            containerMap.put(Container.CONF_TRANSPORT_TCP_PORT, Integer.toString(container
                    .getTCPPort()));
        }

        if(container.getRegistryPort() != 0) {
            containerMap.put(Container.CONF_REGISTRY_PORT, Integer.toString(container
                    .getRegistryPort()));
        }
        
        if (ContainerConfiguration.ContainerState.STARTED.equals(container.getState())) {
            containerMap.put(Container.CONF_STATE, ContainerStates.STATE_STARTED);
        } else {
            containerMap.put(Container.CONF_STATE, ContainerStates.STATE_STOPPED);
        }

        this.log.end();
        return containerMap;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ow2.petals.kernel.admin.PetalsAdminServiceMBean#ping()
     */
    public boolean ping() {
        return this.topologyService.isContainerStarted(this.containerConfiguration.getName());
    }
}
