/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.commons.scxml.invoke;

import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.scxml.Context;
import org.apache.commons.scxml.Evaluator;
import org.apache.commons.scxml.SCInstance;
import org.apache.commons.scxml.SCXMLExecutor;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.env.SimpleDispatcher;
import org.apache.commons.scxml.env.SimpleErrorHandler;
import org.apache.commons.scxml.env.SimpleErrorReporter;
import org.apache.commons.scxml.env.SimpleSCXMLListener;
import org.apache.commons.scxml.io.SCXMLParser;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.SCXML;
import org.xml.sax.SAXException;

/**
 * A simple {@link Invoker} for SCXML documents. Invoked SCXML document
 * may not contain external namespace elements, further invokes etc.
 */
public class SimpleSCXMLInvoker implements Invoker, Serializable {

    /** Serial version UID. */
    private static final long serialVersionUID = 1L;
    /** Parent state ID. */
    private String parentStateId;
    /** Event prefix, all events sent to the parent executor must begin
     *  with this prefix. */
    private String eventPrefix;
    /** Invoking document's SCInstance. */
    private SCInstance parentSCInstance;
    /** The invoked state machine executor. */
    private SCXMLExecutor executor;
    /** Cancellation status. */
    private boolean cancelled;

    //// Constants
    /** Prefix for all events sent to the parent state machine. */
    private static String invokePrefix = ".invoke.";
    /** Suffix for invoke done event. */
    private static String invokeDone = "done";
    /** Suffix for invoke cancel response event. */
    private static String invokeCancelResponse = "cancel.response";

    /**
     * {@inheritDoc}.
     */
    public void setParentStateId(final String parentStateId) {
        this.parentStateId = parentStateId;
        this.eventPrefix = this.parentStateId + invokePrefix;
        this.cancelled = false;
    }

    /**
     * {@inheritDoc}.
     */
    public void setSCInstance(final SCInstance scInstance) {
        this.parentSCInstance = scInstance;
    }

    /**
     * {@inheritDoc}.
     */
    public void invoke(final String source, final Map params)
    throws InvokerException {
        SCXML scxml = null;
        try {
            scxml = SCXMLParser.parse(new URL(source),
                new SimpleErrorHandler());
        } catch (ModelException me) {
            throw new InvokerException(me.getMessage(), me.getCause());
        } catch (IOException ioe) {
            throw new InvokerException(ioe.getMessage(), ioe.getCause());
        } catch (SAXException se) {
            throw new InvokerException(se.getMessage(), se.getCause());
        }
        Evaluator eval = parentSCInstance.getEvaluator();
        executor = new SCXMLExecutor(eval,
            new SimpleDispatcher(), new SimpleErrorReporter());
        Context rootCtx = eval.newContext(null);
        for (Iterator iter = params.entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            rootCtx.setLocal((String) entry.getKey(), entry.getValue());
        }
        executor.setRootContext(rootCtx);
        executor.setStateMachine(scxml);
        executor.addListener(scxml, new SimpleSCXMLListener());
        executor.registerInvokerClass("scxml", this.getClass());
        try {
            executor.go();
        } catch (ModelException me) {
            throw new InvokerException(me.getMessage(), me.getCause());
        }
        if (executor.getCurrentStatus().isFinal()) {
            TriggerEvent te = new TriggerEvent(eventPrefix + invokeDone,
                TriggerEvent.SIGNAL_EVENT);
            new AsyncTrigger(parentSCInstance.getExecutor(), te).start();
        }
    }

    /**
     * {@inheritDoc}.
     */
    public void parentEvents(final TriggerEvent[] evts)
    throws InvokerException {
        if (cancelled) {
            return; // no further processing should take place
        }
        boolean doneBefore = executor.getCurrentStatus().isFinal();
        try {
            executor.triggerEvents(evts);
        } catch (ModelException me) {
            throw new InvokerException(me.getMessage(), me.getCause());
        }
        if (!doneBefore && executor.getCurrentStatus().isFinal()) {
            TriggerEvent te = new TriggerEvent(eventPrefix + invokeDone,
                TriggerEvent.SIGNAL_EVENT);
            new AsyncTrigger(parentSCInstance.getExecutor(), te).start();
        }
    }

    /**
     * {@inheritDoc}.
     */
    public void cancel()
    throws InvokerException {
        cancelled = true;
        TriggerEvent te = new TriggerEvent(eventPrefix
            + invokeCancelResponse, TriggerEvent.SIGNAL_EVENT);
        new AsyncTrigger(parentSCInstance.getExecutor(), te).start();
    }

}

