/*
 * Copyright 2005-2006 The Apache Software Foundation.
 *
 * Licensed 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.sca.tuscany;

import java.util.List;

import org.apache.tuscany.common.monitor.MonitorFactory;
import org.apache.tuscany.common.monitor.impl.NullMonitorFactory;
import org.apache.tuscany.core.builder.ContextFactoryBuilder;
import org.apache.tuscany.core.builder.impl.DefaultWireBuilder;
import org.apache.tuscany.core.config.ConfigurationException;
import org.apache.tuscany.core.config.ModuleComponentConfigurationLoader;
import org.apache.tuscany.core.context.AggregateContext;
import org.apache.tuscany.core.context.CoreRuntimeException;
import org.apache.tuscany.core.context.EventContext;
import org.apache.tuscany.core.context.SystemAggregateContext;
import org.apache.tuscany.core.runtime.RuntimeContext;
import org.apache.tuscany.core.runtime.RuntimeContextImpl;
import org.apache.tuscany.model.assembly.AssemblyModelContext;
import org.apache.tuscany.model.assembly.ModuleComponent;
import org.apache.tuscany.model.scdl.loader.SCDLModelLoader;
import org.osoa.sca.ModuleContext;
import org.osoa.sca.SCA;
import org.osoa.sca.ServiceRuntimeException;

public class TuscanyRuntime extends SCA {
    private final TuscanyRuntime.Monitor monitor;
    private final Object sessionKey = new Object();

    private final RuntimeContext runtime;
    private final AggregateContext moduleContext;
    
    private final ModuleComponent moduleComponent;

    private static final String SYSTEM_MODULE_COMPONENT = "org.apache.tuscany.core.system";

    /**
     * Construct a runtime using a null MonitorFactory.
     *
     * @param name the name of the module component
     * @param uri  the URI to assign to the module component
     * @throws ConfigurationException if there was a problem loading the SCA configuration
     * @see TuscanyRuntime#TuscanyRuntime(String, String, org.apache.tuscany.common.monitor.MonitorFactory)
     */
    public TuscanyRuntime(String name, String uri) throws ConfigurationException {
        this(name, uri, 
             Thread.currentThread().getContextClassLoader(), 
             new NullMonitorFactory());
    }

    /**
     * Construct a runtime containing a single module component with the
     * specified name. The module definition is loaded from a "/sca.module"
     * resource found on the classpath of the current Thread context classloader.
     *
     * @param name           the name of the module component
     * @param uri            the URI to assign to the module component
     * @param classLoader    the class loader to use for the assembly
     * @param monitorFactory the MonitorFactory for this runtime
     * @throws ConfigurationException if there was a problem loading the SCA configuration
     */
    public TuscanyRuntime(String name, String uri, ClassLoader classLoader, MonitorFactory monitorFactory) throws ConfigurationException {
        this.monitor = monitorFactory.getMonitor(TuscanyRuntime.Monitor.class);

        // Create an assembly model context
        AssemblyModelContext modelContext = BootstrapHelper.getModelContext(classLoader);

        // Create a runtime context and start it
        List<SCDLModelLoader> loaders = modelContext.getAssemblyLoader().getLoaders();
        List<ContextFactoryBuilder> configBuilders = BootstrapHelper.getBuilders();
        runtime = new RuntimeContextImpl(monitorFactory, loaders, configBuilders, new DefaultWireBuilder());
        runtime.start();
        monitor.started(runtime);

        // Load and start the system configuration
        SystemAggregateContext systemContext = runtime.getSystemContext();
        ModuleComponentConfigurationLoader loader = BootstrapHelper.getConfigurationLoader(systemContext, modelContext);
        ModuleComponent systemModuleComponent = loader.loadSystemModuleComponent(SYSTEM_MODULE_COMPONENT, SYSTEM_MODULE_COMPONENT);
        AggregateContext context = BootstrapHelper.registerModule(systemContext, systemModuleComponent);
        context.fireEvent(EventContext.MODULE_START, null);

        // Load the SCDL configuration of the application module
        AggregateContext rootContext = runtime.getRootContext();
        moduleComponent = loader.loadModuleComponent(name, uri);
        moduleContext = BootstrapHelper.registerModule(rootContext, moduleComponent);
    }

    public ModuleComponent getModuleComponent() {
        return moduleComponent;
    }
    
    public AggregateContext getModuleContext() {
        return moduleContext;
    }
    
    /**
     * Start the runtime and associate the module context with the calling thread.
     */
    @Override
    public void start() {
        setModuleContext((ModuleContext) moduleContext);
        try {
            //moduleContext.start();
            moduleContext.fireEvent(EventContext.MODULE_START, null);
            moduleContext.fireEvent(EventContext.REQUEST_START, null);
            moduleContext.fireEvent(EventContext.SESSION_NOTIFY, sessionKey);
            monitor.started(moduleContext);
        } catch (CoreRuntimeException e) {
            setModuleContext(null);
            monitor.startFailed(moduleContext, e);
            //FIXME throw a better exception
            throw new ServiceRuntimeException(e);
        }
    }

    /**
     * Disassociate the module context from the current thread and shut down the runtime.
     */
    @Override
    public void stop() {
        setModuleContext(null);
        moduleContext.fireEvent(EventContext.REQUEST_END, null);
        moduleContext.fireEvent(EventContext.SESSION_END, sessionKey);
        moduleContext.fireEvent(EventContext.MODULE_STOP, null);
        moduleContext.stop();
        monitor.stopped(moduleContext);
        runtime.stop();
        monitor.stopped(runtime);
    }

    /**
     * Monitor interface for a TuscanyRuntime.
     */
    public static interface Monitor {
        /**
         * Event emitted after the runtime has been started.
         *
         * @param ctx the runtime's module component context
         */
        void started(AggregateContext ctx);

        /**
         * Event emitted when an attempt to start the runtime failed.
         *
         * @param ctx the runtime's module component context
         * @param e   the exception that caused the failure
         */
        void startFailed(AggregateContext ctx, CoreRuntimeException e);

        /**
         * Event emitted after the runtime has been stopped.
         *
         * @param ctx the runtime's module component context
         */
        void stopped(AggregateContext ctx);
    }

}