/*
 * Copyright (C) 2001 - 2025 ScalAgent Distributed Technologies
 * Copyright (C) 1996 - 2000 BULL
 * Copyright (C) 1996 - 2000 INRIA
 *
 * 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 */
package fr.dyade.aaa.agent;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Set;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.Strings;
import fr.dyade.aaa.common.Debug;

/**
 * Object which manages services.
 * There is only one <code>ServiceManager</code> object per agent server.
 * The <code>ServiceManager</code> object is initialized in <code>init</code>,
 * called from <code>AgentServer.init</code>. This classes reuses the
 * persistency service provided by <code>Transaction</code>.
 */
public class ServiceManager implements Serializable {
  /** Define serialVersionUID for interoperability. */
  private static final long serialVersionUID = 1L;

  static Logger logger = Debug.getLogger(ServiceManager.class.getName());
  private static final boolean DEBUG = logger.isLoggable(BasicLevel.DEBUG);

  /** the unique <code>ServiceManager</code> in the agent server */
  static ServiceManager manager;

  private static String name = null;

  public final static String getName() {
    if (name == null)
      name = AgentServer.getName() + ".ServiceManager";
    return name;
  }
  
  /**
   * Initializes the <code>ServiceManager</code> object. Synchronize the
   * persistent image and the configuration file.
   *
   * @exception Exception	unspecialized exception
   */
  static void init() throws Exception {
    manager = ServiceManager.load();
    if (manager == null) {
      manager = new ServiceManager();
    }
    save();
  }

  public static String txname = "serviceManager";
  
  /**
   * Builds object from persistent image.
   *
   * @return	loaded object or null if no persistent image exists
   *
   * @exception IOException
   *	when accessing the stored image
   * @exception ClassNotFoundException
   *	if the corresponding image class may not be found
   */
  static ServiceManager load() throws IOException, ClassNotFoundException {
    return (ServiceManager) AgentServer.getTransaction().load(txname);
  }

