import { validateOrder } from "./color-order";
import FillArea from "./fillarea";
import { AreaMap, Centers, ColorMap } from "./interfaces";
import { Bitmap, Potrace } from "./potrace";
import Utils from "./utils";


export default class SVGGenerator {
  mapData: ImageData;
  mapPixels: Uint32Array;
  pageData: ImageData;
  pagePixels: Uint32Array;
  orgData: ImageData;
  orgPixels: Uint32Array;

  areaMap: AreaMap;

  areaCanvas: HTMLCanvasElement;
  areaCtx: CanvasRenderingContext2D;
  colorBlack: number;

  public progress : number;
  public done : boolean = false;

  svg : string;
  paths : string[] = [];

  constructor(private map: HTMLImageElement, private page: HTMLImageElement, private org: HTMLImageElement,
    private colorMap: ColorMap, private centers: Centers, private colorOrder: any) {
    
    this.pageData = Utils.getImageData(this.page);
    this.pagePixels = new Uint32Array(this.pageData.data.buffer);
    this.mapData = Utils.getImageData(this.map);
    this.mapPixels = new Uint32Array(this.mapData.data.buffer);
    this.areaMap = SVGGenerator.createAreaMap(this.mapPixels, this.map.width);

    this.areaCanvas = Utils.createCanvas(this.map.width, this.map.height);
    this.areaCtx = this.areaCanvas.getContext('2d');

    this.colorBlack = Utils.getColorInteger("#000000");

  }

  async process(flag: number = 0) : Promise<Blob> {

    let output = [];
    output.push(`<svg id="svg" version="1.1" width="${this.map.width}" height="${this.map.height}" viewBox="0,0,${this.map.width},${this.map.height}" xmlns="http://www.w3.org/2000/svg">`);

    output.push(`\t<g id="areas">`);
    

    let colorMap = this.colorMap
    let colorOrder = this.colorOrder;
    let colorMapKeys = Object.keys(colorMap);
    // 判断colorOrder是否已经涵盖了colorMap里出现的颜色, 如果涵盖则采用order, 否则不采用
    if (validateOrder(colorMap, colorOrder)) {  // 根据colorOrder 给colorMap做个排序, 这个顺序将与生成的svg息息相关
      colorMapKeys.sort((a, b) => {
        let aidx = colorOrder.indexOf(colorMap[a].color);
        let bidx = colorOrder.indexOf(colorMap[b].color);
        return aidx - bidx;
      });
    }

    let color: string;
    for (let i = 0; i < colorMapKeys.length; i++) {
      let area = this.areaMap[colorMapKeys[i]];
      if (!area || !this.centers || !this.centers[area.color]) continue;
      color = Utils.colorToHex(this.colorMap[area.color].cssColor);
      let bitmap = this.getExpandAreaBitmap(area, 1);
      let path = this.getAreaPath2(area, color, bitmap);
      this.paths.push(path);
      output.push(`\t\t${path}`);
      console.log(i / colorMapKeys.length, path);
      this.progress = i / colorMapKeys.length;
      await Utils.delay(1);
    }
    
    if (flag  == 1) { // 对于未涂色的区块，也生成相应的area path， fill='none'
      let unColoredAreas = Object.keys(this.areaMap).filter(a => { return !this.colorMap[a]});
      for (let i = 0; i < unColoredAreas.length; i++) {
        let area = this.areaMap[unColoredAreas[i]];
        if (!this.centers || !this.centers[area.color]) continue;
        let path = this.getAreaPath2(area);
        this.paths.push(path);
        output.push(`\t\t${path}`);
      }
    }

    output.push(`\t</g>`)


    output.push(`\t<g id="outline">`)

    let p =   new Potrace(this.org || this.page);
    let path = p.getSVG(1, null, true, "#000000");
    output.push(`\t\t${path}`);
    output.push(`\t</g>`)


    output.push(`</svg>`);

    let svg = output.join('\n');
    this.svg = svg;

    this.done = true;

    console.log('svg:', svg);
    return new Blob([svg], {type : "image/svg+xml"});
  }

  private async getAreaPath(area: FillArea): Promise<string> {
    let svg = '';
    let width = this.map.width;
    let height = this.map.height;


    this.areaCtx.clearRect(0, 0, width, height);
    let imageData = this.areaCtx.getImageData(0, 0, width, height);
    let pixels = new Uint32Array(imageData.data.buffer);

    let ai, aw, i, x, y;
    aw = area.width();
    let total = area.width() * area.height();
    for (ai = 0; ai < total; ai++) {
      x = area.left + ai % aw;
      y = area.top + Math.floor(ai / aw);
      i = y * width + x;
      if (this.mapPixels[i] == area.color) {
        pixels[i] = this.colorBlack;
      }
    }
    this.areaCtx.putImageData(imageData, 0, 0);
    let blob = await this.canvasToBlob(this.areaCanvas);
    let p = await Potrace.fromFile(blob);
    let color = Utils.colorToHex(this.colorMap[area.color].cssColor);
    svg = p.getSVG(1, null, true, color);

    //center
    let center =  this.centers[area.color];
    let id = `${area.color}`;
    let c = `${center.x},${center.y},${center.radius.toFixed(5)}`;
    svg = svg.replace('path', `path id="${id}" c="${c}"`);

    return svg;
  }


