/*
 * ******************************************************************************
 *  * Copyright 2015 See AUTHORS file.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *   http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *  *****************************************************************************
 */

package games.rednblack.editor.utils.poly.tracer;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

/**
* http://astroboid.x50.cc/blog/2010/06/mapping-box2d-shapes-from-textures/ A
* mostly "blind" C# conversion of the TextureConverter code from Farseer, I
* even left the original german comments in place. Seems to work.
*/
public class TextureConverter {
   // User contribution from Sickbattery
   // / <summary>
   // / TODO:
   // / 1.) Das Array welches ich bekomme am besten in einen boolean array
   // verwandeln. Wurde die Geschwindigkeit verbessern
   // / </summary>
   private static int[][] ClosePixels = new int[][] { { -1, -1 }, { 0, -1 },
         { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };

   public static Array<Vector2> createPolygon(int[] data, int width, int height)
         throws Exception {
      PolygonCreationAssistance pca = new PolygonCreationAssistance(data,
            width, height);
      Array<Array<Vector2>> verts = createPolygon(pca);
      return verts.get(0);
   }

   public static Array<Vector2> createPolygon(int[] data, int width,
         int height, boolean holeDetection) throws Exception {
      PolygonCreationAssistance pca = new PolygonCreationAssistance(data,
            width, height);
      pca.HoleDetection = holeDetection;
      Array<Array<Vector2>> verts = createPolygon(pca);
      return verts.get(0);
   }

   public static Array<Array<Vector2>> createPolygon(int[] data, int width,
         int height, float hullTolerance, int alphaTolerance,
         boolean multiPartDetection, boolean holeDetection) throws Exception {
      PolygonCreationAssistance pca = new PolygonCreationAssistance(data,
            width, height);
      pca.setHullTolerance(hullTolerance);
      pca.setAlphaTolerance(alphaTolerance);
      pca.MultipartDetection = multiPartDetection;
      pca.HoleDetection = holeDetection;
      return createPolygon(pca);
   }

   private static Array<Array<Vector2>> createPolygon(
         PolygonCreationAssistance pca) throws Exception {
      Array<Array<Vector2>> polygons = new Array<Array<Vector2>>();
      Array<Vector2> polygon;
      Array<Vector2> holePolygon;
      Vector2 holeEntrance = null;
      Vector2 polygonEntrance = null;
      Array<Vector2> blackList = new Array<Vector2>();
      // First of all: Check the array you just got.
      if (pca.IsValid()) {
         boolean searchOn;
         do {
            if (polygons.size == 0) {
               polygon = CreateSimplePolygon(pca, new Vector2(),
                     new Vector2());
               if (polygon != null && polygon.size > 2) {
                  polygonEntrance = GetTopMostVertex(polygon);
               }
            } else if (polygonEntrance != null) {
               polygon = CreateSimplePolygon(pca, polygonEntrance,
                     new Vector2(polygonEntrance.x - 1f,
                           polygonEntrance.y));
            } else {
               break;
            }
            searchOn = false;
            if (polygon != null && polygon.size > 2) {
               if (pca.HoleDetection) {
                  do {
                     holeEntrance = GetHoleHullEntrance(pca, polygon,
                           holeEntrance);
                     if (holeEntrance != null) {
                        if (!vectorListContains(blackList, holeEntrance)) {
                           blackList.add(holeEntrance);
                           holePolygon = CreateSimplePolygon(pca,
                                 holeEntrance, new Vector2(
                                       holeEntrance.x + 1,
                                       holeEntrance.y));
                           if (holePolygon != null
                                 && holePolygon.size > 2) {
                              holePolygon.add(holePolygon.get(0));
                              Reference<Integer> vertex2IndexRef = new Reference<Integer>(
                                    0);
                              Reference<Integer> vertex1IndexRef = new Reference<Integer>(
                                    0);
                              if (SplitPolygonEdge(polygon,
                                    EdgeAlignment.Vertical,
                                    holeEntrance, vertex1IndexRef,
                                    vertex2IndexRef)) {

                                 polygon.ensureCapacity(holePolygon.size);
                                 for (int i = holePolygon.size - 1; i <= 0; i--) {
                                    polygon.insert(
                                          vertex2IndexRef.v,
                                          holePolygon.get(i));
                                 }
                              }
                           }
                        } else {
                           break;
                        }
                     } else {
                        break;
                     }
                  } while (true);
               }
               polygons.add(polygon);
               if (pca.MultipartDetection) {
                  // 1: 95 / 151
                  // 2: 232 / 252
                  //
                  polygonEntrance = new Vector2();
                  while (GetNextHullEntrance(pca, polygonEntrance,
                        polygonEntrance)) {
                     boolean inPolygon = false;
                     for (int i = 0; i < polygons.size; i++) {
                        polygon = polygons.get(i);
                        if (InPolygon(pca, polygon, polygonEntrance)) {
                           inPolygon = true;
                           break;
                        }
                     }
                     if (!inPolygon) {
                        searchOn = true;
                        break;
                     }
                  }
               }
            }
         } while (searchOn);
      } else {
         throw new Exception(
               "Sizes don't match: Color array must contain texture width * texture height elements.");
      }
      return polygons;
   }

   private static Vector2 GetHoleHullEntrance(PolygonCreationAssistance pca,
         Array<Vector2> polygon, Vector2 startVertex) throws Exception {
      Array<CrossingEdgeInfo> edges = new Array<CrossingEdgeInfo>();
      Vector2 entrance;
      int startLine;
      int endLine;
      int lastSolid = 0;
      boolean foundSolid;
      boolean foundTransparent;
      if (polygon != null && polygon.size > 0) {
         if (startVertex != null) {
            startLine = (int) startVertex.y;
         } else {
            startLine = (int) GetTopMostCoord(polygon);
         }
         endLine = (int) GetBottomMostCoord(polygon);
         if (startLine > 0 && startLine < pca.Height && endLine > 0
               && endLine < pca.Height) {
            // go from top to bottom of the polygon
            for (int y = startLine; y <= endLine; y += pca
                  .getHoleDetectionLineStepSize()) {
               // get x-coord of every polygon edge which crosses y
               edges = GetCrossingEdges(polygon, EdgeAlignment.Vertical, y);
               // we need an even number of crossing edges
               if (edges.size > 1 && edges.size % 2 == 0) {
                  for (int i = 0; i < edges.size; i += 2) {
                     foundSolid = false;
                     foundTransparent = false;
                     for (int x = (int) edges.get(i).CrossingPoint.x; x <= (int) edges
                           .get(i + 1).CrossingPoint.x; x++) {
                        if (pca.IsSolid(x, y)) {
                           if (!foundTransparent) {
                              foundSolid = true;
                              lastSolid = x;
                           }
                           if (foundSolid && foundTransparent) {
                              entrance = new Vector2(lastSolid, y);
                              if (DistanceToHullAcceptable(pca,
                                    polygon, entrance, true)) {
                                 return entrance;
                              }
                              entrance = null;
                              break;
                           }
                        } else {
                           if (foundSolid) {
                              foundTransparent = true;
                           }
                        }
                     }
                  }
               }
            }
         }
      }
      return null;
   }

   private static boolean DistanceToHullAcceptable(
         PolygonCreationAssistance pca, Array<Vector2> polygon,
         Vector2 point, boolean higherDetail) {
      if (polygon != null && polygon.size > 2) {
         Vector2 edgeVertex2 = polygon.get(polygon.size - 1).cpy();
         Vector2 edgeVertex1 = new Vector2();
         if (higherDetail) {
            for (int i = 0; i < polygon.size; i++) {
               edgeVertex1.set(polygon.get(i));
               if (LineTools.DistanceBetweenPointAndLineSegment(point,
                     edgeVertex1, edgeVertex2) <= pca.getHullTolerance()
                     || LineTools.DistanceBetweenPointAndPoint(point,
                           edgeVertex1) <= pca.getHullTolerance()) {
                  return false;
               }
               edgeVertex2.set(polygon.get(i));
            }
            return true;
         } else {
            for (int i = 0; i < polygon.size; i++) {
               edgeVertex1.set(polygon.get(i));
               if (LineTools.DistanceBetweenPointAndLineSegment(point,
                     edgeVertex1, edgeVertex2) <= pca.getHullTolerance()) {
                  return false;
               }
               edgeVertex2.set(polygon.get(i));
            }
            return true;
         }
      }
      return false;
   }

   private static boolean InPolygon(PolygonCreationAssistance pca,
         Array<Vector2> polygon, Vector2 point) throws Exception {
      boolean inPolygon = !DistanceToHullAcceptable(pca, polygon, point, true);
      if (!inPolygon) {
         Array<CrossingEdgeInfo> edges = GetCrossingEdges(polygon,
               EdgeAlignment.Vertical, (int) point.y);
         if (edges.size > 0 && edges.size % 2 == 0) {
            for (int i = 0; i < edges.size; i += 2) {
               if (edges.get(i).CrossingPoint.x <= point.x
                     && edges.get(i + 1).CrossingPoint.x >= point.x) {
                  return true;
               }
            }
            return false;
         }
         return false;
      }
      return true;
   }

   private static Vector2 GetTopMostVertex(Array<Vector2> vertices) {
      float topMostValue = Float.MAX_VALUE;
      Vector2 topMost = null;
      for (int i = 0; i < vertices.size; i++) {
         if (topMostValue > vertices.get(i).y) {
            topMostValue = vertices.get(i).y;
            topMost = vertices.get(i);
         }
      }
      return topMost.cpy();
   }

   private static float GetTopMostCoord(Array<Vector2> vertices) {
      float returnValue = Float.MAX_VALUE;
      for (int i = 0; i < vertices.size; i++) {
         if (returnValue > vertices.get(i).y) {
            returnValue = vertices.get(i).y;
         }
      }
      return returnValue;
   }

   private static float GetBottomMostCoord(Array<Vector2> vertices) {
      float returnValue = Float.MIN_VALUE;
      for (int i = 0; i < vertices.size; i++) {
         if (returnValue < vertices.get(i).y) {
            returnValue = vertices.get(i).y;
         }
      }
      return returnValue;
   }

   public static Boolean vectorEquals(Vector2 v1, Vector2 v2) {
      return v1.x == v2.x && v1.y == v2.y;
   }

   public static int vectorListIndexOf(Array list, Vector2 v) {
      for (int i = 0; i < list.size; i++) {
         Object obj = list.get(i);
         if (obj == v)
            return i;
         if (obj instanceof Vector2) {
            Vector2 vect = (Vector2) obj;
            if (vectorEquals(v, vect))
               return i;
         }
      }
      return -1;
   }

   public static Boolean vectorListContains(Array list, Vector2 v) {
      int index = vectorListIndexOf(list, v);
      return index != -1;
   }

   public static Vector2 vectorSub(Vector2 v1, Vector2 v2) {
      return new Vector2(v1.x - v2.x, v1.y - v2.y);
   }

   public static Vector2 vectorAdd(Vector2 v1, Vector2 v2) {
      return new Vector2(v1.x + v2.x, v1.y + v2.y);
   }

   public static float vectorCross(Vector2 v1, Vector2 v2) {
      return v1.x * v2.y - v1.y * v2.x;
   }

   public static Vector2 vectorCross(Vector2 v1, float scalar) {
      return new Vector2(scalar * v1.y, -scalar * v1.x);
   }

   public static float vectorDot(Vector2 v1, Vector2 v2) {
      return v1.x * v2.x + v1.y * v2.y;
   }

   public static Vector2 vectorMul(Vector2 v1, float scalar) {
      return new Vector2(v1.x * scalar, v1.y * scalar);
   }

   private static Array<CrossingEdgeInfo> GetCrossingEdges(
         Array<Vector2> polygon, EdgeAlignment edgeAlign, int checkLine)
         throws Exception {
      Array<CrossingEdgeInfo> edges = new Array<CrossingEdgeInfo>();
      Vector2 slope = new Vector2();
      Vector2 edgeVertex1 = new Vector2();
      Vector2 edgeVertex2 = new Vector2();
      Vector2 slopePreview = new Vector2();
      Vector2 edgeVertexPreview = new Vector2();
      Vector2 crossingPoint = new Vector2();
      boolean addCrossingPoint;
      if (polygon.size > 1) {
         edgeVertex2.set(polygon.get(polygon.size - 1));
         switch (edgeAlign) {
         case Vertical:
            for (int i = 0; i < polygon.size; i++) {
               edgeVertex1.set(polygon.get(i));
               if ((edgeVertex1.y >= checkLine && edgeVertex2.y <= checkLine)
                     || (edgeVertex1.y <= checkLine && edgeVertex2.y >= checkLine)) {
                  if (edgeVertex1.y != edgeVertex2.y) {
                     addCrossingPoint = true;
                     slope.set(vectorSub(edgeVertex2, edgeVertex1));
                     if (edgeVertex1.y == checkLine) {
                        edgeVertexPreview.set(polygon.get((i + 1)
                              % polygon.size));
                        slopePreview.set(vectorSub(edgeVertex1,
                              edgeVertexPreview));
                        if (slope.y > 0) {
                           addCrossingPoint = (slopePreview.y <= 0);
                        } else {
                           addCrossingPoint = (slopePreview.y >= 0);
                        }
                     }
                     if (addCrossingPoint) {
                        crossingPoint = new Vector2(
                              (checkLine - edgeVertex1.y) / slope.y
                                    * slope.x + edgeVertex1.x,
                              checkLine);
                        edges.add(new CrossingEdgeInfo(edgeVertex1,
                              edgeVertex2, crossingPoint, edgeAlign));
                     }
                  }
               }
               edgeVertex2.set(edgeVertex1);
            }
            break;
         case Horizontal:
            throw new Exception(
                  "EdgeAlignment.Horizontal isn't implemented yet. Sorry.");
         }
      }
      edges.sort();
      // Collections.sort(edges);
      return edges;
   }

   private static boolean SplitPolygonEdge(Array<Vector2> polygon,
         EdgeAlignment edgeAlign, Vector2 coordInsideThePolygon,
         Reference<Integer> vertex1IndexRef,
         Reference<Integer> vertex2IndexRef) throws Exception {
      Array<CrossingEdgeInfo> edges;
      Vector2 slope = new Vector2();
      int nearestEdgeVertex1Index = 0;
      int nearestEdgeVertex2Index = 0;
      boolean edgeFound = false;
      float shortestDistance = Float.MAX_VALUE;
      boolean edgeCoordFound = false;
      Vector2 foundEdgeCoord = new Vector2();
      vertex1IndexRef.v = 0;
      vertex2IndexRef.v = 0;
      switch (edgeAlign) {
      case Vertical:
         edges = GetCrossingEdges(polygon, EdgeAlignment.Vertical,
               (int) coordInsideThePolygon.y);
         foundEdgeCoord.y = coordInsideThePolygon.y;
         if (edges != null && edges.size > 1 && edges.size % 2 == 0) {
            float distance;
            for (int i = 0; i < edges.size; i++) {
               if (edges.get(i).CrossingPoint.x < coordInsideThePolygon.x) {
                  distance = coordInsideThePolygon.x
                        - edges.get(i).CrossingPoint.x;
                  if (distance < shortestDistance) {
                     shortestDistance = distance;
                     foundEdgeCoord.x = edges.get(i).CrossingPoint.x;
                     edgeCoordFound = true;
                  }
               }
            }
            if (edgeCoordFound) {
               shortestDistance = Float.MAX_VALUE;
               int edgeVertex2Index = polygon.size - 1;
               int edgeVertex1Index;
               for (edgeVertex1Index = 0; edgeVertex1Index < polygon.size; edgeVertex1Index++) {
                  Vector2 tempVector1 = polygon.get(edgeVertex1Index)
                        .cpy();
                  Vector2 tempVector2 = polygon.get(edgeVertex2Index)
                        .cpy();
                  distance = LineTools
                        .DistanceBetweenPointAndLineSegment(
                              foundEdgeCoord, tempVector1,
                              tempVector2);
                  if (distance < shortestDistance) {
                     shortestDistance = distance;
                     nearestEdgeVertex1Index = edgeVertex1Index;
                     nearestEdgeVertex2Index = edgeVertex2Index;
                     edgeFound = true;
                  }
                  edgeVertex2Index = edgeVertex1Index;
               }
               if (edgeFound) {
                  slope.set(vectorSub(
                        polygon.get(nearestEdgeVertex2Index),
                        polygon.get(nearestEdgeVertex1Index)));
                  slope.nor();
                  Vector2 tempVector = polygon.get(
                        nearestEdgeVertex1Index).cpy();
                  distance = LineTools.DistanceBetweenPointAndPoint(
                        tempVector, foundEdgeCoord);
                  vertex1IndexRef.v = nearestEdgeVertex1Index;
                  vertex2IndexRef.v = nearestEdgeVertex1Index + 1;
                  // distance * slope + polygon[vertex1Index]
                  polygon.insert(
                        nearestEdgeVertex1Index,
                        vectorAdd(vectorMul(slope, distance),
                              polygon.get(vertex1IndexRef.v)));
                  polygon.insert(
                        nearestEdgeVertex1Index,
                        vectorAdd(vectorMul(slope, distance),
                              polygon.get(vertex2IndexRef.v)));
                  return true;
               }
            }
         }
         break;
      case Horizontal:
         throw new Exception(
               "EdgeAlignment.Horizontal isn't implemented yet. Sorry.");
      }
      return false;
   }

   private static Array<Vector2> CreateSimplePolygon(
         PolygonCreationAssistance pca, Vector2 entrance, Vector2 last) {
      boolean entranceFound = false;
      boolean endOfHull = false;
      Array<Vector2> polygon = new Array<Vector2>();
      Array<Vector2> hullArea = new Array<Vector2>();
      Array<Vector2> endOfHullArea = new Array<Vector2>();
      Vector2 current = new Vector2();
      Vector2 zeroVec = new Vector2();
      // Get the entrance point. //todo: alle moglichkeiten testen
      if (vectorEquals(entrance, zeroVec) || !pca.InBounds(entrance)) {
         entranceFound = GetHullEntrance(pca, entrance);
         if (entranceFound) {
            current.set(entrance.x - 1f, entrance.y);
         }
      } else {
         if (pca.IsSolid(entrance)) {
            if (IsNearPixel(pca, entrance, last)) {
               current.set(last);
               entranceFound = true;
            } else {
               Vector2 temp = new Vector2();
               if (SearchNearPixels(pca, false, entrance, temp)) {
                  current.set(temp);
                  entranceFound = true;
               } else {
                  entranceFound = false;
               }
            }
         }
      }
      if (entranceFound) {
         polygon.add(entrance);
         hullArea.add(entrance);
         Vector2 next = entrance.cpy();
         do {
            // Search in the pre vision list for an outstanding point.
            Vector2 outstanding = new Vector2();
            if (SearchForOutstandingVertex(hullArea,
                  pca.getHullTolerance(), outstanding)) {
               if (endOfHull) {
                  // We have found the next pixel, but is it on the last
                  // bit of the
                  // hull?
                  if (vectorListContains(endOfHullArea, outstanding)
                        && !vectorListContains(polygon, outstanding)) {
                     // Indeed.
                     polygon.add(outstanding);
                  }
                  // That's enough, quit.
                  break;
               }
               // Add it and remove all vertices that don't matter anymore
               // (all the vertices before the outstanding).
               polygon.add(outstanding);
               int index = vectorListIndexOf(hullArea, outstanding);
               if (index == -1) {
                  int debug = 1;
               }
               if (index >= 0) {
                  // hullArea = hullArea.subList(index + 1,
                  // hullArea.size);

                  // Array<Vector2> newArray = new Array<Vector2>
                  // (hullArea.size - (index + 1));
                  int counter = 0;
                  for (int i = index + 1; i < hullArea.size; i++) {
                     Vector2 v = hullArea.get(index);
                     // newArray.add(v);
                     hullArea.set(counter, v);
                     counter++;
                  }
                  // hullArea.clear();
                  // hullArea = newArray;
                  for (int i = 0; i < index + 1; i++) {
                     hullArea.pop();
                  }
               }
            }
            // Last point gets current and current gets next. Our little
            // spider is
            // moving forward on the hull ;).
            last.set(current);
            current.set(next);
            // Get the next point on hull.
            next = new Vector2();
            if (GetNextHullPoint(pca, last, current, next)) {
               // Add the vertex to a hull pre vision list.
               hullArea.add(next);
            } else {
               // Quit
               break;
            }
            if (vectorEquals(next, entrance) && !endOfHull) {
               // It's the last bit of the hull, search on and exit at next
               // found
               // vertex.
               endOfHull = true;
               endOfHullArea.addAll(hullArea);
            }
         } while (true);
      }
      return polygon;
   }

   private static boolean SearchNearPixels(PolygonCreationAssistance pca,
         boolean searchingForSolidPixel, Vector2 current, Vector2 foundPixel) {
      int x;
      int y;
      for (int i = 0; i < 8; i++) {
         x = (int) current.x + ClosePixels[i][0];
         y = (int) current.y + ClosePixels[i][1];
         if (!searchingForSolidPixel ^ pca.IsSolid(x, y)) {
            foundPixel.set(x, y);
            return true;
         }
      }
      // Nothing found.
      foundPixel.set(0, 0);
      return false;
   }

   private static boolean IsNearPixel(PolygonCreationAssistance pca,
         Vector2 current, Vector2 near) {
      for (int i = 0; i < 8; i++) {
         int x = (int) current.x + ClosePixels[i][0];
         int y = (int) current.y + ClosePixels[i][1];
         if (x >= 0 && x <= pca.Width && y >= 0 && y <= pca.Height) {
            if (x == (int) near.x && y == (int) near.y) {
               return true;
            }
         }
      }
      return false;
   }

   private static boolean GetHullEntrance(PolygonCreationAssistance pca,
         Vector2 entrance) {
      // Search for first solid pixel.
      for (int y = 0; y < pca.Height; y++) {
         for (int x = 0; x < pca.Width; x++) {
            if (pca.IsSolid(x, y)) {
               entrance.set(x, y);
               return true;
            }
         }
      }
      // If there are no solid pixels.
      entrance.set(0, 0);
      return false;
   }

   private static boolean GetNextHullEntrance(PolygonCreationAssistance pca,
         Vector2 start, Vector2 entrance) {
      // Search for first solid pixel.
      int size = pca.Height * pca.Width;
      int x;
      boolean foundTransparent = false;
      for (int i = (int) start.x + (int) start.y * pca.Width; i < size; i++) {
         if (pca.IsSolid(i)) {
            if (foundTransparent) {
               x = i % pca.Width;
               entrance.set(x, (i - x) / pca.Width);
               return true;
            }
         } else {
            foundTransparent = true;
         }
      }
      // If there are no solid pixels.
      entrance.set(0, 0);
      return false;
   }

   private static boolean GetNextHullPoint(PolygonCreationAssistance pca,
         Vector2 last, Vector2 current, Vector2 next) {
      int x;
      int y;
      int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(last,
            current);
      int indexOfPixelToCheck;
      int pixelsToCheck = 8; // _closePixels.Length;
      for (int i = 0; i < pixelsToCheck; i++) {
         indexOfPixelToCheck = (indexOfFirstPixelToCheck + i)
               % pixelsToCheck;
         x = (int) current.x + ClosePixels[indexOfPixelToCheck][0];
         y = (int) current.y + ClosePixels[indexOfPixelToCheck][1];
         if (x >= 0 && x < pca.Width && y >= 0 && y <= pca.Height) {
            if (pca.IsSolid(x, y)) // todo
            {
               next.set(x, y);
               return true;
            }
         }
      }
      next.set(0, 0);
      return false;
   }

   private static boolean SearchForOutstandingVertex(Array<Vector2> hullArea,
         float hullTolerance, Vector2 outstanding) {
      Vector2 outstandingResult = new Vector2();
      boolean found = false;
      if (hullArea.size > 2) {
         int hullAreaLastPoint = hullArea.size - 1;
         Vector2 tempVector1;
         Vector2 tempVector2 = hullArea.get(0);
         Vector2 tempVector3 = hullArea.get(hullAreaLastPoint);
         // Search between the first and last hull point.
         for (int i = 1; i < hullAreaLastPoint; i++) {
            tempVector1 = hullArea.get(i);
            // Check if the distance is over the one that's tolerable.
            if (LineTools.DistanceBetweenPointAndLineSegment(tempVector1,
                  tempVector2, tempVector3) >= hullTolerance) {
               outstandingResult.set(hullArea.get(i));
               found = true;
               break;
            }
         }
      }
      outstanding.set(outstandingResult);
      return found;
   }

   private static int GetIndexOfFirstPixelToCheck(Vector2 last, Vector2 current) {
      // .: pixel
      // l: last position
      // c: current position
      // f: first pixel for next search
      // f . .
      // l c .
      // . . .
      // Calculate in which direction the last move went and decide over the
      // next
      // first pixel.
      switch ((int) (current.x - last.x)) {
      case 1:
         switch ((int) (current.y - last.y)) {
         case 1:
            return 1;
         case 0:
            return 0;
         case -1:
            return 7;
         }
         break;
      case 0:
         switch ((int) (current.y - last.y)) {
         case 1:
            return 2;
         case -1:
            return 6;
         }
         break;
      case -1:
         switch ((int) (current.y - last.y)) {
         case 1:
            return 3;
         case 0:
            return 4;
         case -1:
            return 5;
         }
         break;
      }
      return 0;
   }
}

enum EdgeAlignment {
   Vertical, Horizontal
}

class CrossingEdgeInfo implements Comparable<CrossingEdgeInfo> {
   private EdgeAlignment _alignment;
   private Vector2 _crossingPoint;
   @SuppressWarnings("unused")
   private Vector2 _edgeVertex2;
   @SuppressWarnings("unused")
   private Vector2 _egdeVertex1;
   public Vector2 EdgeVertex1;
   public Vector2 EdgeVertex2;
   public EdgeAlignment CheckLineAlignment;
   public Vector2 CrossingPoint;

