/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.servicemix.jbi.framework;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;

import javax.jbi.component.ServiceUnitManager;
import javax.jbi.management.DeploymentException;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanOperationInfo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.servicemix.jbi.deployment.Descriptor;
import org.apache.servicemix.jbi.deployment.DescriptorFactory;
import org.apache.servicemix.jbi.deployment.ServiceUnit;
import org.apache.servicemix.jbi.deployment.Services;
import org.apache.servicemix.jbi.event.ServiceUnitEvent;
import org.apache.servicemix.jbi.event.ServiceUnitListener;
import org.apache.servicemix.jbi.management.AttributeInfoHelper;
import org.apache.servicemix.jbi.management.MBeanInfoProvider;
import org.apache.servicemix.jbi.management.OperationInfoHelper;

public class ServiceUnitLifeCycle implements ServiceUnitMBean, MBeanInfoProvider {

    private static final Log LOG = LogFactory.getLog(ServiceUnitLifeCycle.class);

    private ServiceUnit serviceUnit;

    private String currentState = SHUTDOWN;
    
    private String serviceAssembly;
    
    private Registry registry;

    private PropertyChangeListener listener;
    
    private Services services;
    
    private File rootDir;
    
    public ServiceUnitLifeCycle(ServiceUnit serviceUnit, 
                                String serviceAssembly,
                                Registry registry,
                                File rootDir) {
        this.serviceUnit = serviceUnit;
        this.serviceAssembly = serviceAssembly;
        this.registry = registry;
        this.rootDir = rootDir;
        Descriptor d = DescriptorFactory.buildDescriptor(rootDir);
        if (d != null) {
            services = d.getServices();
        }
    }

    /**
     * Initialize the service unit.
     * @throws DeploymentException 
     */
    public void init() throws DeploymentException {
        LOG.info("Initializing service unit: " + getName());
        checkComponentStarted("init");
        ServiceUnitManager sum = getServiceUnitManager();
        File path = getServiceUnitRootPath();
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(getComponentClassLoader());
            sum.init(getName(), path.getAbsolutePath());
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
        currentState = STOPPED;
    }
    
    /**
     * Start the service unit.
     * @throws DeploymentException 
     */
    public void start() throws DeploymentException {
        LOG.info("Starting service unit: " + getName());
        checkComponentStarted("start");
        ServiceUnitManager sum = getServiceUnitManager();
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(getComponentClassLoader());
            sum.start(getName());
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
        currentState = STARTED;
    }

    /**
     * Stop the service unit. This suspends current messaging activities.
     * @throws DeploymentException 
     */
    public void stop() throws DeploymentException {
        LOG.info("Stopping service unit: " + getName());
        checkComponentStarted("stop");
        ServiceUnitManager sum = getServiceUnitManager();
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(getComponentClassLoader());
            sum.stop(getName());
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
        currentState = STOPPED;
    }

