import { BoardConfig, Header, PiecesStyle, Position } from "./../types";
import { Style, BoardStyle } from "../types";
import drawRectangle from "./layers/drawRectangle";
import drawCoords from "./layers/drawCoords";
import drawMoveIndicators from "./layers/drawMoveIndicators";
import drawPieces from "./layers/drawPieces";
import drawHeader from "./layers/drawHeader";
import drawExtraInfo from "./layers/drawExtraInfo";
import boards from "./styles-board";
import isLink from "../utils/isLink";

const defaultConfig: BoardConfig = {
  size: 720,
  tiles: 8,
  boardStyle: "standard",
  piecesStyle: "tatiana",
  showBorder: true,
  showExtraInfo: true,
  showMaterial: true,
  showMoveIndicator: true,
  showChecks: true,
  showCoords: true,
  flipped: false,
  anonymous: false,
};

const defaultHeader: Header = {
  White: "White",
  Black: "Black",
  WhitePretty: "White",
  BlackPretty: "Black",
  WhiteElo: null,
  BlackElo: null,
  Date: null,
  DatePretty: null,
  Event: null,
  Round: null,
  Site: null,
  Result: null,
};

class Board {
  private cfg: BoardConfig = defaultConfig;

  private scale: number = 1;

  private size: number = 0;
  private squareSize: number = 0;
  private innerSize: number = 0;
  private borderWidth: number = 0;
  private margin: number = 0;

  private style: Style = boards.standard;
  private header: Header = defaultHeader;
  private lastPosition: Position | null = null;
  private background: HTMLCanvasElement | null = null;
  private currentScreen: "title" | "move" = "move";

  private ctx: CanvasRenderingContext2D;
  private tempCtx: CanvasRenderingContext2D;

  private tempCanvas: HTMLCanvasElement = document.createElement("canvas");
  public canvas: HTMLCanvasElement = document.createElement("canvas");

  constructor(config: Partial<BoardConfig> = {}) {
    const ctx = this.canvas.getContext("2d");
    const tempCtx = this.tempCanvas.getContext("2d");
    this.canvas.classList.add("board");

    if (ctx === null || tempCtx === null) {
      throw new Error("Cannot create canvas 2D context");
    }

    this.ctx = ctx;
    this.tempCtx = tempCtx;

    this.updateConfig(config, false);
  }

  async updateConfig(config: Partial<BoardConfig>, refresh: boolean = true) {
    const cfg = { ...this.cfg, ...config };

    this.cfg = cfg;

    this.setSize(cfg.size);
    this.setStyle(cfg.boardStyle);

    if (refresh) {
      await this.refresh();
    }
  }

  async refresh() {
    await this.renderBackground();

    if (this.currentScreen === "title") {
      await this.titleFrame(this.header);
    } else if (this.lastPosition !== null) {
      await this.frame(this.lastPosition, this.header);
    }

    this.render();
  }

  setSize(size: number) {
    this.size = size;
    this.scale = size / 720;
    this.margin = this.cfg.showExtraInfo ? Math.round(50 * this.scale) : 0;

    const height = size + this.margin * 2;

    if (this.canvas.width !== size || this.canvas.height !== height) {
      this.canvas.width = size;
      this.canvas.height = height;
      this.tempCanvas.width = size;
      this.tempCanvas.height = height;
    }

    const tempBorderWidth = this.cfg.showBorder ? this.size / 32 : 0;
    const tempInnerSize = this.size - tempBorderWidth * 2;
    this.squareSize = Math.floor(tempInnerSize / this.cfg.tiles);
    this.innerSize = this.squareSize * this.cfg.tiles;
    this.borderWidth = (this.size - this.innerSize) / 2;

    return this;
  }

  getSize() {
    return this.size;
  }

  setStyle(style: BoardStyle) {
    this.style = boards[style];
    this.cfg.boardStyle = style;
    this.refresh();
    return this;
  }

  setPiecesStyle(style: PiecesStyle) {
    this.cfg.piecesStyle = style;
    this.refresh();
    return this;
  }

  flip() {
    this.cfg.flipped = !this.cfg.flipped;
    this.refresh();
    return this;
  }

  flipWhite() {
    if (!this.cfg.flipped) {
      return;
    }

    this.cfg.flipped = false;
    this.refresh();
    return this;
  }

  flipBlack() {
    if (this.cfg.flipped) {
      return;
    }

    this.cfg.flipped = true;
    this.refresh();
    return this;
  }

  hideBorder() {
    this.cfg.showBorder = false;
    this.setSize(this.size);
    this.refresh();
    return this;
  }

  showBorder() {
    this.cfg.showBorder = true;
    this.setSize(this.size);
    this.refresh();
    return this;
  }