  private getAreaPath2(area: FillArea, color: string = 'none', bitmap?: Bitmap): string {
    let svg = '';
    let width = this.map.width;
    let height = this.map.height;

    if (!bitmap) {
      bitmap = new Bitmap(width, height);
      // 生成potrace所需的bitmap
      let ai, aw, i, x, y;
      aw = area.width();
      let total = area.width() * area.height();
      for (ai = 0; ai < total; ai++) {
        x = area.left + ai % aw;
        y = area.top + Math.floor(ai / aw);
        i = y * width + x;
        if (this.mapPixels[i] == area.color) {
          bitmap.data[i] = 1;
        }
      }
    }

    let p = new Potrace(bitmap);
    svg = p.getSVG(1, null, true, color);

    //center
    let center =  this.centers[area.color];
    let id = `${area.color}`;
    let c = `${center.x},${center.y},${center.radius.toFixed(5)}`;
    svg = svg.replace('path', `path id="${id}" c="${c}"`);

    return svg;
  }

  /**
   * 扩大area, 返回相应的bitmap
   * @param area 原area
   * @param borderWidth 扩大的宽度
   * @returns 直接返回bitmap
   */
  private getExpandAreaBitmap(area: FillArea, borderWidth: number): Bitmap {
    let ai, ax, ay, aw, ah, i, j, x, y, p, px, py, index, idx, width, height;
    aw = area.width();
    ah = area.height();
    width = this.map.width;
    height = this.map.height;
    let bitmap = new Bitmap(width, height);
    
    // 直接生成potrace所需的bitmap
    let total = area.width() * area.height();
    for (ai = 0; ai < total; ai++) {
      x = area.left + ai % aw;
      y = area.top + Math.floor(ai / aw);
      i = y * width + x;
      if (this.mapPixels[i] == area.color) {
        bitmap.data[i] = 1;
      }
    }

    let areaPixels = new Uint32Array(aw * ah);

    for (ai = 0; ai < areaPixels.length; ai++) {
      ax = ai % aw;
      ay = (ai - ax) / aw;
      x = area.left + ax;
      y = area.top + ay;
      i = y * width + x;
      if (this.mapPixels[i] == area.color) {
        areaPixels[ai] = this.mapPixels[i];
      } else {
        areaPixels[ai] = 0;
      }
    }

    //find contours
    let flag, pixel;
    let contours = [];
    for (ax = 0; ax < aw; ax++) {
      flag = 0;
      for (ay = 0; ay < ah; ay++) {
        ai = ay * aw + ax;
        pixel = areaPixels[ai];
        if (!flag && pixel) {
          flag = 1;
          contours.push(ai);
        } else if (flag && !pixel) {
          flag = 0;
          contours.push(ai - aw);
        } else if (flag && pixel) continue;
        else if (!flag && !pixel) continue;
      }
      if (flag) {
        //last point
        contours.push(ai);
      }
    }

    for (ay = 0; ay < ah; ay++) {
      flag = 0;
      for (ax = 0; ax < aw; ax++) {
        ai = ay * aw + ax;
        pixel = areaPixels[ai];
        if (!flag && pixel) {
          flag = 1;
          contours.push(ai);
        } else if (flag && !pixel) {
          flag = 0;
          contours.push(ai - 1);
        } else if (flag && pixel) continue;
        else if (!flag && !pixel) continue;
      }
      if (flag) {
        //last point
        contours.push(ai);
      }
    }

    let neighbours = this.getNearestNeighbours(borderWidth);
    let neighbourSet: Set<number> = new Set();
    for(i = 0; i < contours.length; i++) {
      ai = contours[i];
      ax = ai % aw;
      ay = (ai - ax) / aw;
      x = area.left + ax;
      y = area.top + ay;
      index = y * width + x;
      idx = index * 4 + 3;
      if (this.pagePixels[index] 
        && this.pageData.data[idx] == 255) continue; // 如果该点实打实的线条，则不渗透
        
      for (j = 0; j < neighbours.length; j++) {
        p = neighbours[j];
        px = x + p.x;
        py = y + p.y;
        index = py * width + px;
        idx = index * 4 + 3;
        if (px > 0 && px < width && py > 0 && py < height
          && this.mapPixels[index] != area.color) {
              neighbourSet.add(index);
        }
      }
    }

    // expand bitmap
    for (let value of neighbourSet) {
      bitmap.data[value] = 1;
    }

    return bitmap;
  }

    /**
 * Get the nearest N neighbours
 * and sort by distance.
 * @param n integer >=1
 */
private getNearestNeighbours(n) {
  var distArr = [];
  for (var i = (-1) * n; i <= n; i++) {
    for (var j = (-1) * n; j <= n; j++) {
      if (i == 0 && j == 0) {
        // exclued self
        continue;
      }
      var distance = Math.pow(i, 2) + Math.pow(j, 2);
      distArr.push({ x: i, y: j, dist: distance });
    }
  }

  distArr.sort(function (a, b) { return a.dist - b.dist; });

  return distArr;
}

  private canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
    return new Promise((done, reject) => {
      canvas.toBlob((blob) => {
        done(blob);
      }, 'image/png');
    })
  }


  /**
   * Create area map from map pixels.
   */
  public static createAreaMap(pixels: Uint32Array, width: number): AreaMap {
    let areaMap = {};
    let i: number, x: number, y: number, color: number;
    for (i = 0; i < pixels.length; i++) {
      color = pixels[i];
      if (color == 0) continue;
      x = i % width;
      y = Math.floor(i / width);
      if (areaMap[color]) {
        areaMap[color].addPoint(x, y);
      } else {
        areaMap[color] = new FillArea(color, x, y);
      }
    }
    return areaMap;
  }


}