    /**
     * Shut down the service unit. 
     * This releases resources, preparatory to uninstallation.
     * @throws DeploymentException 
     */
    public void shutDown() throws DeploymentException {
        LOG.info("Shutting down service unit: " + getName());
        checkComponentStartedOrStopped("shutDown");
        ServiceUnitManager sum = getServiceUnitManager();
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(getComponentClassLoader());
            sum.shutDown(getName());
        } finally {
            Thread.currentThread().setContextClassLoader(cl);
        }
        currentState = SHUTDOWN;
    }

    /**
     * @return the currentState as a String
     */
    public String getCurrentState() {
        return currentState;
    }

    public boolean isShutDown() {
        return currentState.equals(SHUTDOWN);
    }

    public boolean isStopped() {
        return currentState.equals(STOPPED);
    }

    public boolean isStarted() {
        return currentState.equals(STARTED);
    }
    
    /**
     * @return the name of the ServiceAssembly
     */
    public String getName() {
        return serviceUnit.getIdentification().getName();
    }

    /**
     * @return the description of the ServiceAssembly
     */
    public String getDescription() {
        return serviceUnit.getIdentification().getDescription();
    }
    
    public String getComponentName() {
        return serviceUnit.getTarget().getComponentName();
    }

    public String getServiceAssembly() {
        return serviceAssembly;
    }

    public String getDescriptor() {
        File suDir = getServiceUnitRootPath();
        return DescriptorFactory.getDescriptorAsText(suDir);
    }
    
    public Services getServices() {
        return services;
    }

    protected void checkComponentStarted(String task) throws DeploymentException {
        String componentName = getComponentName();
        String suName = getName();
        ComponentMBeanImpl lcc = registry.getComponent(componentName);
        if (lcc == null) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " is not installed");
        }
        if (!lcc.isStarted()) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " is not started");
        }
        if (lcc.getServiceUnitManager() == null) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " does not accept deployments");
        }
    }
    
    protected void checkComponentStartedOrStopped(String task) throws DeploymentException {
        String componentName = getComponentName();
        String suName = getName();
        ComponentMBeanImpl lcc = registry.getComponent(componentName);
        if (lcc == null) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " is not installed");
        }
        if (!lcc.isStarted() && !lcc.isStopped()) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " is not started");
        }
        if (lcc.getServiceUnitManager() == null) {
            throw ManagementSupport.componentFailure("deploy", componentName, "Target component "
                            + componentName + " for service unit " + suName + " does not accept deployments");
        }
    }
    
    protected File getServiceUnitRootPath() {
        return rootDir;
    }
    
    protected ServiceUnitManager getServiceUnitManager() {
        ComponentMBeanImpl lcc = registry.getComponent(getComponentName());
        return lcc.getServiceUnitManager();
    }

    protected ClassLoader getComponentClassLoader() {
        ComponentMBeanImpl lcc = registry.getComponent(getComponentName());
        // TODO: should retrieve the real component class loader
        return lcc.getComponent().getClass().getClassLoader();
    }

    public MBeanAttributeInfo[] getAttributeInfos() throws JMException {
        AttributeInfoHelper helper = new AttributeInfoHelper();
        helper.addAttribute(getObjectToManage(), "currentState", "current state of the service unit");
        helper.addAttribute(getObjectToManage(), "name", "name of the service unit");
        helper.addAttribute(getObjectToManage(), "componentName", "component name of the service unit");
        helper.addAttribute(getObjectToManage(), "serviceAssembly", "service assembly name of the service unit");
        helper.addAttribute(getObjectToManage(), "description", "description of the service unit");
        return helper.getAttributeInfos();
    }

    public MBeanOperationInfo[] getOperationInfos() throws JMException {
        OperationInfoHelper helper = new OperationInfoHelper();
        helper.addOperation(getObjectToManage(), "getDescriptor", "retrieve the jbi descriptor for this unit");
        return helper.getOperationInfos();
    }

    public Object getObjectToManage() {
        return this;
    }

    public String getType() {
        return "ServiceUnit";
    }

    public String getSubType() {
        return getComponentName();
    }

    public void setPropertyChangeListener(PropertyChangeListener l) {
        this.listener = l;
    }

    protected void firePropertyChanged(String name, Object oldValue, Object newValue) {
        PropertyChangeListener l = listener;
        if (l != null) {
            PropertyChangeEvent event = new PropertyChangeEvent(this, name, oldValue, newValue);
            l.propertyChange(event);
        }
    }

    public String getKey() {
        return getComponentName() + "/" + getName();
    }

    protected void fireEvent(int type) {
        ServiceUnitEvent event = new ServiceUnitEvent(this, type);
        ServiceUnitListener[] listeners = (ServiceUnitListener[]) registry.getContainer().getListeners(ServiceUnitListener.class);
        for (int i = 0; i < listeners.length; i++) {
            switch (type) {
            case ServiceUnitEvent.UNIT_STARTED:
                listeners[i].unitStarted(event);
                break;
            case ServiceUnitEvent.UNIT_STOPPED:
                listeners[i].unitStopped(event);
                break;
            case ServiceUnitEvent.UNIT_SHUTDOWN:
                listeners[i].unitShutDown(event);
                break;
            default:
                break;
            }
        }
    }

}
