/*--------------------------------------------------------------*
  Copyright (C) 2006-2015 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  'License' for details on this and other legal matters.
*--------------------------------------------------------------*/

package org.omnetpp.figures.misc;

import org.eclipse.draw2d.AncestorListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Layer;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.StackLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;

/**
 * Container layer that hooks itself to its reference figure and relatively locates itself
 * to it using the provided locator. By default it has a StackLayout and is not opaque.
 *
 * @author rhornig
 */
// FIXME no longer used. can be removed
public class AttachedLayer extends Layer implements AncestorListener {

    protected IFigure refFigure;
    protected PrecisionPoint refPoint;
    protected IFigure attachedFigure;
    protected PrecisionPoint attachedPoint;
    protected Point translation = new Point(0,0);

    public AttachedLayer(IFigure refFig, PrecisionPoint refPoint,
                           IFigure attachedFig, PrecisionPoint attachedPoint,
                           Point translation) {
        super();
        this.refFigure = refFig;
        this.refPoint = refPoint;
        this.attachedFigure = attachedFig;
        this.attachedPoint = attachedPoint;
        this.translation = (translation == null) ? new Point(0,0) : translation;
        setLayoutManager(new StackLayout());

        // add the attached fig as our child
        add(attachedFig);

        // XXX NOTE that it would be much better to register ourselves in addNotify
        // and deregister  in removeNotify, however this causes a weird bug, that prevents
        // sending a notification to the next ancestor listener. Seems that we SHOULD NOT
        // modify (add/remove) to a listener list when directly in a notification processing.
        // This MAY cause that this object is still kept in memory even after removing it from
        // its parent because the referenceFigure still keep a a reference to it in the ancestorListener list
        // general problem: is it possible to manipulate a listenerList while processing an event fired for
        // this listener?
        // FIXME should be fixed once the issue is resolved in draw2d
        // (should provide a way of firing to all listener, even if the listener list is being modified)
        refFigure.addAncestorListener(this);
    }


//    @Override
//    public void addNotify() {
//        super.addNotify();
//        refFigure.addAncestorListener(this);
//    }
//
//
//    @Override
//    public void removeNotify() {
//        refFigure.removeAncestorListener(this);
//        super.removeNotify();
//    }


    /**
     * Static factory method to attach a figure to an other. It creates a wrapper LocatableFigure around
     * the provided figure and adds it as a child. The wrapper figure is added to the parentFig as
     * a child. The two figure is attached in a way that the two reference point matches.
     * @param refFig Figure to attach to
     * @param refLoc Reference point locator on the figure attach to
     * @param attachedFig The figure to be attached
     * @param attachedLoc Reference point locator for the attached figure
     * @param parentFig A figure to be used as a parent of the newly created wrapper figure
     * @return the wrapper figure used the attach the two figures
     */
    public AttachedLayer(IFigure refFig, int refLoc,
                                         IFigure attachedFig, int attachedLoc) {
        this(refFig, locToPrecPoint(refLoc), attachedFig, locToPrecPoint(attachedLoc), null);
    }

    public AttachedLayer(IFigure refFig, int refLoc, IFigure attachedFig, int attachedLoc, int dx, int dy) {
        this(refFig, locToPrecPoint(refLoc), attachedFig, locToPrecPoint(attachedLoc), new Point(dx,dy));
    }

    public static PrecisionPoint locToPrecPoint(int location) {
        PrecisionPoint p = new PrecisionPoint();
        switch (location & PositionConstants.NORTH_SOUTH) {
        case PositionConstants.NORTH:
            p.setPreciseY(0);
            break;
        case PositionConstants.SOUTH:
            p.setPreciseY(1.0);
            break;
        default:
            p.setPreciseY(0.5);
        }

        switch (location & PositionConstants.EAST_WEST) {
        case PositionConstants.WEST:
            p.setPreciseX(0);
            break;
        case PositionConstants.EAST:
            p.setPreciseX(1.0);
            break;
        default:
            p.setPreciseX(0.5);
        }
        return p;
    }

    public void setRefPoints(PrecisionPoint refPoint, PrecisionPoint attachedPoint) {
        this.refPoint = refPoint;
        this.attachedPoint = attachedPoint;
    }

    public void setRefPoints(int refLoc, int attachedLoc) {
        this.refPoint = locToPrecPoint(refLoc);
        this.attachedPoint = locToPrecPoint(attachedLoc);
    }

    public void setDeltaXY(int dx, int dy) {
        this.translation = new Point(dx,dy);
    }

    public void ancestorAdded(IFigure ancestor) {
    }

    public void ancestorMoved(IFigure ancestor) {
        // revalidate ourselves if our reference figure has moved
        revalidate();
    }

    public void ancestorRemoved(IFigure ancestor) {
        // remove ourselves too from our parent if the reference figure is removed
        if (getParent() != null)
            getParent().remove(this);
    }

    /**
     * Returns the figure's reference point in absolute coordinates
     * @param figure the figure whose reference point is requested
     * @param relRefPoint The relative placement of the refPoint in the bounding rectangle
     * @return the reference point relative to the figure's upper left corner
     */
    protected Point getRefPoint(IFigure figure, PrecisionPoint relRefPoint) {
        Rectangle bounds = figure.getBounds();
        return new PrecisionPoint((bounds.width - 1) * relRefPoint.preciseX(),
                         (bounds.height - 1) * relRefPoint.preciseY());
    }

    // set the bounds of this figure based on the relative data and the place of the reference figure
    protected void relocate() {
          Rectangle bounds = refFigure.getBounds().getCopy();

          bounds.setSize(attachedFigure.getPreferredSize());
          // set the size, so the calculation of the reference point will be correct
          attachedFigure.setBounds(bounds);

          // translate it to the correct position
          // REVIEW: these two are not needed since the coordinate systems of ref figure and attached figure are the same
          //refFigure.translateToAbsolute(bounds);
          //attachedFigure.translateToRelative(bounds);
          bounds.translate(getRefPoint(refFigure, refPoint));
          bounds.translate(getRefPoint(attachedFigure, attachedPoint).negate());
          bounds.translate(translation);
          attachedFigure.setBounds(bounds);
          // set the container position too (no border/inset calculation)
          setBounds(bounds);
  }

    @Override
    public void validate() {
        if (isValid())
            return;
        relocate();
        super.validate();
    }
}
