/**
 * Copyright (c) 2013-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.extension;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.api.control.IllegalLifeCycleException;
import org.objectweb.fractal.api.control.SuperController;
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.Service;
import org.objectweb.fractal.fraclet.annotation.annotations.type.LifeCycleType;
import org.objectweb.fractal.util.Fractal;
import org.ow2.petals.microkernel.api.configuration.ConfigurationService;
import org.ow2.petals.microkernel.api.extension.InstallationExtension;
import org.ow2.petals.microkernel.api.extension.PetalsExtensionController;
import org.ow2.petals.microkernel.api.extension.exception.CreationExtensionException;
import org.ow2.petals.microkernel.api.extension.exception.NotSatisfiedDependenciesException;
import org.ow2.petals.microkernel.api.extension.exception.PetalsExtensionException;
import org.ow2.petals.microkernel.api.server.FractalHelper;
import org.ow2.petals.microkernel.api.util.LoggingUtil;

/**
 * The implementation of installation extension manager
 * 
 * @author Christophe DENEUX - Linagora
 * 
 */
@FractalComponent
@Provides(interfaces = @Interface(name = "service", signature = ExtensionsManager.class))
public class ExtensionsManagerImpl implements ExtensionsManager {

    /**
     * The configuration service
     */
    @Requires(name = "configuration", signature = ConfigurationService.class)
    private ConfigurationService configurationService;

    /**
     * The installation extension manager
     */
    @Requires(name = "installationExtMgr", signature = InstallationExtensionsManager.class)
    private InstallationExtensionsManager installationExtMgr;

    @Service
    private Component comp;

    private LoggingUtil log = new LoggingUtil(
            java.util.logging.Logger.getLogger(ExtensionsManager.COMPONENT_LOGGER_NAME));

    private Map<PetalsExtensionController, Component> extensionControllers = new HashMap<PetalsExtensionController, Component>();

    /**
     * Starts the installation extensions manager.
     */
    @LifeCycle(on = LifeCycleType.START)
    protected void start() throws PetalsExtensionException, NotSatisfiedDependenciesException {
        this.log.start();

        // Retrieve the extension composite
        try {
            final SuperController extensionMgrSC = Fractal.getSuperController(this.comp);
            if (extensionMgrSC.getFcSuperComponents().length != 1) {
                throw new PetalsExtensionException("Error in fractal architecture");
            }
            final Component extensionComposite = extensionMgrSC.getFcSuperComponents()[0];

            // Load extensions looking up extension controller available on the
            // classloader, and instantiating associated Fractal component
            final ServiceLoader<PetalsExtensionController> serviceLoader = ServiceLoader
                    .load(PetalsExtensionController.class);
            final Iterator<PetalsExtensionController> servicesIterator = serviceLoader.iterator();
            if (servicesIterator.hasNext()) {
                final List<PetalsExtensionController> extensionControllers = new ArrayList<PetalsExtensionController>();
                while (servicesIterator.hasNext()) {
                    final PetalsExtensionController extensionController = servicesIterator.next();
                    extensionControllers.add(extensionController);
                }

                final List<PetalsExtensionController> orderedExtensionControllers = this
                        .sortExtensionControllers(extensionControllers);

                for (final PetalsExtensionController extensionController : orderedExtensionControllers) {
                    this.log.debug("Loading extension: " + extensionController.getExtensionName());
                    if (extensionController.isActivated(this.configurationService
                            .getServerProperties())) {
                        // Check that all dependencies are activated
                        if (this.isAllDependenciesLoaded(extensionController)) {
                            this.log.debug("Extension: " + extensionController.getExtensionName()
                                    + " activated.");

                            try {
                                final Component extensionImpl = extensionController
                                        .createFractalComponent(extensionComposite);

                                this.log.debug("Extension: "
                                        + extensionController.getExtensionName()
                                        + ", Fractal component created.");

                                FractalHelper.startComponent(extensionImpl);
                                this.log.debug("Extension: "
                                        + extensionController.getExtensionName()
                                        + ", Fractal component started.");

                                this.extensionControllers.put(extensionController, extensionImpl);

                                final Object extensionItf = extensionImpl.getFcInterface("service");
                                if (extensionItf instanceof InstallationExtension) {
                                    final InstallationExtension installationExtension = (InstallationExtension) extensionItf;
                                    this.installationExtMgr.register(installationExtension);
                                }
                                this.log.info("Extension loaded: "
                                        + extensionController.getExtensionName());
                            } catch (final NoSuchInterfaceException e) {
                                this.log.warning("Error starting the extension '"
                                        + extensionController.getExtensionName() + "'", e);
                            } catch (IllegalLifeCycleException e) {
                                this.log.warning("Error starting the extension '"
                                        + extensionController.getExtensionName() + "'", e);
                            } catch (final CreationExtensionException e) {
                                this.log.warning("Error starting the extension '"
                                        + extensionController.getExtensionName() + "'", e);
                            }
                        } else {
                            this.log.warning("Unable to activate the extension '"
                                    + extensionController.getExtensionName()
                                    + "' because a dependency is not activated.");
                        }
                    } else {
                        this.log.info("Extension found but not activated: "
                                + extensionController.getExtensionName());
                    }
                }
            } else {
                this.log.info("No Petals extension found.");
            }
        } catch (final NoSuchInterfaceException e) {
            throw new PetalsExtensionException("Unable to retrieve the extension composite", e);
        } catch (final NotSatisfiedDependenciesException e) {
            this.log.warning(e.getMessage());
            for (final PetalsExtensionController pec : e.getExtensionControllers()) {
                this.log.warning(String.format("\tThe extension '%s' requires %s",
                        pec.getExtensionName(), Arrays.toString(pec.getDependencies())));
            }
            throw e;
        } finally {
            this.log.end();
        }
    }

