/*
 * @(#)PlaceFigure.java 5.1
 *
 */

package de.renew.fa.figures;

import java.awt.Dimension;
import java.awt.Graphics;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Vector;

import CH.ifa.draw.figures.ArrowTip;
import CH.ifa.draw.figures.EllipseFigure;
import CH.ifa.draw.figures.PolyLineFigure;
import CH.ifa.draw.framework.ChildFigure;
import CH.ifa.draw.standard.FigureEnumerator;
import CH.ifa.draw.standard.MergedFigureEnumerator;
import CH.ifa.draw.standard.RelativeLocator;
import de.renew.draw.storables.api.StorableApi;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;
import de.renew.draw.ui.ontology.FigureHandle;
import de.renew.faformalism.shadow.ShadowFAState;
import de.renew.gui.CPNTextFigure;
import de.renew.gui.FigureWithHighlight;
import de.renew.gui.InscribableFigure;
import de.renew.gui.InstanceDrawing;
import de.renew.gui.InstanceFigure;
import de.renew.gui.NodeFigure;
import de.renew.gui.SimulableFigure;
import de.renew.remote.ObjectAccessor;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.ShadowNetElement;


/**
 * Displays an FAStateFigure and is used by {@link de.renew.fa.figures.FAStateInstanceFigure FAStateInstanceFigure}.
 * This class handles the inner workings and attributes of the figure as well as decorations.
 *
 *
 */
