/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 */
package org.apache.avalon.phoenix.components.embeddor;

import java.io.File;
import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.excalibur.io.ExtensionFileFilter;
import org.apache.avalon.framework.CascadingException;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.DefaultServiceManager;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.phoenix.Constants;
import org.apache.avalon.phoenix.interfaces.Deployer;
import org.apache.avalon.phoenix.interfaces.Embeddor;
import org.apache.avalon.phoenix.interfaces.EmbeddorMBean;
import org.apache.avalon.phoenix.interfaces.Kernel;

/**
 * This is the top level container that is interacted with to create, manage and
 * dispose of the kernel and related resources represented 
 * as <code> EmbeddorEntry</code>. It is made <code>abstract</code>
 * in order to delegate deployment of default applications and the main loop
 * to concreate <code>Embeddor</code> implementation. 
 *
 * @author <a href="mail@leosimons.com">Leo Simons</a>
 * @author <a href="peter@apache.org">Peter Donald</a>
 * @author <a href="bauer@denic.de">Joerg Bauer</a>
 */
public abstract class AbstractEmbeddor  extends AbstractLogEnabled
                                        implements Embeddor, Contextualizable,
                                      Parameterizable, Configurable, Initializable, Disposable
{
    protected final Resources REZ = getResources();
    
    private Parameters m_parameters;
    /**
     * Context passed to embeddor. See the contextualize() method
     * for details on what is stored in context.
     *
     * @see DefaultEmbeddor#contextualize(Context)
     */
    private Context m_context;

    private EmbeddorEntry[] m_entrys;

    /**
     * Flag is set to true when the embeddor should  shut itself down.
     * It is set to true as a result of a call to shutdown() method.
     *
     * @see Embeddor#shutdown()
     */
    private boolean m_shutdown;

    /**
     * Time at which the embeddor was started.
     */
    private long m_startTime;
    
    /**
     * Provides Resource with the following mandatory messages:
     *  <ul>
     * <li><b>embeddor.notice.no-restart</b> </li>
     * <li><b>embeddor.error.start.failed</b> </li>
     * <li><b>embeddor.error.shutdown.failed</b> </li>
     * <li><b>bad-type.error</b> </li>
     * <li><b>bad-ctor.error</b> </li>
     * <li><b>no-instantiate.error</b> </li>
     * <li><b>no-class.error</b> </li>     
     * </ul>
     */
    protected abstract Resources getResources();
    /**
     * Being called before starting main loop and after start-up of all entries
     */
    protected abstract void defaultDeployment() throws Exception;
    /**
     * This is a main loop of the application. It is supposed to test <code>wasShutdown()</code>
     */
    protected abstract void mainLoop();
    /**
     * Provides a way to modify parameters to be passed into child component of the embeddor.
     */
    protected abstract Parameters createChildParameters();

    public void contextualize( final Context context )
        throws ContextException
    {
        m_context = context;
    }

    public synchronized void parameterize( final Parameters parameters )
        throws ParameterException
    {
        m_parameters = parameters;
    }

    public void configure( final Configuration configuration )
        throws ConfigurationException
    {
        final Configuration[] children = configuration.getChildren( "component" );
        m_entrys = new EmbeddorEntry[ children.length ];
        for( int i = 0; i < children.length; i++ )
        {
            final String role = children[ i ].getAttribute( "role" );
            final String classname = children[ i ].getAttribute( "class" );
            final String logger = children[ i ].getAttribute( "logger" );
            m_entrys[ i ] =
                new EmbeddorEntry( role, classname, logger, children[ i ] );
        }
    }

    /**
     * Creates the core handlers - logger, deployer, Manager and
     * Kernel. Note that these are not set up properly until you have
     * called the <code>run()</code> method.
     */
    public void initialize()
        throws Exception
    {
        m_startTime = System.currentTimeMillis();
        try
        {
            createComponents();
            setupComponents();
        }
        catch( final Exception e )
        {
            // whoops!
            final String message = REZ.getString( "embeddor.error.start.failed" );
            getLogger().fatalError( message, e );
            throw e;
        }
    }

    /**
     * This is the main method of the embeddor. It sets up the core
     * components, and then deploys the <code>Facilities</code>. These
     * are registered with the Kernel and the Manager. The same
     * happens for the <code>Applications</code>.
     * Now, the Kernel is taken through its lifecycle. When it is
     * finished, as well as all the applications running in it, it
     * is shut down, after which the PhoenixEmbeddor is as well.
     */
    public void execute()  throws Exception
    {
      defaultDeployment();
      mainLoop();
    }

    /**
     * Release all the resources associated with kernel.
     */
    public synchronized void dispose()
    {
        shutdown();
        try
        {
            shutdownComponents();
        }
        catch( final Exception e )
        {
            // whoops!
            final String message = REZ.getString( "embeddor.error.shutdown.failed" );
            getLogger().fatalError( message, e );
        }
        for( int i = 0; i < m_entrys.length; i++ )
        {
            m_entrys[ i ].setObject( null );
        }
        System.gc(); // make sure resources are released
    }

    /**
     * Request the Embeddor shutsdown.
     */
    public void shutdown()
    {
        m_shutdown = true;
        synchronized( this )
        {
            notifyAll();
        }
    }

    //////////////////////
    /// HELPER METHODS ///
    //////////////////////
    /**
     * Create the logger, deployer and kernel components.
     * Note that these components are not ready to be used
     * until setupComponents() is called.
     */
    private synchronized void createComponents()
    {
        try
        {
            for( int i = 0; i < m_entrys.length; i++ )
            {
                final String className = m_entrys[ i ].getClassName();
                final Class clazz = Class.forName( className );
                final Object object = createObject( className, clazz );
                m_entrys[ i ].setObject( object );
            }
        }
        catch( Exception e )
        {
            final String message =
                REZ.getString( "embeddor.error.createComponents.failed" );
            getLogger().fatalError( message, e );
        }
    }

    /**
     * The deployer is used to load the applications from the
     * default-apps-location specified in Parameters.
     * TODO: load facilities from .fars as well.
     *
     * @throws Exception if an error occurs
     */

    private void setupComponents()
        throws Exception
    {
        for( int i = 0; i < m_entrys.length; i++ )
        {
            final EmbeddorEntry entry = m_entrys[ i ];
            setupComponent( entry.getObject(),
                            entry.getLoggerName(),
                            entry.getConfiguration() );
        }
    }

    /**
     * Setup a component and run it through al of it's
     * setup lifecycle stages.
     *
     * @param object the component
     * @throws Exception if an error occurs
     */
    private void setupComponent( final Object object,
                                 final String loggerName,
                                 final Configuration config )
        throws Exception
    {
        final Logger childLogger = getLogger().getChildLogger( loggerName );
        ContainerUtil.enableLogging( object, childLogger );
        ContainerUtil.contextualize( object, m_context );
        ContainerUtil.service( object, getServiceManager() );
        ContainerUtil.parameterize( object, createChildParameters() );
        ContainerUtil.configure( object, config );
        ContainerUtil.initialize( object );
        ContainerUtil.start( object );
    }

    

    private void shutdownComponents()
        throws Exception
    {
        //for( int i = m_entrys.length - 1; i >= 0; i-- )
        for( int i = 0; i < m_entrys.length; i++ )
        {
            final Object object = m_entrys[ i ].getObject();
            if( null == object )
            {
                continue;
            }
            ContainerUtil.shutdown( object );
        }
    }

    /**
     * Create a component that implements an interface.
     *
     * @param classname the name of the objects class
     * @param service the name of interface/type
     * @return the created object
     * @throws Exception if an error occurs
     */
    private Object createObject( final String classname,
                                 final Class service )
        throws Exception
    {
        try
        {
            final Object object = Class.forName( classname ).newInstance();
            if( !service.isInstance( object ) )
            {
                final String message =
                    REZ.getString( "bad-type.error",
                                   classname,
                                   service.getName() );
                throw new Exception( message );
            }
            return object;
        }
        catch( final IllegalAccessException iae )
        {
            final String message = REZ.getString( "bad-ctor.error", service.getName(), classname );
            throw new CascadingException( message, iae );
        }
        catch( final InstantiationException ie )
        {
            final String message =
                REZ.getString( "no-instantiate.error",
                               service.getName(),
                               classname );
            throw new CascadingException( message, ie );
        }
        catch( final ClassNotFoundException cnfe )
        {
            final String message = REZ.getString( "no-class.error", service.getName(), classname );
            throw new CascadingException( message, cnfe );
        }
    }

    private ServiceManager getServiceManager()
    {
        final DefaultServiceManager serviceManager = new DefaultServiceManager();
        serviceManager.put( Embeddor.ROLE, this );
        for( int i = 0; i < m_entrys.length; i++ )
        {
            final String role = m_entrys[ i ].getRole();
            final Object component = getEmbeddorComponent( role );
            serviceManager.put( role, component );
        }
        return serviceManager;
    }



    /**
     * Allow subclasses to get access to parameters.
     *
     * @return the Parameters
     */
    protected final Parameters getParameters()
    {
        return m_parameters;
    }

    protected final Context getContext()
    {
        return m_context; 
    }
    protected final long getStartupTime()
    {
      return m_startTime; 
    }

    protected final boolean wasShutdown()
    {
      return m_shutdown; 
    }
    
    
    protected Object getEmbeddorComponent( final String role )
    {
        for( int i = 0; i < m_entrys.length; i++ )
        {
            final EmbeddorEntry entry = m_entrys[ i ];
            if( entry.getRole().equals( role ) )
            {
                return m_entrys[ i ].getObject();
            }
        }
        // Should never happen
        // TODO: create error / warning
        return null;
    }
}

