import Polygon from "./polygon";

interface Debug {
  bulgeCount: number;  // 异常凸起点个数
  createSegmentFailed: number; // 创建segment出错数
  spCount: number; // 特殊端点个数（找不到邻居的segment端点）
  spCountAfterOrderAdjust: number; // 经过单调纠偏后剩余的特殊端点数
  spCountAfterInsertAdjust: number; // 经过插值纠偏后剩余的特殊端点数
  totalIncompletePaths: number; // 非闭合的路径个数
  doesntMatterIncompletePaths: number; // 无所谓的非闭合路径个数
  realIncompletePaths: number;
  removeIncompletePaths: number; // 删除掉的孤立非闭合路径个数
  remvoeInvalidPaths: number; // 非法path的个数
  clipAdjustCount: number; // clip切割点调整个数
  selfIntersectCount: number; // 自相交多边形数量
}

interface Point {
  x: number;
  y: number;
  color?: number;
  a?: Point;
  b?: Point;
  neighbors?: Array<Point>;
  traced?: boolean;
  id?: string;
  adjusted?: boolean;
  bulge?: boolean;
  x0?: number;
  y0?: number;
}

interface Segment {
  endA : Point;
  endB : Point; 
  pt : Array<Point>;
  areaA : number;
  areaB :  number; 
  curve : Curve;
  po: number[];
  m: number;
  invalid?: boolean;
}

interface Path {
  segments: Array<Segment>;  // 一个path由多个segment构成
  complete?: boolean; // 是否完整
  s?: Point;  // path的起点
  e?: Point;  // path的终点
  curve?: Curve;   // bezier curve 信息， 由各个子segment的curve信息合成
  line?: Array<Point>; // 存放直接连线的顶点，即多边形顶点，基本同polygon，只不过可能经过reverse了
  polygon?: Polygon; // path对应的拟合多边形，有各个segment合并而成
  sign?: number;  // 1: 正曲线（逆时针）；-1: 负曲线（顺时针）；按一般惯例，大圆包小圆的场景，大圆为正（逆时针），小圆为负（顺时针）
}

interface PathMap {
  [key: number]: Array<Path> // key即区块id
}

// 两个区块相交点集
interface IntersectMap {
  [key: string]: PointSet  // key的组成： color1 + '-' color2
}


// 一系列点的集合
export class PointSet {
  w: number;
  h: number;
  size: number;
  data: Set<number>;

  constructor(w: number, h: number, data: Set<number>) {
    this.w = w;
    this.h = h;
    this.size = w * h;
    this.data = new Set(data);
  }

  at(x: number, y: number) {
    return (x >= 0 && x < this.w && y >= 0 && y < this.h) &&
      this.data.has(this.w * y + x);
  };

  index(i: number) {
    let y = Math.floor(i / this.w);
    let x = i - y * this.w;
    return { x, y };
  };

  remove(x: number, y: number) {
    this.data.delete(this.w * y + x);
  }

  copy() {
    let contour = new PointSet(this.w, this.h, this.data);
    return contour;
  };

  neighbors(x: number, y: number): Point[] {
    let neibs = [];

    if (this.at(x - 1, y)) { // left
      neibs.push({ x: x - 1, y: y});
    } 
    if (this.at(x + 1, y)) { // right
      neibs.push({ x: x + 1, y: y }); 
    } 
    if (this.at(x, y - 1)) { // up
      neibs.push({ x: x, y: y - 1 });
    } 
    if (this.at( x, y + 1)) { // down
      neibs.push( { x: x, y: y + 1 });
    } 
    if (this.at(x - 1, y - 1)) { // left up
      neibs.push({ x: x - 1, y: y - 1 });
    } 
    if (this.at(x + 1, y - 1)) { // right up
      neibs.push({ x: x + 1, y: y - 1 });
    } 
    if (this.at(x - 1, y + 1)) { // left down
      neibs.push({ x: x - 1, y: y + 1 });
    } 
    if (this.at(x + 1, y + 1)) { // right down
      neibs.push({ x: x + 1, y: y + 1 });
    }
    return neibs;
  }

}

class Curve {
  n: number;
  // tag是CORNER或CURVE类型标志，分解出来的每一段拟合曲线，有CURVE和CORNER两种，
  // CURVE表示贝塞尔曲线(a, u, w, b)，相应的c将存放u, w, b 3个点
  // CORNER表示是直线折角(a, v, b)，相应的c将存放v, b 2个点
  tag: any[];
  // c是控制点，tag 和 c 配合，长度是tag的3倍，因为一个tag对应3个点，
  // 如果是CURVE， 那么c[i][0]和c[i][1]分别对应 u 和 w 控制点，c[i][2]对应b
  // 如果CORNER，那么c[i][0]没用，c[i][1]表示控制点即两条线段交点 v，c[i][2]表示b点
  //（注：起始点 a 不需要存储，每一段的起始点 a 起始就是上一段的结束点 b， 第一个起始点 a 是末尾那段的结束点 b
  // 但这里有个问题， 这是针对闭合路径来说的，如果是开放路径， 这里是有问题的，也是要改造调整的地方）; 
  c: any[]; 
  vertex: any[];  // 调整后的顶点，小数点坐标点

  s: Point;  // 起点
  e: Point;  // 终点

  constructor(n) {
    this.n = n;
    this.tag = new Array(n);
    this.c = new Array(n * 3);
    this.vertex = new Array(n);
  }
}

export interface EtraceOption {
  alphamax?: number;
}

export class ETrace {
  width: number;
  height: number;
  mapPixels: Uint32Array;
  intersectMap: IntersectMap = {};
  segments: Segment[] = [];
  pathMap: PathMap = {};

  config: EtraceOption = {
    alphamax: 1,
  };

  // for debug
  hContour: Set<number> = new Set();
  vContour: Set<number> = new Set();
  poSet: Set<number> = new Set();

  debugInfo: Debug;


  constructor(mapPixels: Uint32Array, width: number, height: number) {
    this.mapPixels = mapPixels;
    this.width = width;
    this.height = height;

    this.debugInfo = { 
      bulgeCount: 0, 
      createSegmentFailed: 0,
      spCount: 0, 
      spCountAfterOrderAdjust: 0, 
      spCountAfterInsertAdjust: 0, 
      totalIncompletePaths: 0, 
      realIncompletePaths: 0,
      doesntMatterIncompletePaths: 0,
      removeIncompletePaths: 0, 
      remvoeInvalidPaths: 0,
      clipAdjustCount: 0,
      selfIntersectCount: 0,
    };

    this.pureMap();
    this.contourScan();
    this.makeSegments();
    this.mergeSegmentEndpoint();
    this.segmentsToPaths();
    this.curvedPaths();

    console.log(this.debugInfo);
  }

  // 对map图进行一轮净化 ，消灭封闭area内的孤点
  private pureMap() {
    let x, y, index, count, color;
    let neibColors, pureNeibColors, changeColor;
    let w = this.width;
    let h = this.height;
    let mapPixels = this.mapPixels;
    let shift = [
      { x: 0, y: -1 },
      { x: 1, y: 0 },
      { x: 0, y: 1 },
      { x: -1, y: 0 },
      { x: 1, y: -1 },
      { x: 1, y: 1 },
      { x: -1, y: 1 },
      { x: -1, y: -1 },
    ];

    for (x = 0; x < w; x++) {
      for (y = 0; y < h; y++) {
        index = y * w + x;
        color = mapPixels[index];
        // 周边邻居点
        neibColors = shift.map(s => mapPixels[(y + s.y) * w + (x + s.x)]).filter(c => c);
        pureNeibColors = neibColors.filter((item, idx, arr) => arr.indexOf(item, 0) == idx);  // 去重

        if (!pureNeibColors. find(c => c == color)) { // 没有邻居跟它的颜色一致
          if (pureNeibColors.length > 1) {
            pureNeibColors = pureNeibColors.map(c => {
              count = 0;
              for (let nc of neibColors) {
                if (nc == c) {
                  count++;
                }
              }
              return { c, count };
            });
            pureNeibColors.sort((a, b) => b.count - a.count);
            changeColor = pureNeibColors[0].c;
          } else {
            changeColor = pureNeibColors[0];
          }
          
          mapPixels[index] = changeColor;
        }
      }
    }
  }

