/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 *
 */

package de.nece.ccrle.organizer;

import java.io.File;
import java.io.FilenameFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Logger;

import eu.gemss.GEMSS;
import eu.gemss.components.Configuration;
import eu.gemss.components.ComponentManager;
import eu.gemss.components.InvalidRequestException;
import eu.gemss.components.ServiceRequest;
import eu.gemss.components.Terminable;
import eu.gemss.components.providers.Provider;
import eu.gemss.signals.Signal;
import eu.gemss.signals.SignalHandler;
import eu.gemss.signals.ComponentUpdateSignal;

import de.nece.ccrle.gemss.ConfigurationImpl;
import de.nece.ccrle.util.Register;
import de.nece.ccrle.util.Services;

/**
 * An implementation of the <code>ComponentManager</code> interface.
 *
 * @author Greg Kohring
 */
public class ComponentOrganizer implements ComponentManager {

    private LoaderLDR loaderRepository = null;
    private ComponentRegistry components = null;
    private Transmitter transmitter = null;
    private Map activeComponents = null;
    private SessionLDR sessionRepository = null;

    private static final String ConfigurationFQN =
                                    "eu.gemss.components.Configuration";

    private boolean notStarted = true;

    private static Logger logger =
                       Logger.getLogger( ComponentOrganizer.class.getName() );
                                                                                
    /** @link dependency */
    /*# Services lnkServices; */