   public CrossingEdgeInfo(Vector2 edgeVertex1, Vector2 edgeVertex2,
         Vector2 crossingPoint, EdgeAlignment checkLineAlignment) {
      _egdeVertex1 = edgeVertex1.cpy();
      _edgeVertex2 = edgeVertex2.cpy();
      _alignment = checkLineAlignment;
      _crossingPoint = crossingPoint;
      CrossingPoint = _crossingPoint;
   }

   public int compareTo(CrossingEdgeInfo obj) {
      CrossingEdgeInfo cei = obj;

      int result = 0;
      switch (_alignment) {
      case Vertical:
         if (_crossingPoint.x < cei.CrossingPoint.y) {
            result = -1;
         } else if (_crossingPoint.x > cei.CrossingPoint.y) {
            result = 1;
         }
         break;
      case Horizontal:
         if (_crossingPoint.y < cei.CrossingPoint.y) {
            result = -1;
         } else if (_crossingPoint.y > cei.CrossingPoint.y) {
            result = 1;
         }
         break;
      }
      return result;
   }
}

// / <summary>
// / Class used as a tools container and helper for the texture-to-vertices code.
// / </summary>
class PolygonCreationAssistance {
   private int _alphaTolerance;
   private int _holeDetectionLineStepSize;
   private float _hullTolerance;

