/**
 * 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 com.t_oster.liblasercut.LaserProperty;
import com.t_oster.liblasercut.VectorCommand;
import com.t_oster.liblasercut.VectorPart;
import com.t_oster.liblasercut.platform.Point;
import com.t_oster.liblasercut.platform.Rectangle;
import java.util.LinkedList;
import java.util.List;

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

  public enum OrderStrategy
  {
    FILE,
    NEAREST,
    INNER_FIRST,
    SMALLEST_FIRST
  }

  protected class Element
  {

    LaserProperty prop;
    Point start;
    List<Point> moves = new LinkedList<Point>();

    void invert()
    {
      if (!moves.isEmpty())
      {
        moves.add(0, start);
        start = moves.remove(moves.size() - 1);
        List<Point> inv = new LinkedList<Point>();
        while (!moves.isEmpty())
        {
          inv.add(moves.remove(moves.size() - 1));
        }
        moves = inv;
      }
    }

    Point getEnd()
    {
      return moves.isEmpty() ? start : moves.get(moves.size() - 1);
    }

    /**
     * compute bounding box of moves, including start point
     *
     * @return Rectangle
     */
    Rectangle boundingBox()
    {
      if (start == null)
      { // TODO may this happen?
        return null;
      }
      Rectangle bb = new Rectangle(start.x, start.y, start.x, start.y);
      for (Point p : moves)
      {
        bb.add(p);
      }
      return bb;
    }

    /**
     * test if this Element represents a closed path (polygon)
     *
     * @return true if start equals end, false otherwise
     */
    boolean isClosedPath()
    {
      if ((start == null) || moves.isEmpty())
      {
        return false;
      }
      return getEnd().equals(start);
    }
  }

  public static VectorOptimizer create(OrderStrategy s)
  {
    switch (s)
    {
      case FILE:
        return new FileVectorOptimizer();
      case NEAREST:
        return new NearestVectorOptimizer();
      case INNER_FIRST:
        return new InnerFirstVectorOptimizer();
      case SMALLEST_FIRST:
        return new SmallestFirstVectorOptimizer();
    }
    throw new IllegalArgumentException("Unknown Order Strategy: " + s);
  }

  protected List<Element> divide(VectorPart vp)
  {
    List<Element> result = new LinkedList<Element>();
    Element cur = null;
    Point lastMove = null;
    LaserProperty lastProp = null;
    boolean stop = false;
    for (VectorCommand cmd : vp.getCommandList())
    {
      switch (cmd.getType())
      {
        case MOVETO:
        {
          lastMove = new Point(cmd.getX(), cmd.getY());
          stop = true;
          break;
        }
        case LINETO:
        {
          if (stop)
          {
            stop = false;
            if (cur != null)
            {
              result.add(cur);
            }
            cur = new Element();
            cur.start = lastMove;
            cur.prop = lastProp;
          }
          cur.moves.add(new Point(cmd.getX(), cmd.getY()));
          break;
        }
        case SETPROPERTY:
        {
          lastProp = cmd.getProperty();
          stop = true;
          break;
        }
      }
    }
    if (cur != null)
    {
      result.add(cur);
    }
    return result;
  }

  protected double dist(Point a, Point b)
  {
    return Math.sqrt((a.y - b.y) * (a.y - b.y) + (a.x - b.x) * (a.x - b.x));
  }

  protected abstract List<Element> sort(List<Element> e);

  public VectorPart optimize(VectorPart vp)
  {
    List<Element> opt = this.sort(this.divide(vp));
    LaserProperty cp = opt.isEmpty() ? vp.getCurrentCuttingProperty() : opt.get(0).prop;
    VectorPart result = new VectorPart(cp, vp.getDPI());
    for (Element e : opt)
    {
      if (!e.prop.equals(cp))
      {
        result.setProperty(e.prop);
        cp = e.prop;
      }
      result.moveto(e.start.x, e.start.y);
      for (Point p : e.moves)
      {
        result.lineto(p.x, p.y);
      }
    }
    return result;
  }
}