package yi.graph.polygon;

import jim.image.*;
import java.util.*;
import yi.graph.polygon.gpc.*;
import java.io.*;

/**
 * Generic Polygon.  One generic polygon may contain several generic contours,
 * some of which are holes.
 */

public class GenericPolygon implements Serializable{

  public final static int GPC_DIFF = GPC.GPC_DIFF;   // Difference
  public final static int GPC_INT = GPC.GPC_INT;    // Intersection
  public final static int GPC_XOR = GPC.GPC_XOR;    // Exclusive or
  public final static int GPC_UNION = GPC.GPC_UNION;  // Union

  /**
   * Hole (internal coutour) / external contour flags
   */
  public boolean [] holes;

  /**
   * Contour array.  One generic polygon is made of several generic contours,
   * some of which are holes.
   */
  public GenericContour[] contours;

  public GenericPolygon() {
  }


  public GenericPolygon(boolean[] holes, GenericContour[] contours) {
    this.holes = holes;
    this.contours = contours;
  }

  /**
   * Init a simple GenericPolygon having only one contour
   * @param contour
   */
  public GenericPolygon(GenericContour contour) {
    contours = new GenericContour[]{contour};
    holes = new boolean[]{false};
  }

  /**
   * Construct GenericPolygon from GpcPolygon.  Use SHALLOW copy to accerate the
   * speed.
   * @param poly
   */
  public GenericPolygon(GpcPolygon poly) {
    int n = poly.getContourNum();
    holes = new boolean[n];
    contours = new GenericContour[n];
    for (int i=0; i<n; i++) {
      holes[i] = (poly.holes[i]!=0);
      contours[i] = new GenericContour(poly.contours[i]);
    }
  }

  public GenericPolygon(XPolygon poly) {
    contours = new GenericContour[] {
        new GenericContour(poly)
    };
    holes = new boolean[] {
        false
    };
  }

  /**
   * Construct a GenericPolygon from a jim.image.mask.  The mask ignores the
   * holes.  If a polygon contains a hole, two polygon will be returned: one
   * is the outer boundary and the other is the inner boundary.  So, "XOR" can
   * be used to make the holes.
   * @param mask
   */
  public GenericPolygon(Mask mask) {

    XPolygon[] out_polys = mask.getOutBoundary();
    XPolygon[] in_polys = mask.getInBoundary();

    GenericPolygon out_poly = null;
    if (out_polys!=null && out_polys.length > 0) {
      out_poly = new GenericPolygon(out_polys[0]);
      for (int i = 1; i < out_polys.length; i++) {
        GenericPolygon poly2 = new GenericPolygon(out_polys[i]);
        out_poly = gpcPolygonClip(GPC_XOR, out_poly, poly2);
      }
    }

    GenericPolygon in_poly = null;
    if (in_polys!=null && in_polys.length > 0) {
      in_poly = new GenericPolygon(in_polys[0]);
      for (int i = 1; i < in_polys.length; i++) {
        GenericPolygon poly2 = new GenericPolygon(in_polys[i]);
        in_poly = gpcPolygonClip(GPC_XOR, in_poly, poly2);
      }
    }

    if (in_poly != null) {
      out_poly = gpcPolygonClip(GPC_DIFF, out_poly, in_poly);
    }

    contours = out_poly.contours;
    holes = out_poly.holes;
  }

  public int getContourNum() {
    return (contours==null)? 0: contours.length;
  }

  public double getArea() {
    int contour_num = getContourNum();
    double area = 0;
    for (int i=0; i<contour_num; i++) {
      if (holes[i]) { // hole
        area -= contours[i].getArea();
      } else { // not hole
        area += contours[i].getArea();
      }
    }
    return area;
  }

  public double getLeft() {
    double left = Double.POSITIVE_INFINITY;
    int contour_num = getContourNum();
    for (int i=0; i<contour_num; i++) {
      if (holes[i]) { // hole
      } else { // not hole
        double l = contours[i].getLeft();
        if (l < left) left = l;
      }
    }
    return left;
  }

  public double getRight() {
    double right = Double.NEGATIVE_INFINITY;
    int contour_num = getContourNum();
    for (int i=0; i<contour_num; i++) {
      if (holes[i]) { // hole
      } else { // not hole
        double r = contours[i].getRight();
        if (r > right) right = r;
      }
    }
    return right;
  }

  public double getTop() {
    double top = Double.POSITIVE_INFINITY;
    int contour_num = getContourNum();
    for (int i=0; i<contour_num; i++) {
      if (holes[i]) { // hole
      } else { // not hole
        double t = contours[i].getTop();
        if (t < top) top = t;
      }
    }
    return top;
  }

  public double getBottom() {
    double bottom = Double.NEGATIVE_INFINITY;
    int contour_num = getContourNum();
    for (int i=0; i<contour_num; i++) {
      if (holes[i]) { // hole
      } else { // not hole
        double b = contours[i].getBottom();
        if (b > bottom) bottom = b;
      }
    }
    return bottom;
  }

  /**
   * This function is just for castor and it doesn't provide any real
   * functionalities.  Don't use it in your program
   * @param area
   */
  public void setArea(double area) {
  }