   public PolygonCreationAssistance(int[] data, int width, int height) {
      Data = data;
      Width = width;
      Height = height;
      setAlphaTolerance((byte) 20);
      setHullTolerance(1.5f);
      setHoleDetectionLineStepSize(1);
      HoleDetection = false;
      MultipartDetection = false;
   }

   private int[] Data;
   public int Width;
   public int Height;

   public int getAlphaTolerance() {
      return _alphaTolerance;
   }

   public void setAlphaTolerance(int value) {
      _alphaTolerance = value & 0xFF;
   }

   public float getHullTolerance() {
      return _hullTolerance;
   }

   public void setHullTolerance(float value) {
      float hullTolerance = value;
      /*if (hullTolerance > 4f)
         hullTolerance = 4f;*/
      if (hullTolerance < 0.9f)
         hullTolerance = 0.9f;
      _hullTolerance = hullTolerance;
   }

   public int getHoleDetectionLineStepSize() {
      return _holeDetectionLineStepSize;
   }

   private void setHoleDetectionLineStepSize(int value) {
      if (value < 1) {
         _holeDetectionLineStepSize = 1;
      } else {
         if (value > 10) {
            _holeDetectionLineStepSize = 10;
         } else {
            _holeDetectionLineStepSize = value;
         }
      }
   }