  /**
   * Saves object in persistent storage.
   * @throws IOException an error occurs.
   */
  static void save() throws IOException {
    AgentServer.getTransaction().save(manager, txname);
    // with OSGi the service start asynchronously, 
    // and perhaps without running transaction.
    AgentServer.getTransaction().begin();
    AgentServer.getTransaction().commit(true);
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " service manager saved.");
  }

  /** repository holding <code>Service</code>s */
  Hashtable<String, ServiceDesc> registry;

  /**
   * Default constructor.
   */
  private ServiceManager() {
    registry = new Hashtable<>();
  }
  
  /**
   * Loads the class of every service.
   * @throws Exception if an error occurs
   */
  public static void loadServiceClasses() throws Exception {
    for (Enumeration<ServiceDesc> e = manager.registry.elements(); e.hasMoreElements() ;) {
      ServiceDesc desc = e.nextElement();
      try {
        loadServiceClass(desc);
      } catch (Exception exc) {
        if (DEBUG)
          logger.log(BasicLevel.ERROR, getName() + ", cannot load service " + desc.getClassName(), exc);
        else
          logger.log(BasicLevel.ERROR, getName() + ", cannot load service " + desc.getClassName() + ": " + exc.getMessage());
      }
    }
  }
  
  /**
   * Loads the class of a service.
   * @param desc the service descriptor
   * @throws Exception if an error occurs
   */
  public static void loadServiceClass(ServiceDesc desc) throws Exception {
    Class.forName(desc.getClassName());
  }

  /**
   * Start a <code>Service</code> defined by its descriptor.
   *
   * @param desc	service descriptor.
   * @throws Exception if an error occurs
   */
  public static void start(ServiceDesc desc) throws Exception {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " start service: " + desc);

    if (desc.running)
      throw new Exception("Service already running");
    Class ptypes[] = new Class[] { String.class, Boolean.TYPE };
    Object args[] = new Object[] { desc.getArguments(), Boolean.valueOf(!desc.isInitialized()) };
    
    Class service = null;
    service = Class.forName(desc.getClassName());
    Method init = service.getMethod("init", ptypes);

    try {
      init.invoke(null, args);
    } catch (InvocationTargetException exc) {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, getName() + ", cannot start service:", exc.getCause());
      throw new Exception(exc.getCause().getMessage());
    }
    
    desc.running = true;
    desc.initialized = true;
    
    if (DEBUG) {
      logger.log(BasicLevel.DEBUG, getName() + " service started");
    }
    save();
  }

  /**
   * Start a <code>Service</code> identified by its name.
   *
   * @param scname	service class name.
   * @throws Exception if an error occurs
   */
  static void start(String scname) throws Exception {
    ServiceDesc desc = (ServiceDesc) manager.registry.get(scname);
    if (desc == null)
      throw new NoSuchElementException("Unknown service: " + scname);
    start(desc);
  }

  /**
   * Starts all defined services.
   * @param exitOnFailure if true exit on failure.
   * @throws Exception if an error occurs
   */
  static void start(boolean exitOnFailure) throws Exception {
    // Launch all services defined in A3CML file
    for (Enumeration<ServiceDesc> e = manager.registry.elements(); e.hasMoreElements() ;) {
      ServiceDesc desc = e.nextElement();
      try {
        start(desc);
      } catch (Exception exc) {
        if (DEBUG)
          logger.log(BasicLevel.ERROR, getName() + ", cannot start service:" + desc.getClassName(), exc);
        else
          logger.log(BasicLevel.ERROR, getName() + ", cannot start service " + desc.getClassName() + ": " + exc);
        if (exitOnFailure)
          throw new Exception("Cannot start service " + desc.getClassName());
      }
    }
  }

  /**
   * Stop a <code>Service</code> defined by its descriptor.
   *
   * @param desc	service descriptor.
   * @throws Exception if an error occurs
   */
  public static void stop(ServiceDesc desc) throws Exception {
    // DF: idempotency (could be done in AgentAdmin)
    if (! desc.running) return;
//       throw new Exception("Service already stopped");
    Class service = Class.forName(desc.getClassName());
    Method stop = service.getMethod("stopService", new Class[0]);
    stop.invoke(null, (Object[]) null);
    desc.running = false;

    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "Service " + desc.scname + " stopped.");
  }

  /**
   * Stop a <code>Service</code> identified by its name.
   *
   * @param scname	service class name.
   * @throws Exception if an error occurs
   */
  public static void stop(String scname) throws Exception {
    ServiceDesc desc = manager.registry.get(scname);
    if (desc == null)
      throw new NoSuchElementException("Unknown service: " + scname);
    stop(desc);
  }

  /**
   * Stops all running services.
   */
  static void stop() {
    if ((manager == null) || (manager.registry == null))
      return;

    for (Enumeration<ServiceDesc> e = manager.registry.elements(); e.hasMoreElements();) {
      ServiceDesc desc = e.nextElement();
      try {
        if (DEBUG)
          logger.log(BasicLevel.DEBUG, getName() + ", stops: " + desc);

        if (desc.running)
          stop(desc);

        if (DEBUG)
          logger.log(BasicLevel.DEBUG, getName() + ", service stopped");
      } catch (Throwable exc) {
        if (DEBUG)
          logger.log(BasicLevel.WARN, getName() + ", cannot stop service " + desc.getClassName(), exc);
        else
          logger.log(BasicLevel.WARN, getName() + ", cannot stop service " + desc.getClassName() + ": " + exc);
      }
    }
  }

  /**
   * Registers a new <code>Service</code> object.
   *
   * @param scname	service class name.
   * @param args	launching arguments.
   */
  public static void register(String scname, String args) {
  	synchronized (manager) {
  		ServiceDesc desc = (ServiceDesc) manager.registry.get(scname);
  		if (DEBUG)
  			logger.log(BasicLevel.DEBUG, getName() + ", register " + scname + " -> " + desc);
  		if (desc == null) {
  			desc =  new ServiceDesc(scname, args);
  			manager.registry.put(scname, desc);
  		} else {
  			desc.args = args;
  		}
  		try {
  			save();
  		} catch (IOException e) {
  			if (logger.isLoggable(BasicLevel.ERROR))
  				logger.log(BasicLevel.ERROR, getName() + ", register save service manager." + scname, e);
  		}
  	}
  }

  /**
   * Unregisters useless <code>Service</code>.
   *
   * @param scname	service class name.
   */
  static void unregister(String scname) {
  	if (DEBUG)
  		logger.log(BasicLevel.DEBUG, getName() + ", unregister " + scname);
  	synchronized (manager) {
  		manager.registry.remove(scname);
  		try {
  			save();
  		} catch (IOException e) {
  			if (logger.isLoggable(BasicLevel.ERROR))
  				logger.log(BasicLevel.ERROR, getName() + ", unregister save service manager." + scname, e);
  		}
  	}
  }
  
  public static ServiceDesc getService(String serviceClassName) {
    return (ServiceDesc) manager.registry.get(serviceClassName);
  }

  static ServiceDesc[] getServices() {
    ServiceDesc[] services = new ServiceDesc[manager.registry.size()];
    int i = 0;
    for (Enumeration<ServiceDesc> e = manager.registry.elements(); e.hasMoreElements();) {
      services[i++] = e.nextElement();
    }
    return services;
  }

  static Set<String> listServices() {
    return new HashSet<>(manager.registry.keySet());
  }

  /**
   * Provides a string image for this object.
   *
   * @return	a string image for this object
   */
  public String toString() {
    StringBuffer output = new StringBuffer();
    output.append('(');
    output.append(super.toString());
    output.append(",registry=").append(Strings.toString(registry));
    output.append(')');
    return output.toString();
  }
}
