import Repeater from '../common/repeater';
import Utils from '../common/utils';
import Layer from '../core/layer';
import Rect from '../core/rect';
import Tool from '../core/tool';
import tool from '../core/tool';
import FillArea from '../common/fillarea';
import FloodFill2 from '../common/floodfill2';
import MapBrushTool from './map-brush-tool';
import MapEditTool from './map-edit-tool';
import MapMergeTool from './map-merge-tool';
import MapRecoverTool from './map-recover-tool';
import MapSplitAreaTool from './map-split-area-tool';
import MapPencilTool from './map-pencil-tool';

export default class MapEditLayer extends Layer {


  page: HTMLImageElement;
  map: HTMLImageElement;
  scale: number;
  mapData: ImageData;
  mapPixels: Uint32Array;
  originalMap: HTMLImageElement;
  originalMapPixels: Uint32Array;
  mapCanvas: HTMLCanvasElement;
  mapCtx: CanvasRenderingContext2D;
  undoList: any[];
  redoList: any[];
  saved: boolean = true;
  areaMap: {};
  brushBackup: boolean;
  brushUpdate: boolean;
  brushStartArea: any;
  brushLastPoint: any;
  newMap: any;
  newMapPixels: Uint32Array;

  raw: HTMLImageElement;


  override get defaultToolKey(): string { return 'map-edit-merge'; }

  private _showPage: boolean = true;
  public get showPage(): boolean {
    return this._showPage;
  }
  public set showPage(value: boolean) {
    this._showPage = value;
    this.invalidate();
  }

  /**
   * @param page original image.
   * @param map map image.
   * @param output  output image.
   */
  constructor(page: HTMLImageElement, map: HTMLImageElement, raw?: HTMLImageElement) {
    super(map.width, map.height);
    //console.log('init map editor layer....', original, map)

    this.addTool(new MapBrushTool(this));
    this.addTool(new MapMergeTool(this));
    this.addTool(new MapRecoverTool(this));
    this.addTool(new MapSplitAreaTool(this));
    this.addTool(new MapPencilTool(this));

    this.raw = raw;
    this.page = page;
    this.map = map;
    this.originalMap = map;

    this.init();
  }


  init() {

    this.scale = this.map.width / this.page.width;

    console.log('FillerLayer@width=', this.width, ' height=', this.height,
      ' scale=', this.scale);

    this.mapData = Utils.getImageData(this.map);
    this.mapPixels = new Uint32Array(this.mapData.data.buffer);


    let originalMapData = Utils.getImageData(this.originalMap);
    this.originalMapPixels = new Uint32Array(originalMapData.data.buffer);

    this.mapCanvas = Utils.createCanvas(this.width, this.height);
    this.mapCtx = this.mapCanvas.getContext('2d');
    this.mapCtx.putImageData(this.mapData, 0, 0);

    this.createAreaMap();

    this.undoList = [];
    this.redoList = [];

    this.invalidate();
  }

  update(page: HTMLImageElement, map: HTMLImageElement, raw?: HTMLImageElement) {
    this.page = page || this.page;
    this.map = map || this.map;
    this.raw = raw || this.raw;
    this.originalMap = this.map;
    this.saved = false;
    //只有更新了map图才需要重新初始化,
    //Fix map修改丢失的问题
    if (map) {
      this.init();
    }

    if (page) {
      //Fix 线稿更新后 splitArea不生效
      this.newMap = null;
    }
  }



  /**
   * Translate point.
   * Because our cordinate system is based on map, so
   * we don't need to translate.
   */
  translate(p) {
    return p;
    /*
    let pp = {};
    pp.x = p.x * this.scale;
    pp.y = p.y * this.scale;
    return pp;
    */
  }

  /**
   * redraw filler layer.
   */
  override invalidate() {
    this.updateAreaList();
    this.trigger('invalidate');
  }

  triggerMapUpdate() {
    this.saved = false;
    this.trigger('map-update');
    this.invalidate();
  }

  setSaved() {
    this.saved = true;
  }

  isSaved() {
    return this.saved;
  }