   public boolean HoleDetection;
   public boolean MultipartDetection;

   public boolean IsSolid(Vector2 pixel) {
      return IsSolid((int) pixel.x, (int) pixel.y);
   }

   public boolean IsSolid(int x, int y) {
      if (x >= 0 && x < Width && y >= 0 && y < Height) {
         int data = Data[x + y * Width];
         long mask1 = (long) data & 0xFFFFFFFFL;
         long mask2 = mask1 & 0x000000FF;
         Boolean opaque = mask2 >= _alphaTolerance;
         if (opaque || mask2 != 0) {
            int debug = 1;
         }
         return opaque;
      }
      return false;
   }

   public boolean IsSolid(int index) {
      if (index >= 0 && index < Width * Height) {
         int data = Data[index];
         long mask1 = (long) data & 0xFFFFFFFFL;
         long mask2 = mask1 & 0x000000FF;
         Boolean opaque = mask2 >= _alphaTolerance;
         if (opaque || mask2 != 0) {
            int debug = 1;
         }
         return opaque;
         // return (mask1 >= _alphaToleranceRealValue);
      }
      return false;
   }

   public boolean InBounds(Vector2 coord) {
      return (coord.x >= 0f && coord.x < Width && coord.y >= 0f && coord.y < Height);
   }