    /**
    * The default constructor.
    *
    */
    public ComponentOrganizer() throws IOException {

        // Create the component registry

        components = new ComponentRegistry ();


        // Locate all default components

        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    ClassLoader thisCL = this.getClass().getClassLoader();

                    components.addAllCategories( thisCL );

                    components.registerAllComponentProviders( thisCL  );
                    return null;
                }
            }
        );

        // Find the Configuration store

        Configuration configuration = null;

        try{
            configuration = (Configuration) getInstance( ConfigurationFQN );
        } catch ( Exception e ){
            logger.severe( e.getMessage() );
            throw new RuntimeException( e.getMessage() );
        }

        if ( configuration == null ){
            logger.severe( "Failed to instantiate Configuration component!" );
            throw new RuntimeException( 
                        "Failed to instantiate Configuration component!" );
        }

        // Find the component repository and make a list of all jar files

        String componentPath = configuration.getComponentStore();

        // Make sure the component path is not in the class path

        checkClassPath( componentPath );

        final String fComponentPath = componentPath;

        //AccessController.doPrivileged(new PrivilegedAction() {
         //   public Object run() {
                try {
                    loaderRepository = new LoaderLDR( fComponentPath, true );
                } catch ( FileNotFoundException fnfe ){
                    logger.severe( fnfe.getMessage() );
                    throw new RuntimeException( fnfe.getMessage() );
                }
                Collection classLoaders = loaderRepository.getClassLoaders();
                // Add the services and providers from each jar file to 
                // the service registry.

                if ( !classLoaders.isEmpty() ){
                    for ( Iterator clit = classLoaders.iterator(); 
                                clit.hasNext(); ){
                        ClassLoader loader = (ClassLoader) clit.next();
                        components.addAllCategories( loader );
                        components.registerAllComponentProviders( loader );
                    }
                }
         //       return null;
          //  }
        //});


        // Initialize the store of active components.

        activeComponents = new HashMap();

        // Locate and initialize the session repository;

        String dirName = configuration.getSessionStore();

        if ( dirName == null ){
            logger.severe( "SessionStore string not initialized!" );
            throw new RuntimeException("SessionStore string not initialized!");
        }

        final File fDir = new File( dirName );

        try {
            AccessController.doPrivileged(
                new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                        sessionRepository = new SessionLDR( fDir );
                        return null;
                    }
                }
            );
        } catch ( PrivilegedActionException pae ) {
            logger.severe( pae.getException().getMessage() );
            throw new RuntimeException( pae.getException().getMessage() );
        }

        sessionRepository.useLoaders( loaderRepository );

        // Make the configuration component visible for everyone.

        addInstance( ConfigurationFQN, configuration );


        // Initialze the signal framework.

        transmitter = new Transmitter();

        // Monitor the repository for any changes.

        final long fPeriod = configuration.getRepositoryUpdateInterval();
        //loaderRepository.addListener( new RepositoryMonitor() );
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    loaderRepository.setPeriod( fPeriod*1000 );
                    loaderRepository.addListener( new RepositoryMonitor() );
                    return null;
                }
            }
        );

    }

    // Check the claass path for the presence of the specified path 
    // component.
    private void checkClassPath( String path ) throws IOException {

        if ( path == null  || path.equals( "" )     
                                || path.equals( File.separator ) ) {
            return;
        }

        String lPath = path;
        if ( lPath.endsWith( File.separator ) ){
            lPath = path.substring( 0, path.length() - 1 );
        }

        String componentDir = lPath.substring( 
                                lPath.lastIndexOf( File.separatorChar ) + 1 );

        String classPath = (String ) AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    return System.getProperty( "java.class.path" );
                }
            }
        );

        if ( classPath.lastIndexOf( componentDir ) > 0 ){
            throw new IOException( "Invalid class path. " +
                                "Contains component directory." );
        }
    }


    public synchronized void startDaemons(){

        if ( notStarted ){

            notStarted = false;

            // Start any daemon components

            Configuration configuration =
                           (Configuration) getInstance( ConfigurationFQN );

            String[] initThese = configuration.getDaemons();

            for ( int i  = 0; i < initThese.length; i++ ){
                String daemonComponent =  initThese[i];
                Object obj = null;
                obj = getInstance( daemonComponent );
                if ( obj != null ){
                    if ( obj instanceof Runnable ){
                        Runnable runnable = (Runnable) obj;
                        Thread daemonThread = new Thread( runnable );
                        daemonThread.setDaemon( true );
                        daemonThread.start();
                        addInstance( daemonComponent, obj );
                    } else {
                        logger.warning( "Not runnable: " + daemonComponent );
                    }
                } else {
                    logger.warning( "No such daemon: " + daemonComponent );
                }
            }
        }

    }



    // A class for monitoring changes to the repository
    class RepositoryMonitor implements RepositoryChangeListener {

        public synchronized void changed( List changed ){
            process( changed );
        }
        public synchronized void added( List added ){
            process( added );
        }

        private synchronized void process( List loaders ){

    // Create a list of changed services from the changed class loaders

            Set newServices = new HashSet();

            for ( Iterator lit = loaders.iterator(); lit.hasNext(); ){
                ClassLoader loader = (ClassLoader) lit.next();
                Collection services = Services.findServices( loader );
                newServices.addAll( services );
            }

    // Subtract off any services in the application classpath since these
    // cannot be changed at run time.

            Collection fixedServices = Services.findServices();

            newServices.removeAll( fixedServices );

            components.addCategories( newServices );

    // Register the new providers using the new service categories

            for ( Iterator lit = loaders.iterator(); lit.hasNext();){
                ClassLoader loader = (ClassLoader) lit.next();
                components.registerAllComponentProviders( loader );
            }

    // If any of the new components are global components, then
    // we need to exchange them in the registry of active components.

            Configuration configuration = 
                            (Configuration) getInstance( ConfigurationFQN );

            String[] daemonComponents = configuration.getDaemons();

            for ( int i  = 0; i < daemonComponents.length; i++ ){
                String daemon = daemonComponents[i];
                if ( newServices.contains( daemon ) ){
                    Object obj = null;
                    obj = getInstance( daemon );
                    if ( obj instanceof Runnable ){
                        remove( daemon );
                        Runnable runnable = (Runnable) obj;
                        Thread daemonThread = new Thread( runnable );
                        daemonThread.setDaemon( true );
                        daemonThread.start();
                        addInstance( daemon, obj );
                    } else {
                        logger.warning( "Updated component not runnable: "
                                            + daemon );
                    }
                }
            }

    // Generate a component update signal for each of the new components.

            for ( Iterator sit = newServices.iterator(); sit.hasNext(); ){
                ComponentUpdateSignal cus = new ComponentUpdateSignal( 
                        ComponentOrganizer.this, (String) sit.next() );
                generateSignal( cus );
            }

        }

        public synchronized void deleted( List loaders ){
            Collection providers = components.items();
            for ( Iterator lit = loaders.iterator(); lit.hasNext(); ){
                ClassLoader deletedLoader = (ClassLoader) lit.next();
                for ( Iterator pit = providers.iterator(); pit.hasNext(); ){
                    Class providerClass = pit.next().getClass();
                    ClassLoader providerLoader = 
                                            providerClass.getClassLoader();
                    if ( providerLoader.equals( deletedLoader ) ) {
                        components.deregisterComponentProviderbyName(
                                        providerClass.getName() );
                    }
                }
            }


        }


    }

    public Set getProviders( String component ){
        Set providers = components.getItems( component );
        if ( providers == null ){
            return Collections.unmodifiableSet( new HashSet() );
        } else {
            return Collections.unmodifiableSet( providers );
        }
    }

    public Set getComponents(){
        return Collections.unmodifiableSet( components.categories() );
    }

    public Object getInstance( String component ) {
        Object obj = null;
        Iterator it = components.getComponentProviders( component );
        if ( it.hasNext() ){
            Provider provider = (Provider) it.next();
            try {
                obj = provider.createComponent();
            } catch ( IOException ioe ){
                logger.info( "While creating component, " + component +  
                                   ": " + ioe.getMessage() );
                // on exception obj remains null
            }
        } else {
            logger.info( "Component: " + component + " not found." );
        }
        return obj;

    }

    public Object getInstance( String component, String providerName ) {
        try{
            return getInstance( 
                    new ServiceRequest( component, providerName, null ) );
        } catch (  InvalidRequestException ire ){
            //This happens when either of the input parameters are null...
            logger.warning( "While creating component:  " + ire.getMessage() );
            return null;
        }
    }

    public Object getInstance( ServiceRequest sr ) 
            throws InvalidRequestException {

        String component = sr.getComponent();
        String providerName = sr.getProviderName();
        String version = sr.getVersion();

        // If the provider name is an empty string, set the providerName
        // object to null.
        if ( providerName != null ){
            if ( providerName.trim().equals( "" ) ){
                providerName = null;
            }
        }

        if ( component == null ){
            throw new  InvalidRequestException(
                            "Component name has not been set.", 
                            "component == null!" );
        }

        Object obj = null;

        Iterator it = components.getComponentProviders( component );

        while ( it.hasNext() ) {
            Provider provider = (Provider) it.next();
            if ( providerName != null ){
                if ( !providerName.equals( provider.getProviderName() ) ){
                    continue;
                }
            }

            if ( version != null ){
                if ( !version.equals( provider.getVersion() ) ) continue;
            }

            try {
                obj = provider.createComponent( sr );
                break;
            } catch ( IOException ioe ){
                logger.info( "While creating component: " + ioe.getMessage() );
                // on exception obj remains null
            }
        }

        return obj;
    }

    public synchronized Set getManagedInstanceIDs(){
        return Collections.unmodifiableSet( activeComponents.keySet() );
    }

    public Set getManagedInstances( String className ) {
        Class desiredClass = null;
        try {
            desiredClass = Class.forName( className );
        } catch ( ClassNotFoundException cnfe ){
            // The desired class is not found by the default class loader
            logger.info( "Class not found: " + cnfe.getMessage() );
        }
        Set instances = new HashSet();
        for ( Iterator ii = activeComponents.values().iterator();
                        ii.hasNext(); ){
            Object obj = ii.next();
            Class objClass = obj.getClass();
            if ( desiredClass == null ){
                try{
                    desiredClass = Class.forName( className, true,
                                                objClass.getClassLoader() );
                } catch ( ClassNotFoundException cnfe ){
                    // This object does not know about the desired class,
                    // so it cannot be assignable from it.
                    logger.info( "Class not found: " + cnfe.getMessage() );
                    continue;
                }
            }
            if ( desiredClass.isAssignableFrom( objClass ) ){
                instances.add( obj );
            }
        }
        return instances;
    }

    public synchronized boolean manageInstance( String id, Object instance ){
        return addInstance( id, instance );
    }

   // Adds an instance without limitations on the name.
    synchronized boolean addInstance( String id, Object instance ){

        if ( activeComponents.containsKey( id ) ){
            return false;
        } else {
            activeComponents.put( id, instance );
            return true;
        }
    }

    public synchronized Object getManagedInstance( String id ){
        return activeComponents.get( id );
    }

    public boolean terminate( Terminable t ) {
        if ( activeComponents.containsValue( t ) ) {
            Set entries = activeComponents.entrySet();
            for( Iterator entryIt=entries.iterator(); entryIt.hasNext(); ){
                Map.Entry entry = (Map.Entry) entryIt.next();
                if ( t.equals( entry.getValue() ) ){
                    entryIt.remove();
                }
            }
        }
        t.terminate();
        return true;
    }

    public synchronized boolean terminate( String id ){
        return remove( id );
    }

   // Removes the instance from the list of active components and calls
   // terminate if the instance is terminable
    synchronized boolean remove(String id){

        Object obj = activeComponents.remove( id );

        if ( obj == null ){
            return false;
        } else {
            if ( obj instanceof Terminable ){
                Terminable term = (Terminable) obj;
                term.terminate();
            }
            return true;
        }
    }

    public synchronized void registerSignalHandler( SignalHandler sh, 
                                        String signalName ){
        transmitter.registerSignalHandler( sh, signalName );
    }

    public synchronized Signal[] generateSignal( Signal signal ){
        return transmitter.generateSignal( signal );
    }

    public String[] sessions() throws IOException  {
        String allSessions[] = null;
        try{
            allSessions = (String []) AccessController.doPrivileged(
            new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                        return sessionRepository.sessions();
                    }
                }
            );
        } catch ( PrivilegedActionException pae ){
            throw (IOException) pae.getException();
        }
        return allSessions;
    }

    public void saveSession( final String sessionID ) throws IOException  {

        try{
            AccessController.doPrivileged(
            new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                    Session session = 
                            sessionRepository.createSession( sessionID );
                        session.clear();
                        session.putAll( activeComponents );
                        sessionRepository.addSession( sessionID, session );
                        return null;
                    }
                }
            );
        } catch ( PrivilegedActionException pae ){
            throw (IOException) pae.getException();
        }

    }

    public void loadSession( final String sessionID ) throws IOException  {
        Session session = null;
        try{
            session = (Session) AccessController.doPrivileged(
            new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                        return sessionRepository.getSession( sessionID );
                    }
                }
            );
        } catch ( PrivilegedActionException pae ){
            throw (IOException) pae.getException();
        }

        for ( Iterator is = session.entrySet().iterator(); is.hasNext(); ) {
            Map.Entry component = (Map.Entry) is.next();
            String name = (String) component.getKey();
            Object obj = getInstance( name );
            if ( obj != null ) {
                remove( name );
            }
            addInstance( name, component.getValue() );
        }
    }

    public boolean deleteSession( final String sessionID ) throws IOException {
        Boolean info = new Boolean( false );
        try{
            info = (Boolean) AccessController.doPrivileged(
            new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                        return new Boolean( sessionRepository.remove( 
                                                            sessionID ) );
                    }
                }
            );
        } catch ( PrivilegedActionException pae ){
            throw (IOException) pae.getException();
        }

        return info.booleanValue();
    }

}