  /**
   * Create area map from map pixels.
   */
  createAreaMap() {
    let width = this.width;
    let height = this.height;
    let floodArr = this.mapPixels;
    let areaMap = {};
    let i, x, y, color;
    for (i = 0; i < floodArr.length; i++) {
      color = floodArr[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);
      }
    }
    this.areaMap = areaMap;
  }

  getAreaMap() {
    return this.areaMap;
  }

  /**
   * Get area by x, y
   */
  getArea(x, y) {
    x = parseInt(x);
    y = parseInt(y);
    if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
      return null;
    }
    let color = this.mapPixels[y * this.width + x];
    return this.areaMap[color];
  }

  /**
   * Get area by color(areaIndex)
   */
  getAreaByColor(color) {
    return this.areaMap[color];
  }

  toggleOriginal() {
    this.showPage = !this.showPage;
    this.invalidate();
  }

  toggleSmoothing() {
    this.smoothing = !this.smoothing;
    console.log('toggleSmoothing', this.smoothing)
    this.invalidate();
  }

  /**
   * Draw this layer.
   * @Override Layer#draw(ctx)
   */
  override draw(ctx) {
    //ctx.mozImageSmoothingEnabled = this.smoothing;
    //ctx.webkitImageSmoothingEnabled = this.smoothing;
    //ctx.msImageSmoothingEnabled = this.smoothing;
    ctx.imageSmoothingEnabled = this.smoothing;

    // draw map

    //ctx.drawImage(this.map, 0, 0, this.map.width,
    //  this.map.height, 0, 0, this.width, this.height);

    ctx.drawImage(this.mapCanvas, 0, 0, this.mapCanvas.width,
      this.mapCanvas.height, 0, 0, this.width, this.height);

    if (this.blinkCanvas) {
      ctx.drawImage(this.blinkCanvas, 0, 0);
    }

    if (this.showPage) {
      if (this.showSvg && this.raw) {
        ctx.drawImage(this.raw, 0, 0, this.page.width,
          this.page.height, 0, 0, this.width, this.height);
      } else {
        ctx.drawImage(this.page, 0, 0, this.page.width,
          this.page.height, 0, 0, this.width, this.height);
      }
    }

    if (this.pencilCanvas && this.isPencilDrawing) {
      ctx.drawImage(this.pencilCanvas, 0, 0);
    }

  }


  getAreaCount() {
    return Object.keys(this.areaMap).length;
  }


  sortedAreaList: FillArea[] = [];
  updateAreaList() {
    this.sortedAreaList = Object.keys(this.areaMap).map(key => this.areaMap[key])
      .sort((a, b) => {
        return a.count - b.count;
      })
  }

  brushStart(p) {
    this.brushBackup = false;
    this.brushUpdate = false;
    this.brushStartArea = this.getArea(p.x, p.y);
    this.brushLastPoint = { x: parseInt(p.x), y: parseInt(p.y) };
  }

  brushMove(p, linewidth: number) {
    if (!this.brushStartArea) return;
    //let area = this.getArea(point.x, point.y);
    let x = parseInt(p.x);
    let y = parseInt(p.y);

    let points = this.brushGetPionts({ x, y }, linewidth);
    let startArea = this.brushStartArea;
    let change = false;

    for (let pp of points) {
      x = pp.x;
      y = pp.y;
      if (x < 0 || x >= this.width || y < 0 || y >= this.height) continue;
      let i = y * this.width + x;
      console.log("x=" + x + " y=" + y + " i=" + i);
      if (this.mapPixels[i] != startArea.color) {
        if (!this.brushBackup) {
          this.backup(); //backup on the first change.
          this.brushBackup = true;
        }
        this.mapPixels[i] = startArea.color;
        this.mapCtx.putImageData(this.mapData, 0, 0);
        change = true;
      }
    }
    if (change) {
      this.invalidate();
      this.brushUpdate = true;
    }


    // 原代码， 只支持一个点的，即线宽是1的， 采用上面的代码支持动态线宽
    // if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;

    // let i = y * this.width + x;
    // console.log("x=" + x + " y=" + y + " i=" + i);
    // let startArea = this.brushStartArea;
    // if (this.mapPixels[i] != startArea.color) {
    //   if (!this.brushBackup) {
    //     this.backup(); //backup on the first change.
    //     this.brushBackup = true;
    //   }
    //   this.mapPixels[i] = startArea.color;
    //   this.mapCtx.putImageData(this.mapData, 0, 0)
    //   this.invalidate();
    //   this.brushUpdate = true;
    // }

    this.brushLastPoint = { x: p.x, y: p.y };
  }

  brushEnd() {
    if (!this.brushStartArea) return;
    if (this.brushUpdate) {
      this.createAreaMap();
      this.triggerMapUpdate();
    }
    this.brushStartArea = null; //reset
    this.brushLastPoint = null;
  }

  // 根据线宽取点
  brushGetPionts(p, lineWidth: number) {
    let points = [];
    if (lineWidth == 1) {
      points.push({ x: p.x, y: p.y });
    } else if (lineWidth == 2) {
      let x1 = this.brushLastPoint.x, y1 = this.brushLastPoint.y, x2 = p.x, y2 = p.y;
      if (x2 != x1 && y2 == y1) { // 横
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y + 1 });
      } else if (x2 == x1 && y2 != y1) { // 竖
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x + 1, y: p.y });
      } else if (x2 > x1 && y2 > y1) { // 斜向右下
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x + 1, y: p.y + 1 });
      } else if (x2 > x1 && y2 < y1) { // 斜向右上
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x + 1, y: p.y - 1 });
      } else if (x2 < x1 && y2 < y1) { // 斜向左上
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x - 1, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x - 1, y: p.y - 1 });
      } else if (x2 < x1 && y2 > y1) { // 斜向左下
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x - 1, y: p.y });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x - 1, y: p.y + 1 });
      }
    } else if (lineWidth == 3) { // 线宽是3简单处理， 3*3矩阵
      points.push({ x: p.x, y: p.y });
      points.push({ x: p.x, y: p.y + 1 });
      points.push({ x: p.x, y: p.y - 1 });
      points.push({ x: p.x + 1, y: p.y });
      points.push({ x: p.x + 1, y: p.y + 1 });
      points.push({ x: p.x + 1, y: p.y - 1 });
      points.push({ x: p.x - 1, y: p.y });
      points.push({ x: p.x - 1, y: p.y + 1 });
      points.push({ x: p.x - 1, y: p.y - 1 });
    } else if (lineWidth == 4) {
      let x1 = this.brushLastPoint.x, y1 = this.brushLastPoint.y, x2 = p.x, y2 = p.y;
      if (x2 != x1 && y2 == y1) { // 横
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x, y: p.y + 2 });
      } else if (x2 == x1 && y2 != y1) { // 竖
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x + 2, y: p.y });
        points.push({ x: p.x - 1, y: p.y });
      } else if (x2 > x1 && y2 > y1) { // 斜向右下
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x, y: p.y + 2 });
        points.push({ x: p.x - 1, y: p.y - 1 });
        points.push({ x: p.x - 1, y: p.y });
        points.push({ x: p.x - 1, y: p.y + 1 });
        points.push({ x: p.x - 1, y: p.y + 2 });
        points.push({ x: p.x + 1, y: p.y - 1 });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x + 1, y: p.y + 1 });
        points.push({ x: p.x + 1, y: p.y + 2 });
        points.push({ x: p.x + 2, y: p.y - 1 });
        points.push({ x: p.x + 2, y: p.y });
        points.push({ x: p.x + 2, y: p.y + 1 });
        points.push({ x: p.x + 2, y: p.y + 2 });
      } else if (x2 > x1 && y2 < y1) { // 斜向右上
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x, y: p.y - 2 });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x - 1, y: p.y });
        points.push({ x: p.x - 1, y: p.y - 1 });
        points.push({ x: p.x - 1, y: p.y - 2 });
        points.push({ x: p.x - 1, y: p.y + 1 });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x + 1, y: p.y - 1 });
        points.push({ x: p.x + 1, y: p.y - 2 });
        points.push({ x: p.x + 1, y: p.y + 1 });
        points.push({ x: p.x + 2, y: p.y });
        points.push({ x: p.x + 2, y: p.y - 1 });
        points.push({ x: p.x + 2, y: p.y - 2 });
        points.push({ x: p.x + 2, y: p.y + 1 });
      } else if (x2 < x1 && y2 < y1) { // 斜向左上
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x, y: p.y - 2 });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x - 1, y: p.y });
        points.push({ x: p.x - 1, y: p.y - 1 });
        points.push({ x: p.x - 1, y: p.y - 2 });
        points.push({ x: p.x - 1, y: p.y + 1 });
        points.push({ x: p.x - 2, y: p.y });
        points.push({ x: p.x - 2, y: p.y - 1 });
        points.push({ x: p.x - 2, y: p.y - 2 });
        points.push({ x: p.x - 2, y: p.y + 1 });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x + 1, y: p.y - 1 });
        points.push({ x: p.x + 1, y: p.y - 2 });
        points.push({ x: p.x + 1, y: p.y + 1 });

      } else if (x2 < x1 && y2 > y1) { // 斜向左下
        points.push({ x: p.x, y: p.y });
        points.push({ x: p.x, y: p.y - 1 });
        points.push({ x: p.x, y: p.y + 1 });
        points.push({ x: p.x, y: p.y + 2 });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x + 1, y: p.y - 1 });
        points.push({ x: p.x + 1, y: p.y + 1 });
        points.push({ x: p.x + 1, y: p.y + 2 });
        points.push({ x: p.x + 1, y: p.y });
        points.push({ x: p.x - 1, y: p.y - 1 });
        points.push({ x: p.x - 1, y: p.y + 1 });
        points.push({ x: p.x - 1, y: p.y + 2 });
        points.push({ x: p.x - 2, y: p.y });
        points.push({ x: p.x - 2, y: p.y - 1 });
        points.push({ x: p.x - 2, y: p.y + 1 });
        points.push({ x: p.x - 2, y: p.y + 2 });
      }
    } else if (lineWidth == 5) {
      points.push({ x: p.x, y: p.y });
      points.push({ x: p.x, y: p.y - 1 });
      points.push({ x: p.x, y: p.y - 2 });
      points.push({ x: p.x, y: p.y + 1 });
      points.push({ x: p.x, y: p.y + 2 });
      points.push({ x: p.x + 1, y: p.y });
      points.push({ x: p.x + 1, y: p.y - 1 });
      points.push({ x: p.x + 1, y: p.y - 2 });
      points.push({ x: p.x + 1, y: p.y + 1 });
      points.push({ x: p.x + 1, y: p.y + 2 });
      points.push({ x: p.x + 2, y: p.y });
      points.push({ x: p.x + 2, y: p.y - 1 });
      points.push({ x: p.x + 2, y: p.y - 2 });
      points.push({ x: p.x + 2, y: p.y + 1 });
      points.push({ x: p.x + 2, y: p.y + 2 });
      points.push({ x: p.x - 1, y: p.y });
      points.push({ x: p.x - 1, y: p.y - 1 });
      points.push({ x: p.x - 1, y: p.y - 2 });
      points.push({ x: p.x - 1, y: p.y + 1 });
      points.push({ x: p.x - 1, y: p.y + 2 });
      points.push({ x: p.x - 2, y: p.y });
      points.push({ x: p.x - 2, y: p.y - 1 });
      points.push({ x: p.x - 2, y: p.y - 2 });
      points.push({ x: p.x - 2, y: p.y + 1 });
      points.push({ x: p.x - 2, y: p.y + 2 });
    }
    return points;
  }


  merge(p1, p2) {
    if (!p1 || !p2) {
      console.warn('invalid args: ', p1, p2)
      return;
    }
    let area1 = this.getArea(p1.x, p1.y);
    let area2 = this.getArea(p2.x, p2.y);
    if (!area1 || !area2) {
      console.warn('area not found', area1, area2)
    } else if (area1 == area2) {
      console.warn('同一个区域')
      return;
    }
    this.mergeArea(area1, area2);
  }


  mergeArea(area, area2) {
    this.backup();
    console.log('mergeArea', area, area2)
    let ai, ax, ay, aw, i, x, y, ah;
    aw = area.width();
    ah = area.height();
    for (ai = 0; ai < aw * ah; ai++) {
      x = area.left + ai % aw;
      y = area.top + Math.floor(ai / aw);
      i = y * this.width + x;
      if (this.mapPixels[i] == area.color) {
        this.mapPixels[i] = area2.color;
      }
    }
    this.mapCtx.putImageData(this.mapData, 0, 0);
    this.createAreaMap();
    this.triggerMapUpdate();
  }


  backup() {
    console.log('backup................');
    this.redoList = [];
    let data = this.getCurrent();
    this.undoList.push(data);
  }

  getCurrent() {
    let data: any = {};
    let pixels = new Uint32Array(this.mapPixels.length);
    for (var i = 0; i < pixels.length; i++) {
      pixels[i] = this.mapPixels[i];
    }
    data.pixels = pixels;
    data.areaMap = JSON.stringify(this.areaMap);
    return data;
  }

  setCurrent(data) {
    let areaMap = data.areaMap;
    let pixels = data.pixels;
    //this.areaMap = JSON.parse(areaMap);
    for (var i = 0; i < pixels.length; i++) {
      this.mapPixels[i] = pixels[i];
    }
    this.mapCtx.putImageData(this.mapData, 0, 0);
    this.createAreaMap();
    this.triggerMapUpdate();
  }

  override undo() {
    if (this.undoList.length <= 0) return;
    this.redoList.unshift(this.getCurrent());
    let data = this.undoList.pop();
    this.setCurrent(data);
  }

  override redo() {
    if (this.redoList.length <= 0) return;
    this.undoList.push(this.getCurrent());
    let data = this.redoList.shift();
    this.setCurrent(data);
  }

  override undoSize() {
    return this.undoList.length;
  }

  override redoSize() {
    return this.redoList.length;
  }

  /**
   * return map blob
   */
  getMapBlob(): Promise<Blob> {
    return new Promise((done, reject) => {
      this.mapCanvas.toBlob(b => {
        done(b);
      })
    });
  }

  getMapImage(): Promise<HTMLImageElement> {
    return new Promise((done, reject) => {
      var img = new Image();
      img.onload = function () { done(img); };
      img.onerror = reject;
      img.src = this.mapCanvas.toDataURL();
    });
  }


  recover(p) {
    //this.backup();
    let area = this.getArea(p.x, p.y);
    if (!area) return;
    console.log('area: ', area);
    let px = parseInt(p.x);
    let py = parseInt(p.y);
    let index = py * this.width + px;
    let oldColor = this.originalMapPixels[index];
    let curColor = this.mapPixels[index];
    console.log('color: ', oldColor, curColor);
    if (oldColor == curColor) {
      console.log('color no change');
      return;
    }

    this.backup();

    let ai, aw, i, x, y, ah;
    aw = area.width();
    ah = area.height();
    for (ai = 0; ai < aw * ah; ai++) {
      x = area.left + ai % aw;
      y = area.top + Math.floor(ai / aw);
      i = y * this.width + x;
      if (this.originalMapPixels[i] == oldColor) {
        this.mapPixels[i] = oldColor;
      }
    }

    this.mapCtx.putImageData(this.mapData, 0, 0);
    this.createAreaMap();
    this.triggerMapUpdate();
  }


  async createMapFromOriginal() {
    if (!this.newMap) {
      // create new map by new page, exclude current map colors
      let areaIds: number[] = Object.keys(this.areaMap).map(key => parseInt(key));
      this.newMap = await FloodFill2.createMapImage(this.page, this.map, 1, areaIds);
      let newMapData = Utils.getImageData(this.newMap);
      this.newMapPixels = new Uint32Array(newMapData.data.buffer);
    }
  }

  async splitArea(p) {
    let area = this.getArea(p.x, p.y);
    if (!area) return;
    console.log('area: ', area);
    await this.createMapFromOriginal();
    let px = parseInt(p.x);
    let py = parseInt(p.y);
    let index = py * this.width + px;
    let curColor = this.mapPixels[index];
    let oldColor = this.newMapPixels[index];
    console.log('color: ', oldColor, curColor);
    // 算法变了， 现在newmap跟原map基本保持一致，分割成两半的区块，有一半的颜色还跟原来保持一致，所以点击的位置的颜色未必有变化
    // if (oldColor == curColor) {
    //   console.log('color no change');
    //   return;
    // }
    if (oldColor == curColor) {
      // 还需要进一步判断，是否有颜色变化
      let change = false;
      let ai, aw, i, x, y, ah;
      aw = area.width();
      ah = area.height();
      for (ai = 0; ai < aw * ah; ai++) {
        x = area.left + ai % aw;
        y = area.top + Math.floor(ai / aw);
        i = y * this.width + x;
        if (this.mapPixels[i] == curColor && this.newMapPixels[i] != curColor) {
          change = true;
          break;
        }
      }
      if (!change) {
        console.log('color no change');
        return;
      }
    }

    this.backup();

    let ai, aw, i, x, y, ah;
    aw = area.width();
    ah = area.height();
    for (ai = 0; ai < aw * ah; ai++) {
      x = area.left + ai % aw;
      y = area.top + Math.floor(ai / aw);
      i = y * this.width + x;
      // if (this.newMapPixels[i] == oldColor) {
      //   this.mapPixels[i] = oldColor;
      // }
      if (this.mapPixels[i] == curColor) {
        this.mapPixels[i] = this.newMapPixels[i];
      }
    }

    this.mapCtx.putImageData(this.mapData, 0, 0);
    this.createAreaMap();
    this.triggerMapUpdate();
  }


  blinkCanvas: HTMLCanvasElement;
  repeater: Repeater;



  blinkByAreas(areas: FillArea[]) {

    //Stop the previous running repeater.
    if (this.repeater) this.repeater.cancel();

    //没有考虑scale
    let _blinkCanvas = Utils.createCanvas(this.width, this.height);
    let ctx = _blinkCanvas.getContext('2d');
    ctx.clearRect(0, 0, this.width, this.height);

    let imgData = ctx.getImageData(0, 0, _blinkCanvas.width, _blinkCanvas.height);
    let pixels = new Uint32Array(imgData.data.buffer);
    //console.log(TAG, 'pixels=', pixels);

    areas.forEach((area: FillArea) => {
      let ai, aw, i, x, y;
      let areaPixelCount = area.width() * area.height();
      aw = area.width();
      for (ai = 0; ai < areaPixelCount; ai++) {
        x = area.left + ai % aw;
        y = area.top + Math.floor(ai / aw);
        i = y * this.width + x;
        if (this.mapPixels[i] == area.color) {
          pixels[i] = 0xffffffff;
        }
      }
    })
    ctx.putImageData(imgData, 0, 0);

    let rect = new Rect(
      Math.min.call(null, ...areas.map(a => a.left)),
      Math.min.call(null, ...areas.map(a => a.top)),
      Math.max.call(null, ...areas.map(a => a.right)),
      Math.max.call(null, ...areas.map(a => a.bottom))
    );

    ctx.strokeStyle = '#dd0000';
    //ctx.strokeStyle = '#efefef';
    ctx.lineWidth = 2;
    let insetRect = rect.inset(ctx.lineWidth, ctx.lineWidth, ctx.lineWidth, ctx.lineWidth);
    ctx.strokeRect(insetRect.left, insetRect.top, insetRect.width(), insetRect.height());

    this.blinkCanvas = _blinkCanvas;
    this.invalidate();

    this.repeater = new Repeater(() => {
      this.blinkCanvas = this.blinkCanvas ? null : _blinkCanvas;
      this.invalidate();
    }, () => {
      this.blinkCanvas = null;
      this.repeater = null;
      this.invalidate();
    }, 5, 300).start();

    return rect;
  }


  ///////////////////////// pencil tool /////////////////////////
  isPencilDrawing = false;
  pencilCanvas: HTMLCanvasElement;
  pencilCtx: CanvasRenderingContext2D;
  pencilPoints: any[] = [];

  pencilStart(p) {
    console.log("pencilStart");
    if (!this.pencilCanvas) {
      this.pencilCanvas = Utils.createCanvas(this.width, this.height);
      this.pencilCtx = this.pencilCanvas.getContext('2d');
      this.pencilCtx.strokeStyle = '#ffffff';
      this.pencilCtx.lineWidth = 2;
    }
    this.isPencilDrawing = true;
    this.pencilCtx.moveTo(p.x, p.y);
    this.pencilPoints.push({ x: p.x, y: p.y });
  }

  pencilMove(p) {
    console.log("pencilMove");
    if (this.isPencilDrawing) {
      this.pencilCtx.lineTo(p.x, p.y);
      this.pencilCtx.stroke();
      this.pencilPoints.push({ x: p.x, y: p.y });
      this.invalidate();
    }
  }

  pencilEnd() {
    console.log("pencilEnd");
  }

  async pencilCreateArea(p) {
    console.log("pencilCreateArea");
    if (!this.pencilCanvas) {
      this.splitArea(p);
      return;
    }

    this.pencilCtx.clearRect(0, 0, this.width, this.height);
    this.pencilCtx.drawImage(this.page, 0, 0);
    this.pencilCtx.strokeStyle = '#000000';
    this.pencilCtx.moveTo(this.pencilPoints[0].x, this.pencilPoints[0].y);
    for (let i = 1; i < this.pencilPoints.length; i++) {
      this.pencilCtx.lineTo(this.pencilPoints[i].x, this.pencilPoints[i].y);
    }
    this.pencilCtx.stroke();

    let blob = await this.canvasToBlob(this.pencilCanvas);
    let newPage = await Utils.blobToImage(blob);
    // create new map by new page, exclude current map colors
    let areaIds: number[] = Object.keys(this.areaMap).map(key => parseInt(key));
    this.newMap = await FloodFill2.createMapImage(newPage, this.map, 1, areaIds);
    let newMapData = Utils.getImageData(this.newMap);
    this.newMapPixels = new Uint32Array(newMapData.data.buffer);

    this.splitArea(p);

    this.pencilPoints = [];
    this.isPencilDrawing = false;
    this.pencilCanvas = null;
    this.invalidate();
  }


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

}