  toggleBorder() {
    this.cfg.showBorder = !this.cfg.showBorder;
    this.setSize(this.size);
    this.refresh();
    return this;
  }

  toggleExtraInfo() {
    this.cfg.showExtraInfo = !this.cfg.showExtraInfo;
    this.setSize(this.size);
    this.refresh();
    return this;
  }

  private getFinalHeader() {
    return this.cfg.anonymous
      ? {
          ...this.header,
          White: "Anonymous",
          Black: "Anonymous",
          WhitePretty: "Anonymous",
          BlackPretty: "Anonymous",
          Site: isLink(this.header.Site) ? null : this.header.Site,
        }
      : {
          ...this.header,
          Site: isLink(this.header.Site)
            ? (this.header.Site as string).replace(/^http[s]*:\/\//, "")
            : this.header.Site,
        };
  }

  async titleFrame(header: Header) {
    this.currentScreen = "title";
    this.header = header;

    await drawHeader(
      this.tempCtx,
      this.size,
      this.scale,
      this.margin,
      this.style,
      this.getFinalHeader()
    );
  }

  async renderBackground() {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;

    canvas.width = this.size;
    canvas.height = this.size + this.margin * 2;

    const { background, dark, light, border, coords } = this.style;

    await drawRectangle(ctx, this.width, this.height, 0, 0, border);

    await drawRectangle(
      ctx,
      this.innerSize,
      this.innerSize,
      this.cfg.showBorder ? this.borderWidth : 0,
      (this.cfg.showBorder ? this.borderWidth : 0) + this.margin,
      background,
      this.cfg.tiles
    );

    for (let rank = 0; rank < this.cfg.tiles; rank++) {
      for (let file = 0; file < this.cfg.tiles; file++) {
        const style =
          (file % 2 === 0 && rank % 2 === 0) ||
          (file % 2 !== 0 && rank % 2 !== 0)
            ? light
            : dark;

        const x = file * this.squareSize + this.borderWidth;
        const y = rank * this.squareSize + this.borderWidth + this.margin;

        await drawRectangle(ctx, this.squareSize, this.squareSize, x, y, style);
      }
    }

    if (this.cfg.showBorder && this.cfg.showCoords) {
      drawCoords(
        ctx,
        coords,
        this.squareSize,
        this.cfg.tiles,
        this.cfg.flipped,
        this.borderWidth,
        this.size,
        this.cfg.showBorder,
        this.margin
      );
    }

    this.background = canvas;
  }

  async frame(position: Position | null, header?: Header) {
    this.currentScreen = "move";
    this.lastPosition = position;
    this.header = header ?? this.header;

    this.tempCtx.clearRect(0, 0, this.size, this.size);

    if (this.background === null) {
      await this.renderBackground();
    }

    this.tempCtx.drawImage((await this.background) as HTMLCanvasElement, 0, 0);

    if (this.lastPosition?.move && this.cfg.showMoveIndicator) {
      await drawMoveIndicators(
        this.tempCtx,
        this.lastPosition.move,
        this.squareSize,
        this.style,
        this.borderWidth,
        this.cfg.tiles,
        this.cfg.flipped,
        this.margin
      );
    }

    if (!this.cfg.showBorder && this.cfg.showCoords) {
      drawCoords(
        this.tempCtx,
        this.style.coords,
        this.squareSize,
        this.cfg.tiles,
        this.cfg.flipped,
        this.borderWidth,
        this.size,
        this.cfg.showBorder,
        this.margin
      );
    }

    if (!this.lastPosition) {
      return;
    }

    await drawPieces(
      this.tempCtx,
      this.lastPosition,
      this.squareSize,
      this.borderWidth,
      this.cfg.flipped,
      this.margin,
      this.cfg.piecesStyle
    );

    if (this.cfg.showExtraInfo && header) {
      await drawExtraInfo(
        this.tempCtx,
        this.width,
        this.height,
        this.scale,
        this.margin,
        this.style,
        this.getFinalHeader(),
        this.cfg.flipped,
        this.lastPosition
      );
    }
  }

  render() {
    this.ctx.clearRect(0, 0, this.size, this.size);
    this.ctx.drawImage(this.tempCanvas, 0, 0);
  }

  toImgUrl() {
    return this.canvas.toDataURL();
  }

  toImageData() {
    return this.ctx.getImageData(0, 0, this.width, this.height);
  }

  toClampedArray() {
    return this.ctx.getImageData(0, 0, this.width, this.height).data;
  }

  toImgElement() {
    const dataUrl = this.toImgUrl();

    const img = new Image();
    img.classList.add("board");
    img.src = dataUrl;
    return img;
  }

  get width() {
    return this.size;
  }

  get height() {
    return this.size + this.margin * 2;
  }

  toGIF() {}
}

export default Board;
