package de.renew.fa.figures;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import CH.ifa.draw.standard.AbstractFigure;
import CH.ifa.draw.standard.NullHandle;
import CH.ifa.draw.standard.RelativeLocator;
import CH.ifa.draw.util.AWTSynchronizedUpdate;
import de.renew.draw.storables.api.StorableApi;
import de.renew.draw.storables.ontology.DrawingChangeEvent;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.ui.ontology.DrawingView;
import de.renew.draw.ui.ontology.FigureHandle;
import de.renew.engine.thread.SimulationThreadPool;
import de.renew.gui.CPNSimulation;
import de.renew.gui.InstanceDrawing;
import de.renew.gui.InstanceFigure;
import de.renew.gui.ModeReplacement;
import de.renew.remote.EventListener;
import de.renew.remote.ObjectAccessor;
import de.renew.remote.RemoteEventForwarder;
import de.renew.remote.TransitionInstanceAccessor;


public class FAArcInstanceConnection extends AbstractFigure
    implements InstanceFigure, EventListener
{
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(FAArcInstanceConnection.class);
    private FAArcConnection _faArcConnection;
    private InstanceDrawing _drawing;
    private TransitionInstanceAccessor _transitionInstance;
    private boolean _afterglow = false;
    private Thread _afterglowThread;
    private AWTSynchronizedUpdate _updateTask;
    private RemoteEventForwarder _forwarder;

    public FAArcInstanceConnection(
        InstanceDrawing drawing, FAArcConnection fac,
        Hashtable<Serializable, ObjectAccessor> netElements)
    {
        this._faArcConnection = fac;
        this._drawing = drawing;
        initialize(netElements);
        this._updateTask = new AWTSynchronizedUpdate(new Runnable() {
            @Override
            public void run() {
                executeUpdate();
            }
        });
    }

    protected void initialize(Hashtable<Serializable, ObjectAccessor> netElements) {
        if (netElements.size() != 1) {
            LOGGER.error("Error: There are not excactly three net elements for the figure " + this);
            return;
        }


        // find the TransitionInstance of the FAArcs' net elements
        Enumeration<ObjectAccessor> faArcNetElements = netElements.elements();
        while (faArcNetElements.hasMoreElements()) {
            ObjectAccessor netElementAccessor = faArcNetElements.nextElement();
            if (netElementAccessor instanceof TransitionInstanceAccessor) {
                _transitionInstance = (TransitionInstanceAccessor) netElementAccessor;
            }
        }

        // What is this good for?
        try {
            _forwarder = new RemoteEventForwarder(this);
            _transitionInstance.addRemoteEventListener(_forwarder);
        } catch (RemoteException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    //------------------------------ Firing ------------------------------------   
    /**
     * Returns whether the figure shall display firing state.
     * @return Whether the figure is firing.
     */
    public boolean isChangingState() {
        try {
            return _transitionInstance.isFiring();
        } catch (RemoteException e) {
            return false;
        }
    }

    //------------------------------ Figure (with highlight) ------------------------------   
    @Override
    public void draw(Graphics g) {
        /*
         * if (actionCounter > 0) {
         * g.setColor(Color.red);
         * Rectangle r = displayBox();
         * g.fillRect(r.x, r.y, r.width, r.height);
         * }
         */
    }

    @Override
    public Rectangle displayBox() {
        return _faArcConnection.displayBox();
    }

    @Override
    public boolean containsPoint(int x, int y) {
        return _faArcConnection.containsPoint(x, y);
    }

    @Override
    public void basicDisplayBox(Point origin, Point corner) {
        // do nothing, as the DisplayBox of the FAArcConnection is used!
    }

    @Override
    protected void basicMoveBy(int dx, int dy) {
        // do nothing, as the DisplayBox of the FAArcConnection is used!
    }

    @Override
    public boolean isHighlighted() {
        return isChangingState() || _afterglow;
    }

    private void invalidateHighlight() {
        Figure highlight = _faArcConnection.getHighlightFigure();

        if (highlight != null) {
            Rectangle area = highlight.displayBox();
            DrawingChangeEvent dce = new CH.ifa.draw.framework.DrawingChangeEvent(_drawing, area);
            _drawing.drawingInvalidated(dce);
        }
    }

    @Override
    public Vector<FigureHandle> handles() {
        Vector<FigureHandle> handles = new Vector<FigureHandle>();

        handles.addElement(new NullHandle(this, RelativeLocator.northWest()));
        handles.addElement(new NullHandle(this, RelativeLocator.northEast()));
        handles.addElement(new NullHandle(this, RelativeLocator.southWest()));
        handles.addElement(new NullHandle(this, RelativeLocator.southEast()));
        return handles;
    }

    private void executeUpdate() {
        LOGGER.debug("executeUpdate() called");
        if (listener() != null) {
            //            if (_afterglowThread != null && _afterglowThread.isAlive()) {
            //                try {
            //                    _afterglowThread.notifyAll();
            //                } catch (Exception e) {
            //                }
            //            }
            _afterglow = true;
            invalidate();
            invalidateHighlight();
            listener().figureRequestUpdate(StorableApi.createFigureChangeEvent(this));


            final FAArcInstanceConnection thizz = this;
            _afterglowThread = new Thread() {
                @Override
                public void run() {
                    try {
                        synchronized (this) {
                            this.wait(300);
                        }


                        // If another _afterglow is instantiated, the current one is
                        // interrupted. So at this point, we were not.
                        try {
                            //                  _drawing.lock();
                            _afterglow = false;
                            invalidate();
                            invalidateHighlight();
                            listener()
                                .figureRequestUpdate(StorableApi.createFigureChangeEvent(thizz));
                        } finally {
                            //                _drawing.unlock();
                        }
                    } catch (InterruptedException e) {
                        // do nothing
                    }
                }
            };

            SimulationThreadPool.getCurrent().execute(_afterglowThread);
        }
    }

    /**
     * This event signals that a Transition Occurrence has started or completed.
     * <p>
     * This method is naturally called asynchronously to the AWT event
     * queue, therefore the real update is scheduled to be executed within
     * the AWT thread. If multiple update notifications occur before the
     * update execution, those are ignored.
     * </p>
     **/
    @Override
    public void update() {
        if (_updateTask != null) {
            _updateTask.scheduleUpdate();
        }
    }

    @Override
    public boolean inspect(DrawingView view, boolean alternate) {
        if (alternate) { // right-click
            if (fire()) {
                String name;
                try {
                    name = _transitionInstance.asString();
                } catch (RemoteException e) {
                    name = e.toString();
                }
                view.editor().showStatus("Transition " + name + " fired.");
            } else {
                view.editor().showStatus("No binding for this transition.");
            }
        }

        //        else { // double-click
        //            logger.debug("Searching bindings for " + _transitionInstance + "...");
        //            BindingSelectionFrame.open(_transitionInstance, getSimulation());
        //        }
        return true;
    }

    public boolean fire() {
        CPNSimulation simulation = getSimulation();

        simulation.getBreakpointManager().clearLog();
        boolean result;
        try {
            result = _transitionInstance.fireOneBinding();
        } catch (RemoteException e) {
            LOGGER.error(e.getMessage(), e);
            result = false;
        }

        simulation.simulationRefresh();
        return result;
    }

    protected CPNSimulation getSimulation() {
        //return _drawing.getMode().getSimulation();
        return ModeReplacement.getInstance().getSimulation();
    }
}