/**
 * This file is part of LibLaserCut.
 * Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de>
 * RWTH Aachen University - 52062 Aachen, Germany
 *
 * LibLaserCut is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LibLaserCut is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.t_oster.liblasercut.vectoroptimizers;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author Thomas Oster <thomas.oster@rwth-aachen.de>
 */
public class InnerFirstVectorOptimizer extends VectorOptimizer
{

  // helper classes:
  private abstract class ElementValueComparator implements Comparator<Element>
  {

    /**
     * get one integer from the element
     * order ascending by this integer
     * inside objects should have the lowest values
     */
    abstract int getValue(Element e);

    /**
     * compare by getValue()
     */
    @Override
    public int compare(Element a, Element b)
    {
      Integer av = new Integer(getValue(a));
      Integer bv = new Integer(getValue(b));
      return av.compareTo(bv);
    }
  }

  private class XMinComparator extends ElementValueComparator
  {
    // compare by XMin a>b
    @Override
    int getValue(Element e)
    {
      return -e.boundingBox().getXMin();
    }
  }

  private class YMinComparator extends ElementValueComparator
  {
    // compare by YMin a>b
    @Override
    int getValue(Element e)
    {
      return -e.boundingBox().getYMin();
    }
  }

  private class XMaxComparator extends ElementValueComparator
  {
    // compare by XMax a<b
    @Override
    int getValue(Element e)
    {
      return e.boundingBox().getXMax();
    }
  }

  private class YMaxComparator extends ElementValueComparator
  {
    // compare by YMax a<b
    @Override
    int getValue(Element e)
    {
      return e.boundingBox().getYMax();
    }
  }

  @Override
  protected List<Element> sort(List<Element> e)
  {
    List<Element> result = new LinkedList<Element>();
    if (e.isEmpty())
    {
      return result;
    }
    /**
     * cut inside parts first, outside parts later
     * this algorithm is very robust, it works even for unconnected paths that
     * are split into individual lines (e.g. from some DXF imports)
     * it is not completely perfect, as it only considers the bounding-box and
     * not the individual path
     *
     * see below for documentation of the inner workings
     */
    result.addAll(e);
    /**
     * HEURISTIC:
     * this algorithm is based on the following observation:
     * let I and O be rectangles, I inside O
     * for explanations, assume that:
     * - the X-axis goes from left to right
     * - the Y-axis goes from bottom to top
     *
     * ---------------- O: outside rectangle
     * | |
     * | ---- |
     * y axis | |in| I |
     * ^ | ---- |
     * | | |
     * | ----------------
     * |
     * ------> x axis
     *
     * look at each border:
     * right border: I.getXMax() < O.getXMax()
     * left border: I.getXMin() > O.getXMin()
     * top border: I.getYMax() < O.getYMax()
     * bottom border: I.getYMin() > O.getYMin()
     *
     * If we now SORT BY ymax ASCENDING, ymin DESCENDING, xmax ASCENDING, xmin
     * DESCENDING
     * (higher sorting priority listed first)
     * we get the rectangles sorted inside-out:
     * 1. I
     * 2. O
     *
     * Because we sort by four values, this still works if
     * the two rectangles start at the same corner and have the same width,
     * but only differ in height.
     *
     * If each rectangle is split into four separate lines
     * (e.g. because of a bad DXF import),
     * this still mostly works:
     * 1. O: bottom line
     * 2. I: bottom
     * 3. I: top, left, right (both have same YMax, but top has a higher YMin)
     * 4: O: top, left, right (both have same YMax, but top has a higher YMin)
     *
     * TRADEOFFS AND LIMITATIONS:
     * This algorithm does not work for paths that have the same bounding-box
     * (e.g. a circle inscribed to a square)
     *
     * For concave polygons with the same bounding-box,
     * many simple Polygon-inside-Polygon algorithms also fail
     * (or have a useless definition of "inside" that matches the misbehaviour):
     * Draw a concave polygon, remove one point at a concave edge.
     * The resulting polygon is clearly outside the original, although every
     * edge of it is inside the original!
     *
     * FUTURE WORK:
     * It would also be nice to sort intersecting polygons, where one polygon
     * is "90% inside" and "10% outside" the other.
     * Real-world example:_A circular hole at the border of a rectangle.
     * Due to rounding errors, it may appear slightly outside the rectangle.
     * Mathematically, it is neither fully inside nor fully outside, but the
     * user clearly wants it to be counted as "inside".
     *
     * POSSIBLE LIBRARIES:
     * http://sourceforge.net/projects/geom-java/
     * http://sourceforge.net/projects/jts-topo-suite
     *
     * USEFUL METHODS:
     * Element.isClosedPath()
     */
    // do the work:
    Collections.sort(result, new XMinComparator());
    Collections.sort(result, new YMinComparator());
    Collections.sort(result, new XMaxComparator());
    Collections.sort(result, new YMaxComparator());
    return result;
  }
}