/**
 * Copyright (c) 2012 EBM WebSourcing, 2012-2013 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.admin.api;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Logger;

import org.ow2.petals.admin.api.artifact.Artifact;
import org.ow2.petals.admin.api.artifact.ArtifactException;
import org.ow2.petals.admin.api.artifact.ArtifactUtils;
import org.ow2.petals.admin.api.artifact.Component;
import org.ow2.petals.admin.api.artifact.ServiceAssembly;
import org.ow2.petals.admin.api.artifact.SharedLibrary;
import org.ow2.petals.admin.api.artifact.lifecycle.ArtifactLifecycle;
import org.ow2.petals.admin.api.artifact.lifecycle.ArtifactLifecycleFactory;
import org.ow2.petals.admin.api.exception.ArtifactAdministrationException;
import org.ow2.petals.admin.api.exception.ArtifactNotDeployedException;
import org.ow2.petals.admin.api.exception.ArtifactNotFoundException;
import org.ow2.petals.admin.api.exception.ArtifactStartedException;
import org.ow2.petals.admin.api.exception.ArtifactStoppedException;
import org.ow2.petals.admin.api.exception.ArtifactTypeIsNeededException;
import org.ow2.petals.admin.api.exception.UnsupportedArtifactTypeException;

/**
 * 
 * @author Nicolas Oddoux - EBM WebSourcing
 */
public abstract class ArtifactAdministration {

    private final static Logger LOG = Logger.getLogger(ArtifactAdministration.class.getName());

    protected final ArtifactLifecycleFactory artifactLifecycleFactory;

    protected ArtifactAdministration(final ArtifactLifecycleFactory artifactLifecycleFactory) {
        this.artifactLifecycleFactory = artifactLifecycleFactory;
    }

    private final void deployAndStart(Artifact artifact, URL artifactUrl)
            throws ArtifactAdministrationException {
        ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory.createLifecycle(artifact);
        artifactLifecycle.deploy(artifactUrl);
        artifactLifecycle.start();
    }

