/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This 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 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.jmbeans;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

public class ServiceManager {
     private Log logger = LogFactory.getLog(J2EEServer.class);
    /**
     * Reference to the J2EEServer object associated to the managed server.
     */
    private J2EEServer server = null;
    /**
     * This table contains the managed services state.
     * The keys are the service names.
     */
    private Hashtable<String, Object> servicesState = null;

    /**
     * Construct a service manager for JOnAS server.
     * @param server the JOnAS server name
     */
    public ServiceManager(final J2EEServer server) {
        this.server = server;
        this.servicesState = new Hashtable<String, Object>();
    }

    /**
     * Add a service item at server startup or restart.
     * @param serviceItem the service item
     */
    public void addService(final ServiceItem serviceItem) {
        servicesState.put(serviceItem.getName(), serviceItem);
    }

    /**
     * Set mandatory attribute to TRUE.
     * @param serviceName the service name.
     */
    public void setMandatoryService(final String serviceName) {
        ServiceItem serviceItem = (ServiceItem) servicesState.get(serviceName);
        if (serviceItem != null) {
            serviceItem.setMandatory(true);
        }
    }

    /**
     * Delete all services.
     */
    public void deleteAllServices() {
        servicesState.clear();
    }

    /**
     * @return The services names.
     */
    public ArrayList<String> getServices() {
        ArrayList<String> sl = new ArrayList<String>();
        Iterator<Object> items = servicesState.values().iterator();
        while (items.hasNext()) {
            ServiceItem serviceItem = (ServiceItem) items.next();
            sl.add(serviceItem.getName());
        }
        return sl;
    }

    /**
     * Modify a service state. This may arrive at startup when a service registers itself
     * before the J2EEServer server initialization phase, or due to a service state
     * notification (ServiceEvent in OSGI).
     * @param serviceName name of the service
     * @param state new state
     * @return the service state
     */
    public ServiceState setServiceState(final String serviceName, final ServiceState state) {
        if (servicesState == null) {
            logger.error("setServiceState called but servicesState table null");
            return null;
        }
        /**
         * Get the ServiceItem corresponding to this service in order to check
         * and update its state
         */
        ServiceItem serviceItem = (ServiceItem) servicesState.get(serviceName);
        if (serviceItem == null) {
            logger.error("setServiceState called but service " + serviceName + " not known");
            return null;
        }
        ServiceState prevState = serviceItem.getState();
        if (prevState != null && prevState.equals(state)) {
            // State didn't changed (bizZare)
            return prevState;
        } else {
            // State changed, update it
            serviceItem.setState(state);
            // Pass service name for special cases of service restart
            checkServerState(serviceName);
            return state;
        }
    }

    /**
     * Return the state of a given service.
     * @param serviceName service name
     * @return the service state
     */
    public ServiceState getServiceState(final String serviceName) {
        ServiceItem serviceItem = (ServiceItem) servicesState.get(serviceName);
        if (serviceItem != null) {
            return serviceItem.getState();
        }
        logger.error("getServiceState called but service " + serviceName + " not known");
        return null;
    }

    /**
     * Return the description of a given service.
     * @param serviceName service name
     * @return the service description
     */
    public String getServiceDescription(final String serviceName) {
        ServiceItem serviceItem = (ServiceItem) servicesState.get(serviceName);
        if (serviceItem != null) {
            return serviceItem.getDescription();
        }
        logger.error("getServiceDescription called but service " + serviceName + " not known");
        return null;
    }

    /**
     * @return True if all the managed services are in RUNNING state, False otherwise.
     */
    private boolean allServicesRunning() {
        boolean servicesRunning = true;
        Iterator<Object> items = servicesState.values().iterator();
        while (items.hasNext()) {
            ServiceItem serviceItem = (ServiceItem) items.next();
            ServiceState state = serviceItem.getState();
            if (!state.equals(ServiceState.RUNNING)) {
                servicesRunning = false;
                break;
            }
        }
        return servicesRunning;
    }

    /**
     * @return True if all the managed services, except the mandatory ones, are in RUNNING state.
     * False otherwise.
     */
    private boolean allServicesStopped() {
        boolean servicesStopped = true;
        Iterator<Object> items = servicesState.values().iterator();
        while (items.hasNext()) {
            ServiceItem serviceItem = (ServiceItem) items.next();
            ServiceState state = serviceItem.getState();
            if (!state.equals(ServiceState.STOPPED)) {
                // Check if this is not a mandatory service
                if (!serviceItem.isMandatory()) {
                    servicesStopped = false;
                    break;
                }
            }
        }
        return servicesStopped;
    }

    /**
     * Implement state transition from STARTING to RUNNING and
     * from STOPPING/RUNNING to STOPPED.
     * @param serviceName the name of the service that changed state
     */
    protected void checkServerState(final String serviceName) {
        //dumpServicesState();
        /**
         * Implement STARTING to RUNNING transition.
         */
        if (server.isStarting()) {
            // Check if all services are running
            if (allServicesRunning()) {
                server.setServicesRunning();
            }
        }
        /**
         * RUNNING to RUNNING detect a service restart.
         */
        if (server.isRunning()) {
            // Special case to treat in case of depmonitor service restart
            if ("depmonitor".equals(serviceName) && depMonitorRunning()) {
                server.setDepmonitoringRunning(null);
            }
        }
        /**
         * Implement RUNNING --> STOPPED transition
         * Implement STOPPING --> STOPPED transition
         * When all services are stopped
         */
        if (server.isRunning() || server.isStopping()) {
            // Check if all services are stopped
            if (allServicesStopped()) {
                server.setStopped();
            }
        }
    }

    /**
     * @return True only if the depmonitor service is running
     */
    private boolean depMonitorRunning() {
        ServiceItem depMonitorItem = (ServiceItem) servicesState.get(ServiceName.DEPMONITOR);
        if (depMonitorItem != null) {
            ServiceState depMonitorState = depMonitorItem.getState();
            if (ServiceState.RUNNING.equals(depMonitorState)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Can be used to debug.
     */
    private void dumpServicesState() {
        Iterator<Object> items = servicesState.values().iterator();
        System.out.println("********************");
        while (items.hasNext()) {
            ServiceItem serviceItem = (ServiceItem) items.next();
            String name = serviceItem.getName();
            String desc = serviceItem.getDescription();
            ServiceState state = serviceItem.getState();
            boolean mandatory = serviceItem.isMandatory();
            if (mandatory) {
                System.out.println("** Mandatory Service " + name + " (" + desc + ") is " + state);
            } else {
                System.out.println("** Service " + name + " (" + desc + ") is " + state);
            }
        }
    }
}