  public String toString() {
    StringBuffer buffer = new StringBuffer();

    int num_contours = getContourNum();

    // number of contours
    buffer.append(num_contours);
    buffer.append("\n");

    for (int c = 0; c < num_contours; c++) {
      GenericContour contour = contours[c];
      int num_vertices = contour.getVertexNum();

      // number of vertices
      buffer.append(num_vertices);
      buffer.append("\n");

      // hole flag
      buffer.append(holes[c]);
      buffer.append("\n");

      // vertices
      for (int v = 0; v < num_vertices; v++) {
        buffer.append(" ");
        buffer.append(contour.x[v]);
        buffer.append(" ");
        buffer.append(contour.y[v]);
        buffer.append("\n");
      }
    }

    return buffer.toString();
  }

  public void scale(double scale) {
    for (int i=0; i<contours.length; i++) {
      contours[i].scale(scale);
    }
  }

  public void shift(double dx, double dy) {
    for (int i=0; i<contours.length; i++) {
      contours[i].shift(dx, dy);
    }
  }


  public void draw(int[] pixels, int width, int height, int color, int thickness) {
    int num_contours = getContourNum();
    for (int c = 0; c < num_contours; c++) {
      GenericContour contour = contours[c];
      contour.draw(pixels, width, height, color, thickness);
    }
  }

  public void filledDraw(int[] pixels, int width, int height, int color) {
    int num_contours = getContourNum();
    Vector fill_polys = new Vector();
    Vector hole_polys = new Vector();
    for (int c = 0; c < num_contours; c++) {
      XPolygon poly = contours[c].toXPolygon();
      if (holes[c]) {
        hole_polys.add(poly);
      } else {
        fill_polys.add(poly);
      }
    }
    Mask fill_mask = new Mask(fill_polys);
    Mask hole_mask = new Mask(hole_polys);

    boolean[][] fill_boolean = fill_mask.getMask(width, height);
    boolean[][] hole_boolean = hole_mask.getMask(width, height);

    for (int i=0, index=0; i<height; i++) {
      for (int j=0; j<width; j++, index++) {
        if (fill_boolean != null && fill_boolean[i][j]) {
           if (hole_boolean==null || !hole_boolean[i][j]) {
             pixels[index] = color;
           }
        }
      }
    }
  }

  public final static int FILL_REPLACE = 0; // just replace
  public final static int FILL_MAX = 1; // change the value only when the fill is greater than the original value

  public void filledDraw(double[] pixels, int width, int height, double fill, int fill_condition) {
    int num_contours = getContourNum();
    Vector fill_polys = new Vector();
    Vector hole_polys = new Vector();
    for (int c = 0; c < num_contours; c++) {
      XPolygon poly = contours[c].toXPolygon();
      if (holes[c]) {
        hole_polys.add(poly);
      } else {
        fill_polys.add(poly);
      }
    }
    Mask fill_mask = new Mask(fill_polys);
    Mask hole_mask = new Mask(hole_polys);

    boolean[][] fill_boolean = fill_mask.getMask(width, height);
    boolean[][] hole_boolean = hole_mask.getMask(width, height);

    for (int i=0, index=0; i<height; i++) {
      for (int j=0; j<width; j++, index++) {
        if (fill_boolean != null && fill_boolean[i][j]) {
           if (hole_boolean==null || !hole_boolean[i][j]) {
             if (fill_condition == FILL_REPLACE) {
               pixels[index] = fill;
             } else if (fill_condition == FILL_MAX) {
               if (fill > pixels[index]) {
                 pixels[index] = fill;
               }
             }
           }
        }
      }
    }
  }

  public static GenericPolygon[] getPolygons(int[] labels, int width, int height) {
    return getPolygons(labels, width, height, false);
  }

  /**
   * Create polygons from a mask image.  Use the default parameter for polygon
   * generation (in Mask)
   * @param labels
   * @param width
   * @param height
   * @param ignore_zero
   * @return
   */
  public static GenericPolygon[] getPolygons(int[] labels, int width, int height, boolean ignore_zero) {

    return getPolygons(labels, width, height, false, 0, 0);
  }

  public static GenericPolygon[] getPolygons(int[] labels, int width, int height,
                                             boolean ignore_zero,
                                             float min_accuracy, int min_segment) {

    // number of labels
    int n = yi.stat.StatUtil.maximum(labels) + 1;

    GenericPolygon[] polygons = new GenericPolygon[n];
    boolean[][] binary_label = new boolean[height][width];

    int i= ignore_zero? 1: 0;
    if (ignore_zero) polygons[0] = new GenericPolygon();
    for (; i<n; i++) {
      // for each label, construct a binary image first
      for (int j = 0, h = 0; h < height; h++) {
        for (int w = 0; w < width; w++, j++) {
          binary_label[h][w] = (labels[j] == i);
        }
      }

      // use jim.Mask package to convert a region to a polygon
      if (min_accuracy == 0) min_accuracy = Mask.MIN_ACCURACY;
      if (min_segment == 0) min_segment = Mask.MIN_SEGMENT;
      Mask mask = new Mask(binary_label, null, min_accuracy, min_segment);
      polygons[i] = new GenericPolygon(mask);
    }

    return polygons;
  }

  public static GenericPolygon gpcPolygonClip(int set_operation,
                                              GenericPolygon subject_polygon,
                                              GenericPolygon clip_polygon) {
    GpcPolygon poly1 = new GpcPolygon(subject_polygon);
    GpcPolygon poly2 = new GpcPolygon(clip_polygon);
    GpcPolygon poly3 = GPC.gpcPolygonClip(set_operation, poly1, poly2);
    return new GenericPolygon(poly3);
  }

}