    public void deployAndStartArtifact(URL artifactUrl) throws ArtifactAdministrationException {
        try {
            Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
            deployAndStart(artifact, artifactUrl);
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    public void deployAndStartArtifact(URL artifactUrl, Properties configurationProperties)
            throws ArtifactAdministrationException {
        try {
            Artifact artifact = ArtifactUtils.createArtifact(artifactUrl, configurationProperties);
            deployAndStart(artifact, artifactUrl);
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }

    }

    public void deployAndStartArtifact(URL artifactUrl, URL configurationFile)
            throws ArtifactAdministrationException {
        try {
            Artifact artifact = ArtifactUtils.createArtifact(artifactUrl, configurationFile);
            deployAndStart(artifact, artifactUrl);
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    /**
     * Start an artifact.
     * 
     * @param artifactUrl
     *            {@link URL of the artifact to start}
     * 
     * @throws ArtifactStartedException
     *             The artifact is already started
     * @throws ArtifactNotDeployedException
     *             The artifact is not deployed
     * @throws ArtifactNotFoundException
     *             The artifact can not be found
     * @throws ArtifactAdministrationException
     *             An error occurs during the artifact start.
     */
    public void startArtifact(final URL artifactUrl) throws ArtifactStartedException,
            ArtifactNotDeployedException, ArtifactNotFoundException,
            ArtifactAdministrationException {
        try {
            final Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
            this.startArtifact(artifact);
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    /**
     * <p>
     * Start an artifact.
     * </p>
     * <p>
     * The artifact type is needed to distinct between two JBI artifacts having
     * the same identifier.
     * </p>
     * 
     * @param type
     *            The nature (SL, SA, BC, SA) of the artifact to stop.
     * 
     * @param name
     *            The JBI identifier of the artifact to stop.
     * @throws UnsupportedArtifactTypeException
     *             <code>type</code> is a not supported artifact type.
     * @throws ArtifactNotFoundException
     *             No artifact found.
     * @throws ArtifactTypeIsNeededException
     *             Several artifacts match the name, the artifact type MUST be
     *             provided to be able to find the right artifact.
     * @throws ArtifactStartedException
     *             The artifact is already started
     * @throws ArtifactNotDeployedException
     *             The artifact is not deployed
     * @throws ArtifactAdministrationException
     *             An error occurs during the artifact start.
     */
    public void startArtifact(final String type, final String name)
            throws UnsupportedArtifactTypeException, ArtifactNotFoundException,
            ArtifactTypeIsNeededException, ArtifactStartedException, ArtifactNotDeployedException,
            ArtifactAdministrationException {
        final Artifact artifact = getArtifact(type, name);
        this.startArtifact(artifact);
    }

    /**
     * Start an artifact.
     * 
     * @param artifact
     *            The artifact to start
     * 
     * @throws ArtifactStartedException
     *             The artifact is already started
     * @throws ArtifactNotDeployedException
     *             The artifact is not deployed
     * @throws ArtifactNotFoundException
     *             The artifact can not be found
     * @throws ArtifactAdministrationException
     *             An error occurs during the artifact start.
     */
    private void startArtifact(final Artifact artifact) throws ArtifactStartedException,
            ArtifactNotDeployedException, ArtifactNotFoundException,
            ArtifactAdministrationException {

        assert artifact != null : "artifact is null";

        final ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory
                .createLifecycle(artifact);
        artifactLifecycle.start();
    }

    /**
     * Stop artifact
     * 
     * @param artifactUrl
     * @throws ArtifactAdministrationException
     */
    public void stopArtifact(URL artifactUrl) throws ArtifactAdministrationException {
        try {
            Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
            ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory.createLifecycle(artifact);
            artifactLifecycle.stop();
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    /**
     * <p>Stop an artifact.</p>
	 * <p>The artifact type is needed to distinct betwwen two JBI artifacts having the same
     * identifier.</p>
     * 
     * @param type
     *            The nature (SL, SA, BC, SA) of the artifact to stop.
     * 
     * @param name
     *            The JBI identifier of the artifact to stop.
     * @throws UnsupportedArtifactTypeException
     *             <code>type</code> is a not supported artifact type.
     * @throws ArtifactNotFoundException
     *             No artifact found.
     * @throws ArtifactTypeIsNeededException
     *             Several artifacts match the name, the artifact type MUST be
     *             provided to be able to find the right artifact.
     * @throws ArtifactAdministrationException
     *             An error occurs retrieving the artifact.
     */
    public void stopArtifact(String type, String name) throws UnsupportedArtifactTypeException,
            ArtifactNotFoundException, ArtifactTypeIsNeededException,
            ArtifactAdministrationException {

        final Artifact artifact = getArtifact(type, name);
        final ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory
                .createLifecycle(artifact);
        artifactLifecycle.stop();
    }

    public void stopAndUndeployArtifact(URL artifactUrl) throws ArtifactAdministrationException {
        try {
            Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
            this.stopAndUndeployArtifact(artifact);
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    /**
	 * <p>Stop and undeploy an artifact.</p>
	 * <p>The artifact type is needed to distinct betwwen two JBI artifacts having the same
     * identifier.</p>
     * @throws UnsupportedArtifactTypeException
     *             <code>type</code> is a not supported artifact type.
     * @throws ArtifactNotFoundException
     *             No artifact found.
     * @throws ArtifactTypeIsNeededException
     *             Several artifacts match the name, the artifact type MUST be
     *             provided to be able to find the right artifact.
     * @throws ArtifactAdministrationException
     *             An error occurs retrieving the artifact.
     */
    public void stopAndUndeployArtifact(String type, String name)
            throws UnsupportedArtifactTypeException, ArtifactNotFoundException,
            ArtifactTypeIsNeededException, ArtifactAdministrationException {
        Artifact artifact = getArtifact(type, name);
        this.stopAndUndeployArtifact(artifact);
    }

    private void stopAndUndeployArtifact(Artifact artifact) throws ArtifactAdministrationException {

        final ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory
                .createLifecycle(artifact);
        try {
            artifactLifecycle.stop();
        } catch (final ArtifactStoppedException e) {
            // Artifact already stopped, we can continue the undeployment
            // lifecycle
        } catch (final ArtifactNotDeployedException e) {
            // Artifact not completely deployed, we can continue the
            // undeployment
        }

        artifactLifecycle.undeploy();
    }

    public Artifact getArtifactInfo(final URL artifactUrl) throws ArtifactAdministrationException {
        try {
            final Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
            final ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory
                    .createLifecycle(artifact);
            artifactLifecycle.updateState();
            return artifact;
        } catch (ArtifactException ae) {
            throw new ArtifactAdministrationException(ae);
        }
    }

    /**
     * <p>
     * Get information about an artifact already deployed.
     * </p>
     * <p>
     * The artifact type is needed to distinct betwwen two JBI artifacts having the same
     * identifier.
     * </p>
     * 
     * @param type
     *            artifact type (SL,BC,SE,SA). Can be <code>null</code>.
     * @param name
     *            Artifact identifier. Not <code>null</code>.
     * @throws UnsupportedArtifactTypeException
     *             <code>type</code> is a not supported artifact type.
     * @throws ArtifactNotFoundException
     *             No artifact found.
     * @throws ArtifactTypeIsNeededException
     *             Several artifacts match the name, the artifact type MUST be
     *             provided to be able to find the right artifact.
     * @throws ArtifactAdministrationException
     *             An error occurs retrieving the artifact.
     */
    public Artifact getArtifactInfo(String type, String name)
            throws UnsupportedArtifactTypeException, ArtifactNotFoundException,
            ArtifactTypeIsNeededException, ArtifactAdministrationException {

        final Artifact artifact = getArtifact(type, name);
        final ArtifactLifecycle artifactLifecycle = this.artifactLifecycleFactory
                .createLifecycle(artifact);
        artifactLifecycle.updateState();
        return artifact;
    }

    /**
     * Deploy and start all specified artifacts taking into account the artifact
     * type.
     * 
     * @param artifactsUrl
     *            artifacts to deploy and start
     * @throws ArtifactAdministrationException
     */
    public void deployAndStartArtifacts(URL[] artifactsUrl) throws ArtifactAdministrationException {
        final Map<URI, Artifact> sls = new HashMap<URI, Artifact>();
        final Map<URI, Artifact> components = new HashMap<URI, Artifact>();
        final Map<URI, Artifact> sas = new HashMap<URI, Artifact>();
        final Map<URI, Artifact> unknows = new HashMap<URI, Artifact>();

        try {
            for (URL url : artifactsUrl) {
                Artifact artifact;
                try {
                    artifact = ArtifactUtils.createArtifact(url);
                    if (artifact instanceof SharedLibrary) {
                        sls.put(url.toURI(), artifact);
                    } else if (artifact instanceof Component) {
                        components.put(url.toURI(), artifact);
                    } else if (artifact instanceof ServiceAssembly) {
                        sas.put(url.toURI(), artifact);
                    } else {
                        unknows.put(url.toURI(), artifact);
                    }
                } catch (ArtifactException e) {
                    throw new ArtifactAdministrationException(e);
                }
            }

            final Map<URI, Artifact> orderedMap = new LinkedHashMap<URI, Artifact>();
            orderedMap.putAll(sls);
            orderedMap.putAll(components);
            orderedMap.putAll(sas);
            orderedMap.putAll(unknows);

            for (Entry<URI, Artifact> entry : orderedMap.entrySet()) {
                this.deployAndStart(entry.getValue(), entry.getKey().toURL());
            }
        } catch (final MalformedURLException e) {
            throw new ArtifactAdministrationException(e);
        } catch (final URISyntaxException e) {
            throw new ArtifactAdministrationException(e);
        }
    }

    /**
     * <p>
     * Stop and undeploy all artifacts currently deployed in the container.
     * </p>
     * 
     * @throws ArtifactAdministrationException
     */
    public abstract void stopAndUndeployAllArtifacts() throws ArtifactAdministrationException;

    /**
     * <p>
     * Stop and undeploy all specified artifacts, taking into account the
     * artifact type.
     * </p>
     * <p>
     * If <code>artifactsUrl</code> is <code>null</code>, all artifacts
     * installed or deployed on the container will be stopped and undeployed.
     * </p>
     * <p>
     * If <code>artifactsUrl</code> is empty, no artifact will be stopped and
     * undeployed.
     * </p>
     * 
     * @param artifactsUrl
     *            artifacts to stop and undeploy. Can be <code>null</code> or
     *            empty.
     * @throws ArtifactAdministrationException
     */
    public void stopAndUndeployAllArtifacts(final URL[] artifactsUrl)
            throws ArtifactAdministrationException {
        if (artifactsUrl == null) {
            this.stopAndUndeployAllArtifacts();
        } else {
            try {
                // First, we undeploy service-assemblies
                for (final URL artifactUrl : artifactsUrl) {
                    final Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
                    if (ServiceAssembly.TYPE.equals(artifact.getType())) {
                        try {
                            this.stopAndUndeployArtifact(artifactUrl);
                        } catch (final ArtifactNotFoundException e) {
                            // Artifact not deployed, we continue with a warning
                            LOG.warning(String
                                    .format("The artifact [%s] (type; %s) cannot be found. Undeployment skipped !",
                                            e.getName(), e.getType()));
                        }
                    }
                }
                // 2nd, we uninstall components
                for (final URL artifactUrl : artifactsUrl) {
                    final Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
                    if (Component.ComponentType.BC.toString().equals(artifact.getType())
                            || Component.ComponentType.SE.toString().equals(artifact.getType())) {
                        try {
                            this.stopAndUndeployArtifact(artifactUrl);
                        } catch (final ArtifactNotFoundException e) {
                            // Artifact not deployed, we continue with a warning
                            LOG.warning(String
                                    .format("The artifact [%s] (type; %s) cannot be found. Undeployment skipped !",
                                            e.getName(), e.getType()));
                        }
                    }
                }
                // And we finish with shared libraries
                for (final URL artifactUrl : artifactsUrl) {
                    final Artifact artifact = ArtifactUtils.createArtifact(artifactUrl);
                    if (SharedLibrary.TYPE.equals(artifact.getType())) {
                        try {
                            this.stopAndUndeployArtifact(artifactUrl);
                        } catch (final ArtifactNotFoundException e) {
                            // Artifact not deployed, we continue with a warning
                            LOG.warning(String
                                    .format("The artifact [%s] (type; %s) cannot be found. Undeployment skipped !",
                                            e.getName(), e.getType()));
                        }
                    }
                }
            } catch (final ArtifactException e) {
                throw new ArtifactAdministrationException(e);
            }
        }
    }

    /**
     * <p>
     * Get the list of artifact deployed on the current Petals node.
     * </p>
     * <p>
     * Artifacts returned are:
     * <ul>
     * <li>shared libraries,</li>
     * <li>components, included components that have only their component
     * installer loaded,</li>
     * <li>service assemblies.</li>
     * </ul>
     * </p>
     * 
     * @return The list of artifact deployed on the current Petals node
     * @throws ArtifactAdministrationException
     */
    public abstract List<Artifact> listArtifacts() throws ArtifactAdministrationException;

    /**
     * <p>
     * Create an artifact with this type and name. The artifact to return is
     * resolved by the underlying implementation.
     * </p>
     * <p>
     * The artifact type is needed to distinct betwwen two JBI artifacts having the same
     * identifier.
     * </p>
     * 
     * @param type
     *            artifact type (SL,BC,SE,SA). Can be <code>null</code>.
     * @param name
     *            Artifact identifier. Not <code>null</code>.
     * @return
     * @throws UnsupportedArtifactTypeException
     *             <code>type</code> is a not supported artifact type.
     * @throws ArtifactNotFoundException
     *             No artifact found.
     * @throws ArtifactTypeIsNeededException
     *             Several artifacts match the name, the artifact type MUST be
     *             provided to be able to find the right artifact.
     * @throws ArtifactAdministrationException
     *             An error occurs retrieving the artifact.
     */
    public abstract Artifact getArtifact(String type, String name)
            throws UnsupportedArtifactTypeException, ArtifactNotFoundException,
            ArtifactTypeIsNeededException, ArtifactAdministrationException;
}