  // 对map进行全边界扫描, 得到相交线集合
  private contourScan() {
    let mapPixels = this.mapPixels;
    let intersectMap = this.intersectMap;
    let chooseMap = {};
    let w = this.width;
    let h = this.height;
    let x, y, color1, color2, index, index1, index2;

    let hContour = this.hContour;
    let vContour = this.vContour;

    // 两个区块交界处，选择其一作为边缘点
    function chooseEdgePoint(color1, color2, index1, index2) {
      let index: number;
      let color: number;
      let key1 = color2 + '-' + color1;
      let key2 = color1 + '-' + color2;
      let value1 = chooseMap[key1];
      let value2 = chooseMap[key2];
      
      if (value1) {
        color = value1;
      } else if (value2) {
        color = value2;
      } else {
        color = color1;
        chooseMap[key1] = color;
      }

      if (color == color1) index = index1;
      else if (color == color2) index = index2;  
      

      return index;
    }

    function isBorder(x, y) {
      return (x == 0 || y == 0 || x == w - 1  || y == h - 1);
    }

    function addBorder(index, color) {
      let key = color + '-';
      if (!intersectMap[key]) {
        intersectMap[key] = new PointSet(w, h, new Set<number>());
      }
      intersectMap[key].data.add(index);
    }

    function addIntersect(index, color1, color2) {
      let key = color2 + '-' + color1;
      if (!intersectMap[key]) {
        key = color1 + '-' + color2;
        if (!intersectMap[key]) {
          intersectMap[key] = new PointSet(w, h, new Set<number>());
        }
      }
      intersectMap[key].data.add(index);
    }

    for (x = 0; x < w; x++) {
      index1 = x;
      color1 = mapPixels[index1];
      for (y = 0; y < h; y++) {
        index2 = y * w + x;
        color2 = mapPixels[index2];
        if (color1 != color2) { 
          index = chooseEdgePoint(color1, color2, index1, index2);
          addIntersect(index, color1, color2);
          color1 = color2;

          hContour.add(index);
        } 
        if (isBorder(x, y)) {
          addBorder(index2, color2);
        }
        index1 = index2;
      }
    }

    for (y = 0; y < h; y++) {
      index1 = y * w;
      color1 = mapPixels[index1];
      for (x = 0; x < w; x++) {
        index2 = y * w + x;
        color2 = mapPixels[index2];
        if (color1 != color2) {
          index = chooseEdgePoint(color1, color2, index1, index2);
          addIntersect(index, color1, color2);
          color1 = color2;  

          vContour.add(index);
        } 
        if (isBorder(x, y)) {
          addBorder(index2, color2);
        }
        index1 = index2;
      }
    }
  }


  // 相交线进一步处理生成segments
  private makeSegments() {

    let self = this;
    let intersectMap = this.intersectMap;
    let w = this.width, h = this.height;
    let keys = Object.keys(intersectMap);
    for (let key of keys) {
      let areas = key.split('-').map(e => +e); // 将key分解成两个area id
      let pixels = Array.from(intersectMap[key].data).map(e => {
        let y = Math.floor(e / w);
        let x = e - y * w;
        return { x, y };
      });
      
      let segs = pixelSort(pixels, areas[0], areas[1]);
      segs.forEach(s => this.segments.push(s));
    }

    function newPoint(p: Point) {
      return { x: p.x, y: p.y };
    }


    function pixelSort(pixels: Array<Point>, areaA: number, areaB: number): Segment[] {

      // if (areaA == 4282964191 && areaB == 4289335318) {
      //   console.log('pause');
      //   for (let p of pixels) {
      //     console.log(`{x: ${p.x}, y: ${p.y}},`);
      //   }
      // }
      let segment;
      let segments = [];
      let pHash = {};
      pixels.forEach(p => {
        p.id = `${p.x}.${p.y}`
        pHash[p.id] = p;
      })
  
      let shift = [
        { x: 0, y: -1 },
        { x: 1, y: 0 },
        { x: 0, y: 1 },
        { x: -1, y: 0 },
        { x: 1, y: -1 },
        { x: 1, y: 1 },
        { x: -1, y: 1 },
        { x: -1, y: -1 },
      ];
  
      pixels.forEach(p => {
        p.neighbors = shift.map(s => `${p.x + s.x}.${p.y + s.y}`)
          .map(id => pHash[id])
          .filter(p => p);
      })

      // 邻居降序排列
      pixels.forEach(p => {
        p.neighbors.sort((a, b) => a.neighbors.length - b.neighbors.length)
      })
  
      for (let i = 0; i < pixels.length; i++) {
        let p = pixels[i];

        if (p.neighbors.length == 0) { // 没有邻居的孤点，直接处理成segment
          // console.log(`found single point(${p.x}, ${p.y}) in pixels. areaA=${areaA}, areaB=${areaB}`);
          let seg = { endA: newPoint(p), endB: newPoint(p), pt: [newPoint(p)], areaA, areaB };
          segments.push(seg);
          pixels.splice(i, 1);
          i--;
          continue;
        }

        if (p.neighbors.length == 1) { // 只有一个邻居，可能是起止点或异常凸起点，如果是异常凸起点，需要干掉
          let neib = p.neighbors[0];
          let idx, left = false, right = false, up = false, down = false;
          for (let j = 0; j < neib.neighbors.length; j++) {
            let nb = neib.neighbors[j];
            if (nb == p) {
              idx = j;
              continue;
            }
            if (nb.x < neib.x) left = true;
            if (nb.x > neib.x) right = true;
            if (nb.y > neib.y) down = true;
            if (nb.y < neib.y) up = true;
          }
          if ((left && right) || (up && down)) {
            console.warn(`found a bulge: (${p.x}, ${p.y}) in pixels. areaA=${areaA}, areaB=${areaB}`);
            self.debugInfo.bulgeCount++;
            p.bulge = true;
            neib.neighbors.splice(idx, 1);
            pixels.splice(i, 1);
            i--;
          }
        }
      }

      // trace，让点集变成有序
      do {
        let index = pixels.findIndex(p => !p.traced);
        if (index < 0) break;
        let p = pixels[index];
        trace(p);
        // create segment
        segment = createSegment(pixels, areaA, areaB);
        if (segment) {
          segments.push(segment);
        }
        pixels = pixels.filter(p => !p.traced);
      } while (true);

      return segments;
  
    }

    function trace(p) {
      let n;
      let startP = p;
      p.traced = true;
      console.log('tracing');
  
      if (!p.a) {
        do {
          n = p.neighbors.find(pp => !pp.traced);
          if (n) {
            p.a = n;
            n.b = p;
            p = n;
            p.traced = true;
          } else {
            break;
          }
        } while (1)
      }
  
      p = startP;

      if (!p.b) {
        do {
          n = p.neighbors.find(pp => !pp.traced);
          if (n) {
            p.b = n;
            n.a = p;
            p = n;
            p.traced = true;
          } else {
            break;
          }
        } while(1)
  
      }
    }

    function createSegment(pixels: Point[], areaA, areaB) {
      let segment;
      let endA = pixels.find(e => e.a && !e.b);
      let endB = pixels.find(e => !e.a && e.b);
      let pt = [];


      if (!endA || !endB) {
        console.warn("create segment failed! pixel length = " + pixels.length);
        self.debugInfo.createSegmentFailed++;
        return null;
      }

      let p = endA;

      do {
        pt.push({ x: p.x, y: p.y });
        p = p.a;
        if (!p) break;
      } while (true)

      // check
      if (endA.x != pt[0].x || endA.y != pt[0].y 
        || endB.x != pt[pt.length - 1].x || endB.y != pt[pt.length - 1].y) {
          console.error("something wrong!");
      }

      segment = { 
        endA: newPoint(pt[0]), 
        endB: newPoint(pt[pt.length - 1]),
        pt, areaA, areaB 
      };

      return segment;

    }
  
  }

