//----------------------------------------------------------------------------//
//                                                                            //
//                        S y s t e m B o u n d a r y                         //
//                                                                            //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">                          //
//  Copyright © Hervé Bitteur and others 2000-2013. All rights reserved.      //
//  This software is released under the GNU General Public License.           //
//  Goto http://kenai.com/projects/audiveris to report bugs or suggestions.   //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.sheet;

import omr.util.BrokenLine;
import omr.util.Navigable;
import omr.util.VerticalSide;

import net.jcip.annotations.NotThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.ListIterator;

/**
 * Class {@code SystemBoundary} handles the closed boundary of a system
 * as a 2D area, defined by two broken lines, on north and south sides.
 *
 * @author Hervé Bitteur
 */
@NotThreadSafe
public class SystemBoundary
{
    //~ Static fields/initializers ---------------------------------------------

    /** Usual logger utility */
    private static final Logger logger = LoggerFactory.getLogger(SystemBoundary.class);

    //~ Instance fields --------------------------------------------------------
    //
    /** Related system. */
    @Navigable(false)
    private final SystemInfo system;

    /** The north and south limits. */
    private final EnumMap<VerticalSide, BrokenLine> limits = new EnumMap<>(
            VerticalSide.class);

    /** Containment is delegated to a Polygon, lazily created. */
    private Polygon polygon;

    //~ Constructors -----------------------------------------------------------
    //
    //----------------//
    // SystemBoundary //
    //----------------//
    /**
     * Creates a new SystemBoundary object with north and south borders
     *
     * @param system the related system
     * @param north  the northern limit
     * @param south  the southern limit
     */
    public SystemBoundary (SystemInfo system,
                           BrokenLine north,
                           BrokenLine south)
    {
        if ((north == null) || (south == null)) {
            throw new IllegalArgumentException(
                    "SystemBoundary needs non-null limits");
        }

        this.system = system;
        limits.put(VerticalSide.TOP, north);
        limits.put(VerticalSide.BOTTOM, south);
    }

    //~ Methods ----------------------------------------------------------------
    //
    //----------//
    // contains //
    //----------//
    /**
     * Check whether the provided point lies within the SystemBoundary
     *
     * @param point the provided point
     * @return true if the provided point lies within the SystemBoundary
     */
    public boolean contains (Point point)
    {
        return getPolygon().contains(point);
    }

    //-----------//
    // getBounds //
    //-----------//
    /**
     * Report the rectangular bounds that enclose this boundary
     *
     * @return the rectangular bounds
     */
    public Rectangle getBounds ()
    {
        return getPolygon().getBounds();
    }

    //----------//
    // getLimit //
    //----------//
    /**
     * Report the broken line on provided side
     *
     * @param side the desired side (TOP or BOTTOM)
     * @return the desired limit
     */
    public BrokenLine getLimit (VerticalSide side)
    {
        return limits.get(side);
    }

    //-----------//
    // getLimits //
    //-----------//
    /**
     * Report the limits as a collection
     *
     * @return the north and south limits
     */
    public Collection<BrokenLine> getLimits ()
    {
        return limits.values();
    }

    //--------//
    // render //
    //--------//
    /**
     * Paint the SystemBoundary in the provided graphic context.
     *
     * @param g        the Graphics context
     * @param editable flag to indicate whether boundary is editable
     */
    public void render (Graphics g,
                        boolean editable)
    {
        final Graphics2D g2 = (Graphics2D) g;
        final int radius = BrokenLine.getStickyDistance();
        final Polygon poly = getPolygon();

        Color oldColor = g.getColor();

        if (editable) {
            g.setColor(Color.RED);
        }

        // Draw the polygon
        g2.drawPolygon(poly);

        // Mark the reference points
        if (editable) {
            for (int i = 0; i < poly.npoints; i++) {
                g2.drawRect(
                        poly.xpoints[i] - radius,
                        poly.ypoints[i] - radius,
                        2 * radius,
                        2 * radius);
            }
        }

        g.setColor(oldColor);
    }

    //----------//
    // toString //
    //----------//
    @Override
    public String toString ()
    {
        return "{Boundary" + " system#" + system.getId() + " north:"
               + limits.get(VerticalSide.TOP) + " south:"
               + limits.get(VerticalSide.BOTTOM) + "}";
    }

    //--------//
    // update //
    //--------//
    /**
     * Update the system boundary.
     */
    public void update ()
    {
        // Simply invalidate the polygon
        polygon = null;
    }

    //------------//
    // getPolygon //
    //------------//
    /**
     * Report defining polygon (after creating it if needed);
     *
     * @return the system polygon
     */
    private Polygon getPolygon ()
    {
        if (polygon == null) {
            Polygon poly = new Polygon();

            // North side (from left to right)
            for (Point point : limits.get(VerticalSide.TOP).getPoints()) {
                poly.addPoint(point.x, point.y);
            }

            // South side (in reverse order)
            List<Point> points = limits.get(VerticalSide.BOTTOM).getPoints();
            for (ListIterator<Point> it = points.listIterator(points.size());
                    it.hasPrevious();) {
                Point point = it.previous();
                poly.addPoint(point.x, point.y);
            }

            polygon = poly;
        }

        return polygon;
    }
}