public class FAStateFigure extends EllipseFigure
    implements InscribableFigure, FigureWithHighlight, NodeFigure, SimulableFigure
{
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(FAStateFigure.class);
    private static final FAArcConnection ANCHOR =
        new FAArcConnection(null, null, PolyLineFigure.LINE_STYLE_NORMAL);

    // State types
    public static final int NULL = 0;
    public static final int START = 1;
    public static final int END = 2;
    public static final int STARTEND = 3;

    // Marking appearance types
    public static final int HIGHLIGHT = 0;
    public static final int CARDINALITY = 1;
    public static final int TOKENS = 2;
    public static final int EXPANDED_TOKENS = 3;
    /*
     * Serialization support.
     */
    static final long serialVersionUID = 6652986258188946097L;

    /*
     * The shadow element of this FAState.
     * It is null because it will be created,
     * when needed.
     */
    private transient ShadowFAState _shadow = null;

    /**
     * The decoration giving the FAStateFigure graphical properties of a
     * start, end, startend or simple state.
     */
    private FigureDecoration _decoration = new NullDecoration();

    /**
     * This figure will be highlighted in a instance drawing in the same manner
     * as the place figure will be highlighted. May be <code>null</code>.
     *
     * @serial
     */
    private Figure _hilightFig = null;

    /**
     * This is the word the automaton will simulate.
     * The field will always be null, unless this state is a start state.
     */
    private FAWordTextFigure _word = null;

    public FAStateFigure() {
        super();
        setDecoration(new NullDecoration());
        ANCHOR.setFrameColor(getFrameColor());
        ANCHOR.setAttribute("LineShape", Integer.valueOf(PolyLineFigure.BSPLINE_SHAPE));
        ANCHOR
            .setAttribute("BSplineSegments", Integer.valueOf(CH.ifa.draw.util.BSpline.DEFSEGMENTS));
        ANCHOR.setEndDecoration(new ArrowTip());
    }

    /**
     * Checks if this is a start state.
     *
     * @return True if this is a start state, false otherwise.
     */
    public boolean isStartState() {
        return _decoration instanceof StartDecoration || _decoration instanceof StartEndDecoration;
    }

    /**
     * Checks if this is an end state.
     *
     * @return True if this is an end state, false otherwise.
     */
    public boolean isEndState() {
        return _decoration instanceof EndDecoration || _decoration instanceof StartEndDecoration;
    }

    @Override
    public void addChild(ChildFigure child) {
        if (child instanceof FAWordTextFigure word) {
            if (!isStartState()) {
                return;
            }
            if (_word != null) {
                _word.figureRemoved(StorableApi.createFigureChangeEvent(_word));
            }
            _word = word;
        } else {
            super.addChild(child);
        }
    }

    // ------------------------------ Shadow and simulation processing ------------

    // public void release() {
    // setHighlightFigure(null);
    // super.release();
    // if (shadow != null) {
    // shadow.discard();
    // }
    // }

    @Override
    public ShadowNetElement buildShadow(ShadowNet net) {
        LOGGER.debug("buildShadow(Shadownet) called by " + this);
        if (_shadow != null) {
            _shadow.discard();
        }
        _shadow = new ShadowFAState(net);
        _shadow.setContext(this);
        _shadow._stateType = getStateType();
        // Set the figure ID to the shadow element to access this figure
        _shadow.setID(this.getID());
        _shadow.setTrace(getTraceMode());
        if (isStartState() && _word != null) {
            _shadow._word = _word.getText();
        } else if (isStartState()) {
            _shadow._word = " ";
        }
        //TODO: remove
        LOGGER.debug("built " + _shadow);
        return _shadow;
    }

    /**
     * Retrieves the type of the state.
     * @return The type of the state.
     */
    public int getStateType() {
        // state type is determined by the FigureDecorations type
        return _decoration instanceof NullDecoration ? NULL
            : (_decoration instanceof StartDecoration ? START
                : (_decoration instanceof EndDecoration ? END : STARTEND));
    }

    @Override
    public ShadowNetElement getShadow() {
        return _shadow;
    }

    @Override
    public InstanceFigure createInstanceFigure(
        InstanceDrawing drawing, Hashtable<Serializable, ObjectAccessor> netElements)
    {
        return new FAStateInstanceFigure(drawing, this, netElements);
    }

    // ------------------------------ Graphics processing -------------------------  
    public static Dimension defaultDimension() {
        return new Dimension(40, 40);
    }

    /**
     * Draws the decoration in the direction of the displayBox.
     *
     * @param g The graphics object to draw on.
     */
    private void decorate(Graphics g) {
        if (_decoration != null) {
            _decoration.draw(g, displayBox(), getFillColor(), getFrameColor());
        }
    }

    /**
     * Gets the relevant decoration.
     *
     * @return The decoration.
     */
    public FigureDecoration getDecoration() {
        return _decoration;
    }

    /**
     * {@inheritDoc}
     *
     * Also draws the decoration.
     */
    @Override
    public void internalDraw(Graphics g) {
        super.internalDraw(g);
        decorate(g);
    }

    /**
     * Sets this figures decoration to the input.
     * If the input is null, sets it as a new NullDecoration.
     * Does NOT draw the decoration.
     *
     * @param decoration - The decoration.
     */
    public void setDecoration(FigureDecoration decoration) {
        if (decoration == null) {
            _decoration = new NullDecoration();
            return;
        }
        this._decoration = decoration;
        if ((!(_decoration instanceof StartDecoration || _decoration instanceof StartEndDecoration))
            && (_word != null)) {
            _word.figureRemoved(StorableApi.createFigureChangeEvent(_word));
        }
    }

    @Override
    public void setHighlightFigure(Figure fig) {
        _hilightFig = fig;
    }


    // ------------------------------ Other stuff --------------------------------- 
    @Override
    public Vector<FigureHandle> handles() {
        Vector<FigureHandle> handles = super.handles();
        handles.addElement(new FAConnectionHandle(this, RelativeLocator.center(), ANCHOR));
        return handles;
    }

    /**
     * Returns all figures with dependencies of the superclass plus an optional
     * hilight figure.
     */
    @Override
    public FigureEnumeration getFiguresWithDependencies() {
        FigureEnumeration superDep = super.getFiguresWithDependencies();
        Vector<Figure> myDep = new Vector<Figure>(1);
        myDep.addElement(getHighlightFigure());
        return new MergedFigureEnumerator(superDep, new FigureEnumerator(myDep));
    }

    @Override
    public Figure getHighlightFigure() {
        return _hilightFig;
    }

    public int getMarkingAppearance() {
        Object value = getAttribute("MarkingAppearance");
        if (value instanceof Integer) {
            return ((Integer) value).intValue();
        }
        return HIGHLIGHT;
    }

    public boolean getTraceMode() {
        Object value = getAttribute("TraceMode");
        if (value instanceof Boolean) {
            return ((Boolean) value).booleanValue();
        }
        return true;
    }

    @Override
    public void read(StorableInput dr) throws IOException {
        super.read(dr);
        if (dr.getVersion() >= 3) {
            setHighlightFigure((Figure) dr.readStorable());

        }
        _decoration = (FigureDecoration) dr.readStorable();
        dr.readString();
    }

    @Override
    public void write(StorableOutput dw) {
        super.write(dw);
        dw.writeStorable(_hilightFig);

        dw.writeStorable(_decoration);
        if (_decoration != null) {
            dw.writeString(_decoration.getClass().getName());
        } else {
            dw.writeString(NullDecoration.class.getName());
        }
    }

    /**
     * Retrieves the states name
     * @return Name of state
     */
    public String getName() {
        String cln = getClass().getName();
        int ind = cln.lastIndexOf('.') + 1;
        if (ind > 0) {
            cln = cln.substring(ind);
        }

        FigureEnumeration children = children();
        while (children.hasMoreElements()) {
            Figure child = children.nextElement();
            if (child instanceof FATextFigure) {
                FATextFigure textFig = (FATextFigure) child;
                if (textFig.getType() == CPNTextFigure.NAME) {
                    return cln + " \"" + textFig.getText() + "\"";
                }
            }
        }

        return cln + " (" + getID() + ")";
    }

    @Override
    public void removeChild(ChildFigure child) {
        if (child == _word) {
            _word = null;
        }
        super.removeChild(child);
    }

    @Override
    public void release() {
        setHighlightFigure(null);
        super.release();
        if (_shadow != null) {
            _shadow.discard();
        }
        if (_word != null) {
            _word.release();
        }
    }

    @Override
    public String toString() {
        return getName();
    }
}