  // segment顶点调整，即距离相近的起始点，统一成同一个顶点
  private mergeSegmentEndpoint() {

    function sign(i) {
      return i > 0 ? 1 : i < 0 ? -1 : 0;
    }

    function equal(pt1: Point, pt2: Point) {
      return pt1.x == pt2.x && pt1.y == pt2.y;
    }
    
    function ddist(p, q) {
      return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y));
    }

    function isSameSign(s1, s2) {
      if (s1 == 0 || s2 == 0) return true;
      if (s1 == s2) return true;
      return false;
    }

    function isNeighbor(pt1, pt2) {
      if (Math.abs(pt1.x - pt2.x) <= 1 && Math.abs(pt1.y - pt2.y) <= 1) return true;
      return false;
    }

    function exchange(list, idx1, idx2) {
      let tmp = list[idx1];
      list[idx1] = list[idx2];
      list[idx2] = tmp;
    }

    /** 
     * 对segment进行纠偏
     * 所有的segment的端点应该能要能够找到与其衔接的其他segment端点，称为邻居segment，如果没有，那就是trace异常，需要纠偏
     * 可能导致的情况有： 
     * 1）segment端点前或后3个点非单调变化，这种调整下顺序或许就可以顺利找到邻居segment
     * 2）循环打结的场景，这种情况其实有两个segment其端点非常接近，但并非紧挨着距离是1的邻居，导致不能成功配对。可以通过增加插值点来就解决
     */
    function adjustSpecialEndpoint(endpoints) {
      
      // 筛选出没有找到邻居的endpoint
      let specialEndpoints = endpoints.filter(p => {
        if (endpoints.find(pp => pp != p && isNeighbor(pp, p))) return false;
        return true;
      });

      self.debugInfo.spCount = specialEndpoints.length;

      console.warn(`需要纠偏的特殊点数：${specialEndpoints.length}`, specialEndpoints);

      if (specialEndpoints.length == 0) {
        console.log("无需纠偏");
        return;
      }

      console.log('开始进行单调纠偏');
      
      // 对这些没有邻居的endpoint对应的segment进行单调变化纠偏
      for (let sp of specialEndpoints) {
        let seg = sp.seg;
        if (seg.pt.length <= 2) continue;

        if (equal(sp, seg.endA)) {  // 看头部
          let dirx1 = sign(seg.pt[1].x - seg.pt[0].x);
          let dirx2 = sign(seg.pt[2].x - seg.pt[1].x);
          let diry1 = sign(seg.pt[1].y - seg.pt[0].y);
          let diry2 = sign(seg.pt[2].y - seg.pt[1].y);
          if (!isSameSign(dirx1, dirx2) || !isSameSign(diry1, diry2)) {
            exchange(seg.pt, 0, 1);
            seg.endA = { x: seg.pt[0].x, y: seg.pt[0].y };
            sp.x = seg.endA.x;
            sp.y = seg.endA.y;
          }
        } else if (equal(sp, seg.endB)) { // 看尾巴
          let n = seg.pt.length;
          let dirx1 = sign(seg.pt[n-2].x - seg.pt[n-3].x);
          let dirx2 = sign(seg.pt[n-1].x - seg.pt[n-2].x);
          let diry1 = sign(seg.pt[n-2].y - seg.pt[n-3].y);
          let diry2 = sign(seg.pt[n-1].y - seg.pt[n-2].y);
          if (!isSameSign(dirx1, dirx2) || !isSameSign(diry1, diry2)) {
            exchange(seg.pt, n - 1, n - 2);
            seg.endB = { x: seg.pt[n-1].x, y: seg.pt[n-1].y };
            sp.x = seg.endB.x;
            sp.y = seg.endB.y;
          }
        }
      }

      specialEndpoints = endpoints.filter(p => {
        if (endpoints.find(pp => pp != p && isNeighbor(pp, p))) return false;
        return true;
      });

      self.debugInfo.spCountAfterOrderAdjust = specialEndpoints.length;

      if (specialEndpoints.length > 0) {
        console.warn(`经过单调纠偏后，仍有 ${specialEndpoints.length} 个特殊点`, specialEndpoints);
      } else {
        console.log('单调纠偏后已不存在特殊点！');
        return;
      }

      // 对异常偏离值是2的两个endpoint进行纠偏
      let spA, spB, cp;
      do {
        spA = specialEndpoints.find(sp => !sp.done);
        if (!spA) break;

        spA.done = true;

        // 找到靠近spA距离是2的对端spB
        spB = specialEndpoints.find(sp => sp != spA && !sp.done && Math.abs(sp.x - spA.x) <= 2 && Math.abs(sp.y - spA.y) <= 2);
        if (!spB) continue;

        // 找到一个桥接点能够连接spA和spB的
        cp = spA.seg.pt.find(p => isNeighbor(p, spA) && isNeighbor(p, spB));
        if (cp) { // 在spA的segment中找到，则将cp添加到spB的segment中
          if (equal(spB.seg.endA, spB)) {
            spB.seg.pt.unshift({ x: cp.x, y: cp.y });
            spB.seg.endA = { x: cp.x, y: cp.y };
          } else if (equal(spB.seg.endB, spB)) {
            spB.seg.pt.push({ x: cp.x, y: cp.y });
            spB.seg.endB = { x: cp.x, y: cp.y };
          }
          spB.x = cp.x;
          spB.y = cp.y;
          spB.done = true;
          continue;
        }
        cp = spB.seg.pt.find(p => isNeighbor(p, spA) && isNeighbor(p, spB));
        if (cp) { // 在spB的segment中找到，则将cp添加到spA的segment中
          if (equal(spA.seg.endA, spA)) {
            spA.seg.pt.unshift({ x: cp.x, y: cp.y });
            spA.seg.endA = { x: cp.x, y: cp.y };
          } else if (equal(spA.seg.endB, spA)) {
            spA.seg.pt.push({ x: cp.x, y: cp.y });
            spA.seg.endB = { x: cp.x, y: cp.y };
          }
          spB.x = cp.x;
          spB.y = cp.y;
          spB.done = true;
          continue;
        }

      } while(true)


      specialEndpoints = endpoints.filter(p => {
        if (endpoints.find(pp => pp != p && isNeighbor(pp, p))) return false;
        return true;
      });

      self.debugInfo.spCountAfterInsertAdjust = specialEndpoints.length;

      if (specialEndpoints.length > 0) {
        console.warn(`经过插值纠偏后，仍有 ${specialEndpoints.length} 个特殊点`, specialEndpoints);
      } else {
        console.log('插值纠偏后已不存在特殊点！');
        return;
      }

    }

    // 递归寻邻近的endpoint，形成一个完整的group
    function findNeighborEndpoint(group, endpoints) {
      let groupEndpoints = group.endpoints;
      let ninGruopEndpoints = endpoints.filter(s => !s.ingroup);
      let foundEndpoint = null;

      for (let point of ninGruopEndpoints) {
        if (groupEndpoints.find(p => isNeighbor(p, point))) {
          foundEndpoint = point;
          break;
        }
      }
      if (foundEndpoint) {
        group.endpoints.push(foundEndpoint);
        foundEndpoint.ingroup = true;
        findNeighborEndpoint(group, endpoints);
      }

    }

    // 在一个endpoint group中选出组长
    function electGroupLeader(group) {

      let i, j, pureEps, filterEps, dist;

      pureEps = group.endpoints.filter((item, index, arr) => 
        arr.findIndex(e => e.x == item.x && e.y == item.y) == index);

      for (i = 0; i < pureEps.length; i++) {
        dist = 0;
        for (j = 0; j < pureEps.length; j++) {
          if (i == j)  continue;
          dist += ddist(pureEps[i], pureEps[j]);
        }
        pureEps[i].dist = dist;
      }
      pureEps.sort((a, b) => a.dist - b.dist);
      filterEps = pureEps.slice(0, 2);

      if(pureEps.length >= 2 && pureEps[0].dist == pureEps[1].dist) { // 头两个dist都相等，还要再决策下，谁出现的次数多就取谁
        filterEps.forEach(p => {
          let count = 0;
          for (i = 0; i < group.endpoints.length; i++) {
            if (equal(p, group.endpoints[i])) {
              count++;
            }
          }
          p.count = count;
        })
        filterEps.sort((a, b) => b.count - a.count);
      }

      group.leader = filterEps[0];

    }

    // 将相邻的endpoint合并成同一个
    function mergeEndpointIngroup(group) {

      let endpoint, seg, leader, old, neib;

      leader = group.leader;

      for (let i = 0; i < group.endpoints.length; i++) {
        endpoint = group.endpoints[i];
        seg = endpoint.seg;

        if (seg.pt.length <= 2) {  // 只有1个和2个点的segment已经没有存在的必要
          seg.invalid = true;
          continue;
        }

        if (equal(endpoint, leader)) {
          continue;
        }

        // if (equal(leader, seg.endA) || equal(leader, seg.endB)) {
        //   continue;
        // }

        if (equal(seg.endA, endpoint)) {
          old = seg.endA;
          if (isNeighbor(seg.endA, leader)) {
            seg.pt.unshift({ x: leader.x, y: leader.y });
            seg.endA = { x: leader.x, y: leader.y, adjusted: true, ox: old.x, oy: old.y };
          } else { // endpoint距离leader太远, 需要中间插值过渡过去
            do {
              neib = getNeighborToLeader(seg.endA);
              if (!neib) break;

              seg.pt.unshift({ x: neib.x, y: neib.y });
              seg.endA = { x: neib.x, y: neib.y, adjusted: true, ox: old.x, oy: old.y };

              if (neib == leader) break;

            } while (1)
          }

        } else if (equal(seg.endB, endpoint)) {
          old = seg.endB;
          if(isNeighbor(seg.endB, leader)) {
            seg.pt.push({ x: leader.x, y: leader.y });
            seg.endB = { x: leader.x, y: leader.y, adjusted: true, ox: old.x, oy: old.y };
          } else { // endpoint距离leader太远, 需要中间插值过渡过去
            do {
              neib = getNeighborToLeader(seg.endB);
              if (!neib) break;
        
              seg.pt.push({ x: neib.x, y: neib.y });
              seg.endB = { x: neib.x, y: neib.y, adjusted: true, ox: old.x, oy: old.y };

              if (neib == leader) break;

            } while (1)
          }
        }

      }

      function getNeighborToLeader(p: Point) {
        let fitneib;
        let neibs = [];
        for (let dp of group.endpoints) {
          if (!equal(p, dp) && isNeighbor(p, dp)) {
            neibs.push(dp);
          }
        }

        let dist, minP2L = 999;
        for (let neib of neibs) {
          dist = ddist(neib, leader);
          if ( dist < minP2L) {
            minP2L = dist;
            fitneib = neib; 
          }
        }

        return fitneib;

      }

    }

    let self = this;
    let segments = this.segments;
    let endpoints = [];
    let ninGroupPoint;
    let group;
    let count;

    // 先剔除无效孤点
    count = 0;
    let singleSegs = segments.filter(s => s.pt.length == 1);
    for (let ss of singleSegs) {
      let findseg = segments.find(s => s != ss && (equal(s.endA, ss.endA) || equal(s.endB, ss.endA)));
      if (findseg) {
        let idx = segments.findIndex(s => s == ss);
        segments.splice(idx, 1);
        count++;
      }
    }
    console.log(`delete single point segments. totoal: ${singleSegs.length}, delete: ${count}`);

    
    // 整理出所有的endpoint 
    segments.forEach(s => {
      endpoints.push({ x: s.endA.x, y: s.endA.y, seg: s });
      endpoints.push({ x: s.endB.x, y: s.endB.y, seg: s });
    })

    // 先对所有的endpoint做一轮检查，所有的endpoint至少有一个邻居， 如果一个endpoint找不到邻居，那么有可能需要调整对应segment的pt和端点信息
    // 这么做也是对trace算法的一个纠偏， 某些segment点集trace的结果首尾3个点可能不符合要求，x, y坐标变化没有做到单调增/减。 实测有发现这种情况
    adjustSpecialEndpoint(endpoints);


    // 然后对他们进行分组(相邻的endpoint分为一组)
    do {

      ninGroupPoint = endpoints.find(s => !s.ingroup);
      
      if (!ninGroupPoint) break;

      group = { endpoints: [ninGroupPoint] };

      ninGroupPoint.ingroup = true;
      
      findNeighborEndpoint(group, endpoints);

      electGroupLeader(group); 

      mergeEndpointIngroup(group);

    } while (true)

    // 调整完顶点后删除无效segment
    let count1 = this.segments.length;
    this.segments = segments.filter(s => !s.invalid);
    let count2 = this.segments.length;
    console.log(`顶点合并后删除 ${count1 - count2} 个无效segment`);
  }


  // segment组合成path
  private segmentsToPaths() {

    function equal(pt1: Point, pt2: Point) {
      return pt1.x == pt2.x && pt1.y == pt2.y;
    }

    // 判断两个点是否靠近
    function near(pt1: Point, pt2: Point) {
      let threshold = 5;
      let dx = Math.abs(pt1.x - pt2.x);
      let dy = Math.abs(pt1.y - pt2.y);

      // let dd = Math.sqrt(dx * dx + dy * dy);
      // if (dd < threshold) return true;
      // return false;

      if (dx <= threshold && dy <= threshold) return true;
      return false;
    }

    // 从一个segment出发按序找到一个完整的path
    function findPath(startSeg, areaSegs, areaId) {
      
      let path: Path = { segments: [], complete: false };
      let flag = 'B';  // 指示下一个是找endA还是endB
      let seg = startSeg;
      let nextseg;

      seg.inpath = true;
      path.segments.push(seg);
      path.s = { x: seg.endA.x, y: seg.endA.y };

      if (equal(startSeg.endA, startSeg.endB)) {  // 自封闭环，是独立path，不用再继续寻找了，直接返回
        path.complete = true;
        path.e = { x: seg.endB.x, y: seg.endB.y };
        return path;
      }

      do {

        if (flag == 'B') {
          // 找下一个与当前seg的endB端点衔接上的segment（注：排除自闭合的segment）
          nextseg = areaSegs.find(s => !s.inpath && !equal(s.endA, s.endB) 
            && (equal(s.endA, seg.endB) || equal(s.endB, seg.endB)));
          if (!nextseg) break;

          if (equal(seg.endB, nextseg.endA)) flag = 'B';
          else if (equal(seg.endB, nextseg.endB)) flag = 'A';
          seg = nextseg;
        } else if (flag == 'A') {
          // 找下一个与当前seg的endA端点衔接上的segment（注：排除自闭合的segment）
          nextseg = areaSegs.find(s => !s.inpath && !equal(s.endA, s.endB) 
            && (equal(s.endA, seg.endA) || equal(s.endB, seg.endA)));
          if (!nextseg) break;

          if (equal(seg.endA, nextseg.endA)) flag = 'B';
          else if (equal(seg.endA, nextseg.endB)) flag = 'A';
          seg = nextseg;
        }

        seg.inpath = true;
        path.segments.push(seg);

        if (equal(seg.endA, startSeg.endA) || equal(seg.endB, startSeg.endA)) {
          path.complete = true
          break;
        }

      } while (true)


      let endPoint;
      if (flag == 'A') {
        endPoint = path.segments[path.segments.length - 1].endA;
      } else if (flag == 'B') {
        endPoint = path.segments[path.segments.length - 1].endB;
      }
      path.e = { x: endPoint.x, y: endPoint.y };


      if (path.complete) return path;

      // 如果顺序走下来发现不能拼接成一个完整的path，那么尝试逆向寻找，让path尽可能完整
      // console.warn(`${areaId} path incomplete! try reverse find`, path);

      seg = startSeg;
      flag = 'A';

      do {

        if (flag == 'B') {
          // 找下一个与当前seg的endB端点衔接上的segment（注：排除自闭合的segment）
          nextseg = areaSegs.find(s => !s.inpath && !equal(s.endA, s.endB) 
            && (equal(s.endA, seg.endB) || equal(s.endB, seg.endB)));
          if (!nextseg) break;

          if (equal(seg.endB, nextseg.endA)) flag = 'B';
          else if (equal(seg.endB, nextseg.endB)) flag = 'A'
          seg = nextseg;
        } else if (flag == 'A') {
          // 找下一个与当前seg的endA端点衔接上的segment（注：排除自闭合的segment）
          nextseg = areaSegs.find(s => !s.inpath && !equal(s.endA, s.endB) 
            && (equal(s.endA, seg.endA) || equal(s.endB, seg.endA)));
          if (!nextseg) break;

          if (equal(seg.endA, nextseg.endA)) flag = 'B';
          else if (equal(seg.endA, nextseg.endB)) flag = 'A'
          seg = nextseg;
        }

        seg.inpath = true;
        path.segments.unshift(seg);

        if (equal(seg.endA, endPoint) || equal(seg.endB, endPoint)) {
          path.complete = true
          break;
        }

      } while (true)

      let startPoint;
      if (flag == 'A') {
        startPoint = path.segments[0].endA;
      } else if (flag == 'B') {
        startPoint = path.segments[0].endB;
      }
      path.s = { x: startPoint.x, y: startPoint.y };

      if (!path.complete) {
        console.warn(`${areaId} found a incomplete path`, path);
        self.debugInfo.totalIncompletePaths++;
      }

      return path;

    }

    // 强制合并不完整的path
    function tryMergePaths(area, pathlist) {
      let mergeflag;
      let incompletCount = 0;
      for (let path of pathlist) {
        if (!path.complete) {
          incompletCount++;
        }
      }
      if (incompletCount == 1) {
        console.log(`${area} incomplete path probably not a problem`, pathlist);
        self.debugInfo.doesntMatterIncompletePaths++;
      }
      if (incompletCount < 2) return;  // nothing to merge

      // start merge
      do {
        let path, path1 = null, path2 = null;
        for (let i = 0; i < pathlist.length; i++) {
          path = pathlist[i];
          if (!path.complete && !near(path.s, path.e)) {
            path1 = path; // find path1
            for (let j = i + 1; j < pathlist.length; j++) {
              path = pathlist[j];
              if (!path.complete && !near(path.s, path.e)) {
                if (canMerge(path1, path)) { // find path2
                  path2 = path;
                  break;
                }
              }
            }
          }
          if (path1 && path2) {
            break;
          }
        }

        if (path1 && path2) {
          let newpath = merge(path1, path2);
          pathlist.splice(pathlist.findIndex(p => p == path1), 1);
          pathlist.splice(pathlist.findIndex(p => p == path2), 1);
          pathlist.unshift(newpath);
          console.log("merge:", path1, path2, newpath, pathlist);
        } else {
          break;
        }

      } while(true)

      for (let path of pathlist) {
        if (!path.complete && equal(path.s, path.e)) {
          path.complete = true;
        }
      }
      for (let path of pathlist) {
        if (!path.complete) {
          self.debugInfo.realIncompletePaths++;
        }
      }

      function canMerge(path1, path2): boolean {
        if (near(path1.e, path2.s)) {
          mergeflag = '1e2s';
          return true;
          let seg1 = path1.segments[path1.segments.length - 1];
          let seg2 = path2.segments[0];
          if ((seg1.areaA == seg2.areaA && seg1.areaB == seg2.areaB)
            || (seg1.areaA == seg2.areaB && seg1.areaB == seg2.areaA)) {
            mergeflag = '1e2s';
            return true;
          }
        }
        if (near(path1.e, path2.e)) {
          mergeflag = '1e2e';
          return true;
          let seg1 = path1.segments[path1.segments.length - 1];
          let seg2 = path2.segments[path2.segments.length - 1];
          if ((seg1.areaA == seg2.areaA && seg1.areaB == seg2.areaB)
            || (seg1.areaA == seg2.areaB && seg1.areaB == seg2.areaA)) {
            mergeflag = '1e2e';
            return true;
          }
        }
        if (near(path1.s, path2.s)) {
          mergeflag = '1s2s';
          return true;
          let seg1 = path1.segments[0];
          let seg2 = path2.segments[0];
          if ((seg1.areaA == seg2.areaA && seg1.areaB == seg2.areaB)
            || (seg1.areaA == seg2.areaB && seg1.areaB == seg2.areaA)) {
            mergeflag = '1s2s';
            return true;
          }
        }
        if (near(path1.s, path2.e)) {
          mergeflag = '1s2e';
          return true;
          let seg1 = path1.segments[0];
          let seg2 = path2.segments[path2.segments.length - 1];
          if ((seg1.areaA == seg2.areaA && seg1.areaB == seg2.areaB)
            || (seg1.areaA == seg2.areaB && seg1.areaB == seg2.areaA)) {
            mergeflag = '1s2e';
            return true;
          }
        }
        return false;
      }

      function merge(path1, path2): Path {
        let newpath: Path = { segments: [], complete: false };

        if (mergeflag == '1e2s') {
          for (let i = 0; i < path1.segments.length; i++) {
            newpath.segments.push(path1.segments[i]);
          }
          for (let i = 0; i < path2.segments.length; i++) {
            newpath.segments.push(path2.segments[i]);
          }
          newpath.s = path1.s;
          newpath.e = path2.e;
        } else if (mergeflag == '1e2e') {
          for (let i = 0; i < path1.segments.length; i++) {
            newpath.segments.push(path1.segments[i]);
          }
          for (let i = path2.segments.length - 1; i >= 0; i--) {
            newpath.segments.push(path2.segments[i]);
          }
          newpath.s = path1.s;
          newpath.e = path2.s;
        } else if (mergeflag == '1s2s') {
          for (let i = path2.segments.length - 1; i >= 0; i--) {
            newpath.segments.push(path2.segments[i]);
          }
          for (let i = 0; i < path1.segments.length; i++) {
            newpath.segments.push(path1.segments[i]);
          }
          newpath.s = path2.e;
          newpath.e = path1.e;
        } else if (mergeflag == '1s2e') {
          for (let i = 0; i < path2.segments.length; i++) {
            newpath.segments.push(path2.segments[i]);
          }
          for (let i = 0; i < path1.segments.length; i++) {
            newpath.segments.push(path1.segments[i]);
          }
          newpath.s = path2.s;
          newpath.e = path1.e;
        }
        return newpath;
      }
    }


    // pathlist中的path是有顺序的，如大圆套小圆的场景，大圆path必须排在前面先画，然后moveTo，再画小圆
    function sortPathlist(pathlist) {

      if (pathlist.length <= 1) return;

      // 先把每个path的矩形框出来
      for (let path of pathlist) {
        let p = path.segments[0].pt[0];
        let l = p.x, r = p.x, t = p.y, b = p.y;
        for (let seg of path.segments) {
          seg.pt.forEach(p => {
            if (p.x < l) l = p.x;
            if (p.x > r) r = p.x;
            if (p.y < t) t = p.y;
            if (p.y > b) b = p.y;
          })
        }
        path.area = ( r - l ) * ( b - t ); // 矩形框面积
      }

      // 按矩形框大小排序, 从大到小降序
      pathlist.sort((a, b) => b.area - a.area);

      pathlist.forEach(p => delete p.area);

    }

    function removeIncompletePaths(pathMap: PathMap) {

      console.log("删除孤立非闭合路径");

      let keys = Object.keys(pathMap);
      let datas = [];
      for (let key of keys) {
        let pathlist = pathMap[key];
        for (let i = 0; i < pathlist.length; i++) {
          let path = pathlist[i];
          if (!path.complete && path.segments.length == 1 && path.segments[0].pt.length < 30) {
          // if (!path.complete && path.segments.length == 1) {
            datas.push({pathlist, path});
          }
        }
      }

      console.log(`incomplete paths count: ${datas.length}`);

      let data1, data2;
      let count = 0;
      for (let i = 0; i < datas.length; i++) {
        data1 = datas[i];
        if (data1.done) continue;

        for (let j = i + 1; j < datas.length; j++) {
          data2 = datas[j];
          if(data1.path.segments[0] == data2.path.segments[0]) {
            data1.pathlist.splice(data1.pathlist.findIndex(p => p == data1.path), 1);
            data2.pathlist.splice(data2.pathlist.findIndex(p => p == data2.path), 1);
            data1.done = true;
            data2.done = true;
            count += 2;
            console.log(`remove path `, data1.path);
            console.log(`remove path `, data2.path);
          }
        }
        
      }
      
      console.log(`删除孤立非闭合路径：${count}`);
      self.debugInfo.removeIncompletePaths = count;
    }


    let self = this;

    let areaSegHash = {};

    this.segments.forEach(seg => {
      if (seg.areaA) {
        if (!areaSegHash[seg.areaA]) {
          areaSegHash[seg.areaA] = new Set();
        }
        areaSegHash[seg.areaA].add(seg);
      };
      if (seg.areaB) {
        if (!areaSegHash[seg.areaB]) {
          areaSegHash[seg.areaB] = new Set();
        }
        areaSegHash[seg.areaB].add(seg);
      }
    })

    let areas = Object.keys(areaSegHash);
    // 将同一个area的segment组合成path，注：可能组成多个path
    for (let area of areas) {
      let pathlist = [];
      let areaSegs = Array.from(areaSegHash[area]) as [any];  // 属于该area的所有segment
      let startSeg, path;

      do {
        
        startSeg = areaSegs.find(s => !s.inpath);
        
        if (!startSeg) break;

        path = findPath(startSeg, areaSegs, area);

        pathlist.push(path);

      } while (true)

      areaSegs.forEach(seg => delete seg.inpath);

      sortPathlist(pathlist);

      tryMergePaths(area, pathlist);

      sortPathlist(pathlist);  // do sort again after merge paths

      this.pathMap[area] = pathlist;

    }

    // delete alone incomplete path
    // removeIncompletePaths(this.pathMap);

  }



  //////////////////////////////////////////////////////////////////////////////////////
  /////////////////////  以下是将segment曲线化的核心算法，参考potrace ////////////////////

  // path曲线化， 具体来说: 先将各个segment分别解析成多边形得到顶点， 然后path将各个segment的顶点汇聚起来，然后曲线化
  private curvedPaths(mode = 1) {

    function equal(pt1: Point, pt2: Point) {
      return pt1.x == pt2.x && pt1.y == pt2.y;
    }

    function ddist(p, q) {
      return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y));
    }

    function mod(a, n) {
      return a >= n ? a % n : a >= 0 ? a : n - 1 - (-1 - a) % n;
    }

    function sign(i) {
      return i > 0 ? 1 : i < 0 ? -1 : 0;
    }

    function xprod(p1, p2) {
      return p1.x * p2.y - p1.y * p2.x;
    }

    function cyclic(a, b, c) {
      if (a <= c) {
        return (a <= b && b < c);
      } else {
        return (a <= b || b < c);
      }
    }

    function Sum(x, y, xy, x2, y2) {
      this.x = x;
      this.y = y;
      this.xy = xy;
      this.x2 = x2;
      this.y2 = y2;
    }

    function quadform(Q, w) {
      let v = new Array(3), i, j, sum;

      v[0] = w.x;
      v[1] = w.y;
      v[2] = 1;
      sum = 0.0;

      for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
          sum += v[i] * Q.at(i, j) * v[j];
        }
      }
      return sum;
    }

    function Quad() {
      this.data = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    }

    Quad.prototype.at = function (x, y) {
      return this.data[x * 3 + y];
    };

    function interval(lambda, a, b) {
      let res = { x: 0, y: 0 };

      res.x = a.x + lambda * (b.x - a.x);
      res.y = a.y + lambda * (b.y - a.y);
      return res;
    }

    
    function dorth_infty(p0, p2) {
      var r = { x: 0, y: 0 };

      r.y = sign(p2.x - p0.x);
      r.x = -sign(p2.y - p0.y);

      return r;
    }

    function ddenom(p0, p2) {
      let r = dorth_infty(p0, p2);

      return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y);
    }

    function dpara(p0, p1, p2) {
      let x1, y1, x2, y2;

      x1 = p1.x - p0.x;
      y1 = p1.y - p0.y;
      x2 = p2.x - p0.x;
      y2 = p2.y - p0.y;

      return x1 * y2 - x2 * y1;
    }

    // 判断一个segment是否闭合
    function isClosed(seg) {
      if (seg.pt.length <= 2) return false;

      let pt = seg.pt;
      let n = seg.pt.length;

      if (Math.abs(pt[0].x - pt[n-1].x) <= 1 && Math.abs(pt[0].y - pt[n-1].y) <= 1) return true;
      return false;
    }

    function calcSums(seg) {
      let i, x, y;
      seg.sums = [];
      let s = seg.sums;
      s.push(new Sum(0, 0, 0, 0, 0));
      for (i = 0; i < seg.pt.length; i++) {
        x = seg.pt[i].x - seg.endA.x;
        y = seg.pt[i].y - seg.endA.y;
        s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y,
          s[i].x2 + x * x, s[i].y2 + y * y));
      }
    }

    function calcLon(seg) {

      let n = seg.pt.length, pt = seg.pt, dir,
        pivk = new Array(n),
        nc = new Array(n),
        ct = new Array(4);
      seg.lon = new Array(n);

      let isclosed = isClosed(seg);

      let constraint = [ { x: 0, y: 0 }, {x: 0, y: 0} ],
        cur = { x: 0, y: 0 },
        off = { x: 0, y: 0 },
        dk = { x: 0, y: 0 },
        foundk;

      let i, j, k1, a, b, c, d, k = 0;
      for (i = n - 1; i >= 0; i--) {
        if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) {
          k = i + 1;
        }
        if (k >= n) {
          k = 0;  // add by guoziyun. to fit unclosed path
        }
        nc[i] = k;
      }

      for (i = n - 1; i >= 0; i--) {
        ct[0] = ct[1] = ct[2] = ct[3] = 0;

        if (isclosed || i < n - 1)  {  // do this if path is closed or not the last point
          dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) +
          (pt[mod(i + 1, n)].y - pt[i].y)) / 2;
          ct[dir]++;
        }

        constraint[0].x = 0;
        constraint[0].y = 0;
        constraint[1].x = 0;
        constraint[1].y = 0;

        k = nc[i];
        k1 = i;
        while (1) {
          foundk = 0;
          dir = (3 + 3 * sign(pt[k].x - pt[k1].x) +
            sign(pt[k].y - pt[k1].y)) / 2;
          // modify by guoziyun
          if (dir == 0 || dir == 1 || dir == 2 || dir == 3) {
            ct[dir]++;
          } 

          if (ct[0] && ct[1] && ct[2] && ct[3]) {
            pivk[i] = k1;
            foundk = 1;
            break;
          }

          cur.x = pt[k].x - pt[i].x;
          cur.y = pt[k].y - pt[i].y;

          if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) {
            break;
          }

          if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) {

          } else {
            off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1);
            off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1);
            if (xprod(constraint[0], off) >= 0) {
              constraint[0].x = off.x;
              constraint[0].y = off.y;
            }
            off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1);
            off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1);
            if (xprod(constraint[1], off) <= 0) {
              constraint[1].x = off.x;
              constraint[1].y = off.y;
            }
          }
          k1 = k;
          k = nc[k1];
          if (!cyclic(k, i, k1)) {
            break;
          }
        }
        if (foundk === 0) {
          dk.x = sign(pt[k].x - pt[k1].x);
          dk.y = sign(pt[k].y - pt[k1].y);
          cur.x = pt[k1].x - pt[i].x;
          cur.y = pt[k1].y - pt[i].y;

          a = xprod(constraint[0], cur);
          b = xprod(constraint[0], dk);
          c = xprod(constraint[1], cur);
          d = xprod(constraint[1], dk);

          j = 10000000;
          if (b < 0) {
            j = Math.floor(a / -b);
          }
          if (d > 0) {
            j = Math.min(j, Math.floor(-c / d));
          }
          pivk[i] = mod(k1 + j, n);
        }
      }

      j = pivk[n - 1];
      seg.lon[n - 1] = j;
      for (i = n - 2; i >= 0; i--) {
        if (cyclic(i + 1, pivk[i], j)) {
          j = pivk[i];
        }
        seg.lon[i] = j;
      }

      for (i = n - 1; cyclic(mod(i + 1, n), j, seg.lon[i]); i--) {
        seg.lon[i] = j;
      }
    }


    function bestPolygon(seg) {

      function penalty3(seg, i, j) {

        let n = seg.pt.length, pt = seg.pt, sums = seg.sums;
        let x, y, xy, x2, y2,
          k, a, b, c, s,
          px, py, ex, ey,
          r = 0;
        if (j >= n) {
          j -= n;
          r = 1;
        }

        if (r === 0) {
          x = sums[j + 1].x - sums[i].x;
          y = sums[j + 1].y - sums[i].y;
          x2 = sums[j + 1].x2 - sums[i].x2;
          xy = sums[j + 1].xy - sums[i].xy;
          y2 = sums[j + 1].y2 - sums[i].y2;
          k = j + 1 - i;
        } else {
          x = sums[j + 1].x - sums[i].x + sums[n].x;
          y = sums[j + 1].y - sums[i].y + sums[n].y;
          x2 = sums[j + 1].x2 - sums[i].x2 + sums[n].x2;
          xy = sums[j + 1].xy - sums[i].xy + sums[n].xy;
          y2 = sums[j + 1].y2 - sums[i].y2 + sums[n].y2;
          k = j + 1 - i + n;
        }

        px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x;
        py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y;
        ey = (pt[j].x - pt[i].x);
        ex = -(pt[j].y - pt[i].y);

        a = ((x2 - 2 * x * px) / k + px * px);
        b = ((xy - x * py - y * px) / k + px * py);
        c = ((y2 - 2 * y * py) / k + py * py);

        s = ex * ex * a + 2 * ex * ey * b + ey * ey * c;

        return Math.sqrt(s);
      }

      let i, j, m, k,
        n = seg.pt.length,
        pen = new Array(n + 1),
        prev = new Array(n + 1),
        clip0 = new Array(n),
        clip1 = new Array(n + 1),
        seg0 = new Array(n + 1),
        seg1 = new Array(n + 1),
        thispen, best, c;

      for (i = 0; i < n; i++) {
        c = mod(seg.lon[mod(i - 1, n)] - 1, n);
        if (c == i) {
          c = mod(i + 1, n);
        }
        if (c < i) {
          clip0[i] = n;
        } else {
          clip0[i] = c;
        }
      }

      j = 1;
      for (i = 0; i < n; i++) {
        while (j <= clip0[i]) {
          clip1[j] = i;
          j++;
        }
      }

      i = 0;
      for (j = 0; i < n; j++) {
        seg0[j] = i;
        i = clip0[i];
      }
      seg0[j] = n;
      m = j;

      i = n;
      for (j = m; j > 0; j--) {
        seg1[j] = i;
        i = clip1[i];
      }
      seg1[0] = 0;

      pen[0] = 0;
      for (j = 1; j <= m; j++) {
        for (i = seg1[j]; i <= seg0[j]; i++) {
          best = -1;
          for (k = seg0[j - 1]; k >= clip1[i]; k--) {
            thispen = penalty3(seg, k, i) + pen[k];
            if (best < 0 || thispen < best) {
              prev[i] = k;
              best = thispen;
            }
          }
          pen[i] = best;
        }
      }
      seg.m = m;
      seg.po = new Array(m);

      for (i = n, j = m - 1; i > 0; j--) {
        i = prev[i];
        seg.po[j] = i;
      }

    }
    
    

    function adjustVertices(seg) {

      function pointslope(seg, i, j, ctr, dir) {

        let n = seg.pt.length, sums = seg.sums,
          x, y, x2, xy, y2,
          k, a, b, c, lambda2, l, r = 0;

        while (j >= n) {
          j -= n;
          r += 1;
        }
        while (i >= n) {
          i -= n;
          r -= 1;
        }
        while (j < 0) {
          j += n;
          r -= 1;
        }
        while (i < 0) {
          i += n;
          r += 1;
        }

        x = sums[j + 1].x - sums[i].x + r * sums[n].x;
        y = sums[j + 1].y - sums[i].y + r * sums[n].y;
        x2 = sums[j + 1].x2 - sums[i].x2 + r * sums[n].x2;
        xy = sums[j + 1].xy - sums[i].xy + r * sums[n].xy;
        y2 = sums[j + 1].y2 - sums[i].y2 + r * sums[n].y2;
        k = j + 1 - i + r * n;

        ctr.x = x / k;
        ctr.y = y / k;

        a = (x2 - x * x / k) / k;
        b = (xy - x * y / k) / k;
        c = (y2 - y * y / k) / k;

        lambda2 = (a + c + Math.sqrt((a - c) * (a - c) + 4 * b * b)) / 2;

        a -= lambda2;
        c -= lambda2;

        if (Math.abs(a) >= Math.abs(c)) {
          l = Math.sqrt(a * a + b * b);
          if (l !== 0) {
            dir.x = -b / l;
            dir.y = a / l;
          }
        } else {
          l = Math.sqrt(c * c + b * b);
          if (l !== 0) {
            dir.x = -c / l;
            dir.y = b / l;
          }
        }
        if (l === 0) {
          dir.x = dir.y = 0;
        }
      }

      let m = seg.m, po = seg.po, n = seg.pt.length, pt = seg.pt,
        x0 = seg.endA.x, y0 = seg.endA.y,
        ctr = new Array(m), dir = new Array(m),
        q = new Array(m),
        v = new Array(3), d, i, j, k, l,
        s = { x: 0, y: 0 };
      
      seg.curve = new Curve(m);

      for (i = 0; i < m; i++) {
        j = po[mod(i + 1, m)];
        j = mod(j - po[i], n) + po[i];
        ctr[i] = { x: 0, y: 0 };
        dir[i] = { x: 0, y: 0 };
        pointslope(seg, po[i], j, ctr[i], dir[i]);
      }

      for (i = 0; i < m; i++) {
        q[i] = new Quad();
        d = dir[i].x * dir[i].x + dir[i].y * dir[i].y;
        if (d === 0.0) {
          for (j = 0; j < 3; j++) {
            for (k = 0; k < 3; k++) {
              q[i].data[j * 3 + k] = 0;
            }
          }
        } else {
          v[0] = dir[i].y;
          v[1] = -dir[i].x;
          v[2] = - v[1] * ctr[i].y - v[0] * ctr[i].x;
          for (l = 0; l < 3; l++) {
            for (k = 0; k < 3; k++) {
              q[i].data[l * 3 + k] = v[l] * v[k] / d;
            }
          }
        }
      }

      var Q, w, dx, dy, det, min, cand, xmin, ymin, z;
      for (i = 0; i < m; i++) {
        Q = new Quad();
        w = { x: 0, y : 0 };

        s.x = pt[po[i]].x - x0;
        s.y = pt[po[i]].y - y0;

        j = mod(i - 1, m);

        for (l = 0; l < 3; l++) {
          for (k = 0; k < 3; k++) {
            Q.data[l * 3 + k] = q[j].at(l, k) + q[i].at(l, k);
          }
        }

        while (1) {

          det = Q.at(0, 0) * Q.at(1, 1) - Q.at(0, 1) * Q.at(1, 0);
          if (det !== 0.0) {
            w.x = (-Q.at(0, 2) * Q.at(1, 1) + Q.at(1, 2) * Q.at(0, 1)) / det;
            w.y = (Q.at(0, 2) * Q.at(1, 0) - Q.at(1, 2) * Q.at(0, 0)) / det;
            break;
          }

          if (Q.at(0, 0) > Q.at(1, 1)) {
            v[0] = -Q.at(0, 1);
            v[1] = Q.at(0, 0);
          } else if (Q.at(1, 1)) {
            v[0] = -Q.at(1, 1);
            v[1] = Q.at(1, 0);
          } else {
            v[0] = 1;
            v[1] = 0;
          }
          d = v[0] * v[0] + v[1] * v[1];
          v[2] = - v[1] * s.y - v[0] * s.x;
          for (l = 0; l < 3; l++) {
            for (k = 0; k < 3; k++) {
              Q.data[l * 3 + k] += v[l] * v[k] / d;
            }
          }
        }
        dx = Math.abs(w.x - s.x);
        dy = Math.abs(w.y - s.y);
        if (dx <= 0.5 && dy <= 0.5) {
          seg.curve.vertex[i] = { x: w.x + x0, y: w.y + y0 };
          continue;
        }

        min = quadform(Q, s);
        xmin = s.x;
        ymin = s.y;

        if (Q.at(0, 0) !== 0.0) {
          for (z = 0; z < 2; z++) {
            w.y = s.y - 0.5 + z;
            w.x = - (Q.at(0, 1) * w.y + Q.at(0, 2)) / Q.at(0, 0);
            dx = Math.abs(w.x - s.x);
            cand = quadform(Q, w);
            if (dx <= 0.5 && cand < min) {
              min = cand;
              xmin = w.x;
              ymin = w.y;
            }
          }
        }

        if (Q.at(1, 1) !== 0.0) {
          for (z = 0; z < 2; z++) {
            w.x = s.x - 0.5 + z;
            w.y = - (Q.at(1, 0) * w.x + Q.at(1, 2)) / Q.at(1, 1);
            dy = Math.abs(w.y - s.y);
            cand = quadform(Q, w);
            if (dy <= 0.5 && cand < min) {
              min = cand;
              xmin = w.x;
              ymin = w.y;
            }
          }
        }

        for (l = 0; l < 2; l++) {
          for (k = 0; k < 2; k++) {
            w.x = s.x - 0.5 + l;
            w.y = s.y - 0.5 + k;
            cand = quadform(Q, w);
            if (cand < min) {
              min = cand;
              xmin = w.x;
              ymin = w.y;
            }
          }
        }
        seg.curve.vertex[i] = { x: xmin + x0, y: ymin + y0 };
      }
    }

    function smooth(path, alphamax = 1) {
      let m = path.curve.vertex.length;
      let curve = new Curve(m); // curve 信息重新洗过
      curve.vertex = path.curve.vertex;
      path.curve = curve;

      let i, j, k, dd, denom, alpha,
        p2, p3, p4;

      for (i = 0; i < m; i++) {
        j = mod(i + 1, m);
        k = mod(i + 2, m);
        p4 = interval(1 / 2.0, curve.vertex[k], curve.vertex[j]);

        denom = ddenom(curve.vertex[i], curve.vertex[k]);
        if (denom !== 0.0) {
          dd = dpara(curve.vertex[i], curve.vertex[j], curve.vertex[k]) / denom;
          dd = Math.abs(dd);
          alpha = dd > 1 ? (1 - 1.0 / dd) : 0;
          alpha = alpha / 0.75;
        } else {
          alpha = 4 / 3.0;
        }

        if (alpha >= alphamax) {
          curve.tag[j] = "CORNER";
          curve.c[3 * j + 1] = curve.vertex[j];
          curve.c[3 * j + 2] = p4;
        } else {
          if (alpha < 0.55) {
            alpha = 0.55;
          } else if (alpha > 1) {
            alpha = 1;
          }
          p2 = interval(0.5 + 0.5 * alpha, curve.vertex[i], curve.vertex[j]);
          p3 = interval(0.5 + 0.5 * alpha, curve.vertex[k], curve.vertex[j]);
          curve.tag[j] = "CURVE";
          curve.c[3 * j + 0] = p2;
          curve.c[3 * j + 1] = p3;
          curve.c[3 * j + 2] = p4;
        }
      }

    }


    function reversePath(path) {
      // reverse smooth curve
      let n = path.curve.n, tag0 = path.curve.tag, c0 = path.curve.c;
      let tag = new Array(n), c = new Array(n * 3);
      let i, j;

      for (i = 0, j = n - 1; i < n; i++, j--) {
        tag[i] = tag0[j];
        if (tag[i] == 'CORNER') {
          c[i * 3 + 1] = c0[j * 3 + 1];
          c[i * 3 + 2] = c0[j * 3 - 1 < 0 ? n * 3 - 1 : j * 3 - 1];
        } else if (tag[i] == 'CURVE') {
          c[i * 3 + 0] = c0[j * 3 + 1];
          c[i * 3 + 1] = c0[j * 3 + 0];
          c[i * 3 + 2] = c0[j * 3 - 1 < 0 ? n * 3 - 1 : j * 3 - 1];
        }
      }

      path.curve.tag = tag;
      path.curve.c = c;

      // reverse line curve
      path.line.reverse();

    }

    // 对开放segment的曲线化
    function smoothSegment(seg, reverse = false, alphamax = 1) {

      let i, j, k, dd, denom, alpha,
      s, e,
      p0, pn, p2, p3, p4,
      m, curve, vertex, tag, c;

      m = seg.curve.vertex.length, curve = seg.curve;

      if (!reverse) {
        s = { x: seg.endA.x, y: seg.endA.y };
        e = { x: seg.endB.x, y: seg.endB.y };
        vertex = curve.vertex.map(e => e);
      } else {
        s = { x: seg.endB.x, y: seg.endB.y };
        e = { x: seg.endA.x, y: seg.endA.y };
        vertex = curve.vertex.map(e => e).reverse();
      }
      p0 = interval(1 / 2.0, vertex[0], s);
      pn = interval(1 / 2.0, e, vertex[m - 1]);
    
      tag = new Array(m + 2);
      c = new Array( (m + 2) * 3);

      vertex.unshift(s);
      vertex.push(e);

      for (i = 0; i < m; i++) {
        j = i + 1;
        k = i + 2;
        p4 = interval(1 / 2.0, vertex[k], vertex[j]);

        denom = ddenom(vertex[i], vertex[k]);
        if (denom !== 0.0) {
          dd = dpara(vertex[i], vertex[j], vertex[k]) / denom;
          dd = Math.abs(dd);
          alpha = dd > 1 ? (1 - 1.0 / dd) : 0;
          alpha = alpha / 0.75;
        } else {
          alpha = 4 / 3.0;
        }

        if (alpha >= alphamax) {
          tag[j] = "CORNER";
          c[3 * j + 1] = vertex[j];
          c[3 * j + 2] = p4;
        } else {
          if (alpha < 0.55) {
            alpha = 0.55;
          } else if (alpha > 1) {
            alpha = 1;
          }
          p2 = interval(0.5 + 0.5 * alpha, vertex[i], vertex[j]);
          p3 = interval(0.5 + 0.5 * alpha, vertex[k], vertex[j]);
          tag[j] = "CURVE";
          c[3 * j + 0] = p2;
          c[3 * j + 1] = p3;
          c[3 * j + 2] = p4;
        }
      }


      tag[0] = "CORNER";
      c[1] = vertex[0];
      c[2] = p0;

      tag[m + 1] = "CORNER";
      c[ (m + 1) * 3 + 1] = pn;
      c[ (m + 1) * 3 + 2] = vertex[vertex.length - 1];
      
      curve.n = tag.length;
      curve.tag = tag;
      curve.c = c;
    }

    // 构建path的拟合多边形，由path下属的segments的po合并而成
    function buildPathPolygon(path) {

      // 连线前每个segment把两个端点加上
      function fillEndpoint(segments) {
        for (let seg of segments) {
          if(!equal(seg.pt[seg.po[0]], seg.endA)) {
            seg.po.unshift(0);
          }
          if (!equal(seg.pt[seg.po[seg.po.length - 1]], seg.endB)) {
            seg.po.push(seg.pt.length - 1);
          }
        }
      }

      fillEndpoint(path.segments);

      let seg0 = path.segments[0];
      let po = seg0.po.map(idx => { return {x: seg0.pt[idx].x, y: seg0.pt[idx].y} });
      let next = seg0.endB;

      if (!equal(path.s, seg0.endA) && equal(path.s, seg0.endB)) {
        po.reverse();
        next = seg0.endA;
      }

      for (let i = 1; i < path.segments.length; i++) {
        let seg = path.segments[i];
        let ppo = seg.po.map(idx => { return {x: seg.pt[idx].x, y: seg.pt[idx].y} });
        if (equal(next, seg.endA)) {  // 正序
          if (equal(ppo[0], po[po.length - 1])) {
            po.pop();
          }
          po = po.concat(ppo);
          next = seg.endB;
        } else if (equal(next, seg.endB)){ // 反序，需要reverse
          ppo.reverse();
          if (equal(ppo[0], po[po.length - 1])) {
            po.pop();
          }
          po = po.concat(ppo);
          next = seg.endA;
        } else {  // 以上都不是，说明是断连强行merge拼接起来的，需要进一步判断
          let da = ddist(next, seg.endA);
          let db = ddist(next, seg.endB);
          if (da <= db) {
            if (equal(ppo[0], po[po.length - 1])) {
              po.pop();
            }
            po = po.concat(ppo);
            next = seg.endB;
      } else {
            ppo.reverse();
            if (equal(ppo[0], po[po.length - 1])) {
              po.pop();
        }
            po = po.concat(ppo);
            next = seg.endA;
          }
        }
      }

      if (equal(po[0], po[po.length - 1])) {
        po.pop()
      }

      path.polygon = new Polygon(po);
      path.sign = path.polygon.dir();
      // if (path.polygon.isIntersect()) {
      //   self.debugInfo.selfIntersectCount++;
      // }
      for (let p of po) {
        let idx = Math.round(p.y) * self.width + Math.round(p.x);
        self.poSet.add(idx);
      }

    }

    function isPathValid(path: Path) {
      if (!path.polygon.isValid() || path.sign == 0) {
        return false;
      } else {
        return true;
      }
      }

    // 连线方式拟合path
    function linePath(path) {
      path.line = path.polygon.po.slice();
    }

    // bezier曲线化path
    function curvePath(path, alphamax) {

      function mergeSegmentCurves(path) {
        let segments = path.segments;
        let n = 0;
        for (let seg of segments) {
          n += seg.curve.n;
        }
        path.curve = new Curve(n);
  
        let index = 0;
  
        // 合并tag信息
        for (let seg of segments) {
          for (let i = 0; i < seg.curve.n; i++) {
            path.curve.tag[i + index] = seg.curve.tag[i];
          }
          index += seg.curve.n;
        }
  
        // 合并c信息
        index = 0;
        for (let seg of segments) {
          for (let i = 0; i < 3 * seg.curve.n; i++) {
            path.curve.c[i + index] = seg.curve.c[i];
          }
          index += seg.curve.n * 3;
        }
  
      }


      // 先对path下的所有segment分别进行曲线化
      if (path.segments.length == 1) {
        if (path.complete) {
          smooth(path.segments[0], alphamax);
        } else {
          smoothSegment(path.segments[0], false, alphamax);
        }
      } else {
        let startend = path.s;
        for (let seg of path.segments) {
          if (equal(startend, seg.endA)) {  // 正序
            smoothSegment(seg, false, alphamax);
            startend = seg.endB;
          } else if (equal(startend, seg.endB)){ // 反序，需要reverse
            smoothSegment(seg, true, alphamax);
            startend = seg.endA;
          } else {  // 以上都不是，说明是断连强行merge拼接起来的，需要进一步判断
            let da = ddist(startend, seg.endA);
            let db = ddist(startend, seg.endB);
            if (da <= db) {
              smoothSegment(seg, false, alphamax);
              startend = seg.endB;
            } else {
              smoothSegment(seg, true, alphamax);
              startend = seg.endA;
            }
          }
        }
      }
      // 然后merge一个统一结果到path
      mergeSegmentCurves(path);

    }

    // 调整方向
    function doReverse(pathlist: Array<Path>) {
      if (!pathlist || pathlist.length <= 0) {
        console.warn("pathlist is empty!");
        return;
      }

      let path0 = pathlist[0];
      if (path0.sign == -1) { // 第一个强制为逆时针
        reversePath(path0);
        path0.sign = 1;
        // console.log('reverse path', path0, pathlist);
      }

      let n = pathlist.length;
      let i, j;

      for (i = 1; i < n; i++) {
        for (j = i - 1; j >= 0; j--) {
          if (pathlist[j].polygon.contains(pathlist[i].polygon)) { // find a parent path contain this path
            break;
          }
        }
        if (j >= 0) { // 找到，包含它的parent，如果sign相等，则reverse
          if (pathlist[i].sign == pathlist[j].sign) {
            reversePath(pathlist[i]);
            pathlist[i].sign = pathlist[i].sign == 1 ? -1 : 1;
            // console.log('reverse path', pathlist[i], pathlist);
          }
        } else { // 没找到包含它的parent，说明是独立path，强制为逆时针
          if (pathlist[i].sign == -1) {
            reversePath(pathlist[i]);
            pathlist[i].sign = 1;
            // console.log('reverse path', pathlist[i], pathlist);
          }
        }
      }
    }


    let self = this;

    for (let seg of this.segments) {
      calcSums(seg);
      calcLon(seg);
      bestPolygon(seg);  // 得到po，即多边形的顶点，依然是整形坐标的点，即原pt里的点的下标
      adjustVertices(seg);  // 对顶点进行微调，调整后的顶点放在curve.vertex数据结构中，变成小数
    }

    let keys = Object.keys(this.pathMap);
    for (let key of keys) {
      let pathlist = this.pathMap[key];
      for (let i = 0; i < pathlist.length; i++) {
        let path = pathlist[i];
        buildPathPolygon(path);
        if (!isPathValid(path)) {
          console.warn(`found a invalid path, po.length=${path.polygon.po.length} sign=${path.sign}, remove!`, path);
          pathlist.splice(i, 1);
          i--;
          this.debugInfo.remvoeInvalidPaths++;
          continue;
        }
        linePath(path);
        curvePath(path, this.config.alphamax);
      }
      doReverse(pathlist);
    }
  }
  
  
  getSVG(area: number, opt_type: string = null, pathOnly : boolean = false, color: string = 'black', mode: string = 'curve') {

    function path(curve) {

      function bezier(i) {
        let b = 'C' + parseFloat((curve.c[i * 3 + 0].x).toFixed(3)) + ' ' +
          parseFloat((curve.c[i * 3 + 0].y).toFixed(3)) + ',';
        b += parseFloat((curve.c[i * 3 + 1].x).toFixed(3)) + ' ' +
          parseFloat((curve.c[i * 3 + 1].y).toFixed(3)) + ',';
        b += parseFloat((curve.c[i * 3 + 2].x).toFixed(3)) + ' ' +
          parseFloat((curve.c[i * 3 + 2].y).toFixed(3)) + '';
        return b;
      }

      function segment(i) {
        let s = 'L' + parseFloat((curve.c[i * 3 + 1].x).toFixed(3)) + ' ' +
          parseFloat((curve.c[i * 3 + 1].y).toFixed(3)) + ' ';
        s += parseFloat((curve.c[i * 3 + 2].x).toFixed(3)) + ' ' +
          parseFloat((curve.c[i * 3 + 2].y).toFixed(3)) + '';
        return s;
      }

      let n = curve.n, i;
      let p = 'M' + (curve.c[(n - 1) * 3 + 2].x).toFixed(3) + ' ' + (curve.c[(n - 1) * 3 + 2].y).toFixed(3) + '';
      for (i = 0; i < n; i++) {
        if (curve.tag[i] === "CURVE") {
          p += bezier(i);
        } else if (curve.tag[i] === "CORNER") {
          p += segment(i);
        }
      }
      return p;

    }

    function line(po) {
      let p = 'M' + parseFloat(po[0].x.toFixed(3)) + ' ' + parseFloat(po[0].y.toFixed(3)) + 'L';
      for (let i = 1; i < po.length; i++) {
        p += parseFloat(po[i].x.toFixed(3)) + ' ' + parseFloat(po[i].y.toFixed(3)) + ' ';
      }
      p += 'Z'
      return p;
    }

    
    let w = this.width, h = this.height;
    let pathlist = this.pathMap[area];
    let len = pathlist.length;
    let c, i, strokec, fillc, fillrule;

    let pathStr = '';
    if (opt_type === "curve") {
      strokec = color ||  "black";
      fillc = "none";
      fillrule = '';
    } else {
      strokec = "none";
      // strokec = color || "none";
      fillc = color || "black";
      fillrule = ' fill-rule="evenodd"';
    }

    pathStr += '<path stroke="' + strokec + '" fill="' + fillc + '"' + fillrule;

    pathStr += ' d="';

    if (mode == 'line') {
      for (let p of pathlist) {
        pathStr += line(p.line);
      }
    } else {
      for (let p of pathlist) {
        c = p.curve;
        if (!c || !c.n ) {
          console.warn("invalid curve ", pathlist[i]);
          continue;
        }
        pathStr += path(c);
      }
    }
    

    pathStr += '"/>';


    let svg = `<svg id="svg" version="1.1" width="${w}" height="${h}" xmlns="http://www.w3.org/2000/svg">${pathStr}</svg>`;

    return pathOnly ? pathStr : svg;

  }


  getDebugInfo() {
    return this.debugInfo;
  }


}