/**
 * Polygon
 */

interface Point {
  x: number;
  y: number;
}

interface Line {
  p1: Point;
  p2: Point;
}

interface Edge {
  p1: Point;
  p2: Point;
  a: number;
  b: number;
  c: number;
}

export default class Polygon {
  po: Array<Point>;
  edges: Array<Edge>;
  n: number;

  constructor(po) {
    if (po) {
      this.po = po.slice();
      this.n = po.length;
      this.buildEdges();
    }
  }

  private buildEdges() {
    if (!this.isValid()) return;
    let n = this.n, po = this.po;
    let edges = new Array<Edge>(n);

    for (let i = 1; i <= n; i++) {
      edges[i - 1] = {
        p1: po[i - 1],
        p2: po[i % n],
        a: po[i % n].y - po[i - 1].y,
        b: -(po[i % n].x - po[i - 1].x),
        c: po[i % n].x * po[i - 1].y - po[i - 1].x * po[i % n].y
      }
    }
    this.edges = edges;

  }

  /**
   * check if polygon is valid
   */
  isValid(): boolean {
    if (!this.po || this.po.length < 3) return false;
    else return true;
  }

  /**
   * check a polygon is ordered clockwise or counterclockwise
   * Return the clockwise status of a curve, clockwise or counterclockwise
   *  0: return 0 for incomputables eg: colinear points;  1: counterclockwise;  -1: clockwise; 
   * 
   * It is assumed that
   * - the polygon is closed
   * - the last point is not repeated
   * - the polygon is simple (does not intersect itself or have holes)
   */
  dir(): number {
    let sum = 0;
    let po = this.po;

    if (!po || po.length < 3) return 0;

    po.push(po[0]);
    for (let i = 1; i < po.length; i++) {
      sum += (po[i].x - po[i - 1].x) * (po[i].y + po[i - 1].y);
    }
    po.pop();

    if (sum > 0) return 1;
    else if (sum < 0) return -1;
    else return 0;
  }

  /** 
   * https://www.jianshu.com/p/ba03c600a557
   * check if a pixel is in a polygon
   * @param p : pixel to check
   * @return true if contain and false if not
   */
  inside(p: Point): boolean {
    //  判断涉嫌是否与边相交
    function isRayIntersectEdge(p: Point, edge: Edge): boolean {
      if (edge.p1.y == edge.p2.y) return false;  // 排除与射线平行、重合，线段首尾端点重合的情况
      if (edge.p1.y > p.y && edge.p2.y > p.y) return false; // 线段在射线上边
      if (edge.p1.y < p.y && edge.p2.y < p.y) return false; // 线段在射线下边
      if (edge.p1.y == p.y && edge.p2.y > p.y) return false; // 交点为下端点
      if (edge.p2.y == p.y && edge.p1.y > p.y) return false; // 交点为下端点
      if (edge.p1.x < p.x && edge.p2.x < p.x) return false; // 线段在射线左边

      let xseg = edge.p2.x - (edge.p2.x - edge.p1.x) * (edge.p2.y - p.y) / (edge.p2.y - edge.p1.y);
      if (xseg < p.x) return false; // 交点在射线起点的左侧
      return true;
    }

    let n = this.n;
    let edges = this.edges;
    let count = 0;
    for (let edge of edges) {
      if (p.x == edge.p1.x && p.y == edge.p1.y) return true; // 直接跟多边形顶点重合，直接判定为inside
      if (edge.p1.y == edge.p2.y && p.y == edge.p1.y
        && p.x >= Math.min(edge.p1.x, edge.p2.x)
        && p.x <= Math.max(edge.p1.x, edge.p2.x)) return true; // 边与射线平行，且该点直接是在边上，也直接判定为inside
      if (isRayIntersectEdge(p, edge)) {
        count++;
      }
    }

    if (count % 2 == 0) { // outside
      return false;
    } else { // inside
      return true;
    }
  }


  /**
   * check is contains another polygon
   * @param poly 
   */
  contains(poly: Polygon): boolean {

    let po = poly.po;

    for (let p of po) {
      if (!this.inside(p)) {
          return false;
      }
    }
    return true;
  }

  /**
   * check if the polygon self intersect
   * @returns 
   */
  isIntersect(): boolean {
    if (!this.isValid()) return false;

    let n = this.n;
    let edges = this.edges;
    let interPos: Point;
    if (n <= 3) return false;
    for (let i = 0; i < n; i++) {
      interPos = crossPos(i, (i + 2) % n);
      if (!interPos) { // 两条边平行
        continue;
      }
      if (isInLine(interPos, edges[i]) && isInLine(interPos, edges[(i + 2) % n])) {
        return true;
      }
    }

    return false;

    // 求两条边的交点(通过叉积求二维直线交点)
    function crossPos(index1, index2): Point {
      let pos = null;

      let A1 = edges[index1].a;
      let B1 = edges[index1].b;
      let A2 = edges[index2].a;
      let B2 = edges[index2].b;
      let C1 = edges[index1].c;
      let C2 = edges[index2].c;

      let m = A1 * B2 - A2 * B1;
      if (m != 0) {
        pos = { x: (C2 * B1 - C1 * B2) / m, y: (C1 * A2 - C2 * A1) / m };
      }

      return pos;
    }

    // 判断点是否在线段内
    function isInLine(pos: Point, edge: Edge): boolean {
      let maxX = Math.max(edge.p1.x, edge.p2.x);
      let minX = Math.min(edge.p1.x, edge.p2.x);
      let maxY = Math.max(edge.p1.y, edge.p2.y);
      let minY = Math.min(edge.p1.y, edge.p2.y);

      if (pos.x <= maxX && pos.x >= minX && pos.y <= maxY && pos.y >= minY) {
        return true;
      }
      return false;
    }

  }

}