    /**
     * Check that all required dependencies of an extension are activated
     * 
     * @param extensionCtrl
     *            The extension controller of the extension to check
     * @return <code>true</code> if all dependencies are activated,
     *         <code>false</code> if one dependency is not activated
     */
    private boolean isAllDependenciesLoaded(final PetalsExtensionController extensionCtrl) {

        final String[] dependencies = extensionCtrl.getDependencies();
        if (dependencies == null || dependencies.length == 0) {
            return true;
        } else {
            final List<String> extensionAlreadyLoaded = new ArrayList<String>();
            for (final PetalsExtensionController pec : this.extensionControllers.keySet()) {
                extensionAlreadyLoaded.add(pec.getExtensionName());
            }
            for (final String dependency : dependencies) {
                if (!extensionAlreadyLoaded.contains(dependency)) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * Order the extension controllers according to their dependencies on other
     * extension
     * 
     * @param extensionControllers
     *            The extension controllers to sort
     * @return The sorted extension controllers
     * @throws NotSatisfiedDependenciesException
     *             Some extension dependencies are not satisfied
     */
    private List<PetalsExtensionController> sortExtensionControllers(
            final List<PetalsExtensionController> extensionControllers)
            throws NotSatisfiedDependenciesException {

        final List<PetalsExtensionController> orderedExtensionController = new ArrayList<PetalsExtensionController>();
        final List<String> orderedExtensionNames = new ArrayList<String>();

        // First, we add extension controllers that have no dependency
        {
            final Iterator<PetalsExtensionController> itExtensionControllers = extensionControllers
                    .iterator();
            while (itExtensionControllers.hasNext()) {
                final PetalsExtensionController extensionController = itExtensionControllers.next();
                final String[] extensionDependencies = extensionController.getDependencies();
                if (extensionDependencies == null || extensionDependencies.length == 0) {
                    orderedExtensionController.add(extensionController);
                    orderedExtensionNames.add(extensionController.getExtensionName());
                    itExtensionControllers.remove();
                }
            }
        }

        // Second, we add extension controllers that have dependencies
        {
            boolean extensionAdded = false;
            do {
                final Iterator<PetalsExtensionController> itExtensionControllers = extensionControllers
                        .iterator();
                while (itExtensionControllers.hasNext()) {
                    final PetalsExtensionController extensionController = itExtensionControllers
                            .next();
                    final String[] extensionDependencies = extensionController.getDependencies();
                    boolean allDependenciesFound = true;
                    for (final String extensionDependency : extensionDependencies) {
                        if (!orderedExtensionNames.contains(extensionDependency)) {
                            allDependenciesFound = false;
                            break;
                        }
                    }

                    if (allDependenciesFound) {
                        orderedExtensionController.add(extensionController);
                        orderedExtensionNames.add(extensionController.getExtensionName());
                        itExtensionControllers.remove();
                        extensionAdded = true;
                    } else {
                        extensionAdded = false;
                    }
                }
            } while (extensionAdded && extensionControllers.size() > 0);

            if (extensionControllers.size() != 0) {
                // Some dependencies are not satisfied
                throw new NotSatisfiedDependenciesException(extensionControllers);
            }
        }

        return orderedExtensionController;

    }

    /**
     * Stops the installation extensions manager.
     */
    @LifeCycle(on = LifeCycleType.STOP)
    protected void stop() {
        this.log.call();

        // Unload extensions
        final Iterator<Map.Entry<PetalsExtensionController, Component>> itExtensionEntries = this.extensionControllers
                .entrySet().iterator();
        while (itExtensionEntries.hasNext()) {
            final Map.Entry<PetalsExtensionController, Component> entry = itExtensionEntries.next();
            final PetalsExtensionController extensionController = entry.getKey();
            final Component extensionImpl = entry.getValue();
            try {
                FractalHelper.stopComponent(extensionImpl);
                final Object extensionItf = extensionImpl.getFcInterface("service");
                if (extensionItf instanceof InstallationExtension) {
                    final InstallationExtension installationExtension = (InstallationExtension) extensionItf;
                    this.installationExtMgr.deregister(installationExtension);
                }
            } catch (final NoSuchInterfaceException e) {
                this.log.warning(
                        "Error stopping or removing the extension '"
                                + extensionController.getExtensionName()
                                + "'", e);
            } catch (final IllegalLifeCycleException e) {
                this.log.warning(
                        "Error stopping or removing the extension '"
                                + extensionController.getExtensionName()
                                + "'", e);
            }

            extensionController.removeFractalComponent();

            itExtensionEntries.remove();
        }
    }

}
