import {
  ARROWHEAD_LENGTH,
  ARROWHEAD_WIDTH,
} from './config.js';

export class Display {
  scalePower = 0;
  viewOrigin = {x: 0, y: 0};

  constructor(sim) {
    const canvas = document.createElement('canvas')
    this.canvas = canvas;

    if (sim) {
      // Create canvas that fills the window
      // If the window resizes, also resize the canvas
      sim.div.appendChild(canvas);
      this.fullscreen();
      window.addEventListener('resize', () => this.fullscreen());
    }
  }

  toJSON() {
    return {
      scalePower: this.scalePower,
      viewOrigin: this.viewOrigin,
    };
  }

  fromJSON({scalePower, viewOrigin}) {
    this.scalePower = scalePower;
    this.viewOrigin = viewOrigin;
  }

  frame() {
    // Clear canvas in preparation for other modules to render this frame
    this.fillCanvas();
  }

  get scale() {
    return 2 ** this.scalePower;
  }

  get ctx() {
    const ctx = this.canvas.getContext("2d");
    ctx.resetTransform();
    ctx.scale(this.scale, this.scale);
    ctx.translate(-this.viewOrigin.x, -this.viewOrigin.y);
    return ctx;
  }

  get width() {
    return this.canvas.width / this.scale;
  }

  get height() {
    return this.canvas.height / this.scale;
  }

  screenToSim(x, y) {
    return {
      x: x / this.scale + this.viewOrigin.x,
      y: y / this.scale + this.viewOrigin.y,
    };
  }

  simToScreen(x, y) {
    return {
      x: (x - this.viewOrigin.x) * this.scale,
      y: (y - this.viewOrigin.y) * this.scale,
    };
  }

  fullscreen() {
    this.canvas.width = document.documentElement.clientWidth;
    this.canvas.height = document.documentElement.clientHeight;
  }

  fillCanvas() {
    const ctx = this.ctx;
    ctx.fillStyle = '#000';
    ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
  }

  drawArrow(startX, startY, endX, endY, {
    style, width, arrowhead, arrowheadLength, fill, ifShort
  }) {
    const ctx = this.ctx;
    ctx.strokeStyle = style;
    // Keep arrows at normal scale
    ctx.lineWidth = width / this.scale;
    arrowhead = arrowhead ?? true;
    arrowheadLength = arrowheadLength ?? ARROWHEAD_LENGTH;
    const scaledArrowheadLength = arrowheadLength / this.scale;
    ifShort = ifShort ?? 'tail';
    const arrowDirection = Math.atan2(endY - startY, endX - startX);
    const length = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);
    let tail = true;
    if (!length) {
      return;
    }
    if (length <= scaledArrowheadLength) {
      switch (ifShort) {
        case 'head': {
          arrowhead = true;
          tail = false;
          break;
        }
        case 'tail': {
          arrowhead = false;
          tail = true;
          break;
        }
      }
    }

    if (tail) {
      const endAx = arrowhead ? endX - (scaledArrowheadLength) * Math.cos(arrowDirection) : endX;
      const endAy = arrowhead ? endY - (scaledArrowheadLength) * Math.sin(arrowDirection) : endY;
      ctx.beginPath();
      ctx.moveTo(startX, startY);
      ctx.lineTo(endAx, endAy);
      ctx.stroke();
    }
    if (arrowhead) {
      this.drawArrowHead(endX, endY, arrowDirection, {style, length: arrowheadLength, fill});
    }
  }

  drawArrowHead(x, y, direction, {style, length, fill}) {
    const arrowheadLength = length ?? ARROWHEAD_LENGTH;
    const arrowheadWidth = arrowheadLength / ARROWHEAD_LENGTH * ARROWHEAD_WIDTH;
    const ctx = this.ctx;
    ctx.fillStyle = style;
    ctx.strokeStyle = style;
    // To make this simple, draw the arrowhead and then rotate and translate it as needed.
    // Keep arrows at normal scale
    const scaledArrowheadLength = arrowheadLength / this.scale;
    const scaledArrowheadWidth = arrowheadWidth / this.scale;
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.translate(x, y);
    ctx.rotate(direction);
    ctx.lineTo(-scaledArrowheadLength, -scaledArrowheadWidth / 2);
    ctx.lineTo(-scaledArrowheadLength, scaledArrowheadWidth / 2);
    ctx.closePath();
    if (fill !== false) {
      ctx.fill();
    } else {
      ctx.stroke();
    }
    ctx.resetTransform();
  }

  drawBox(start, end) {
    const ctx = this.ctx;
    ctx.strokeStyle = 'rgb(0, 255, 0)';
    ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y);
  }
}