   public boolean IsValid() {
      if (Data != null && Data.length > 0)
         return Data.length == Width * Height;
      return false;
   }
}

class Reference<K> {
   public K v;

   public Reference(K v) {
      this.v = v;
   }

   @Override
   public String toString() {
      return v.toString();
   }

   @Override
   public boolean equals(Object obj) {
      return v.equals(obj);
   }

   @Override
   public int hashCode() {
      return v.hashCode();
   }
}

class LineTools {
   public static float DistanceBetweenPointAndPoint(Vector2 point1,
         Vector2 point2) {
      Vector2 v = TextureConverter.vectorSub(point1, point2);
      return v.len();
   }

   public static float DistanceBetweenPointAndLineSegment(Vector2 point,
         Vector2 lineEndPoint1, Vector2 lineEndPoint2) {
      Vector2 v = TextureConverter.vectorSub(lineEndPoint2, lineEndPoint1);
      Vector2 w = TextureConverter.vectorSub(point, lineEndPoint1);
      float c1 = TextureConverter.vectorDot(w, v);
      if (c1 <= 0)
         return DistanceBetweenPointAndPoint(point, lineEndPoint1);
      float c2 = TextureConverter.vectorDot(v, v);
      if (c2 <= c1)
         return DistanceBetweenPointAndPoint(point, lineEndPoint2);
      float b = c1 / c2;
      Vector2 pointOnLine = TextureConverter.vectorAdd(lineEndPoint1,
            TextureConverter.vectorMul(v, b));
      return DistanceBetweenPointAndPoint(point, pointOnLine);
   }
}
