import React, { useState, useEffect } from "react";

const hexagonRatio = 1.1547005,
  tileHeight = 0.16,
  tileWidth = tileHeight * hexagonRatio,
  roadSize = tileHeight * 0.5 * (5. / 8.),
  settlementSize = tileHeight * 0.5 - roadSize;

const Tiles = (board) => {
  const tiles = [ {
    type: "wood", y: 0. / 4.
  }, {
    type: "wood", y: 1. / 4.
  }, {
    type: "wood", y: 2. / 4.
  }, {
    type: "wood", y: 3. / 4.
  }, {
    type: "wheat", y: 0. / 4.
  }, {
    type: "wheat", y: 1. / 4.
  }, {
    type: "wheat", y: 2. / 4.
  }, {
    type: "wheat", y: 3. / 4.
  }, {
    type: "stone", y: 0. / 4.
  }, {
    type: "stone", y: 1. / 4.
  }, {
    type: "stone", y: 2. / 4.
  }, {
    type: "sheep", y: 0. / 4.
  }, {
    type: "sheep", y: 1. / 4.
  }, {
    type: "sheep", y: 2. / 4.
  }, {
    type: "sheep", y: 3. / 4.
  }, {
    type: "brick", y: 0. / 4.
  }, {
    type: "brick", y: 1. / 4.
  }, {
    type: "brick", y: 2. / 4.
  }, {
    type: "robber", y: 0
  } ];

  [ "robber", "brick", "wood", "sheep", "stone", "wheat" ].forEach((type) => {
    const image = new Image(),
      file = type + ".png";
    tiles.forEach((tile) => {
      if (tile.type == type) {
        tile.image = image;
        tile.x = 0;
        tile.pos = { x: 0, y: 0 };
      }
    });
    image.addEventListener("load", (event) => {
      console.log(`Done loading ${file}`);
      window.requestAnimationFrame(board.drawFrame);
    });
    image.addEventListener("error", (event) => {
      alert(`Error loading ${file}`);
    });
    image.src = `assets/original/${file}`;
  });

  return tiles;
};

const Pips = (board) => {
  const image = new Image(),
    file = 'pip-numbers.png';

  let pips = [
    { roll:  0, pips: 0, y: 3. / 6., x: 0. / 6. },
    { roll:  5, pips: 4, y: 0. / 6., x: 0. / 6. },
    { roll:  2, pips: 1, y: 0. / 6., x: 1. / 6. },
    { roll:  6, pips: 5, y: 0. / 6., x: 2. / 6. },
    { roll:  3, pips: 2, y: 0. / 6., x: 3. / 6. },
    { roll:  8, pips: 5, y: 0. / 6., x: 4. / 6. },
    { roll: 10, pips: 3, y: 0. / 6., x: 5. / 6. },
    { roll:  9, pips: 4, y: 1. / 6., x: 0. / 6. },
    { roll: 12, pips: 1, y: 1. / 6., x: 1. / 6. },
    { roll: 11, pips: 2, y: 1. / 6., x: 2. / 6. },
    { roll:  4, pips: 3, y: 1. / 6., x: 3. / 6. },
    { roll:  8, pips: 5, y: 1. / 6., x: 4. / 6. },
    { roll: 10, pips: 3, y: 1. / 6., x: 5. / 6. },
    { roll:  9, pips: 4, y: 2. / 6., x: 0. / 6. },
    { roll:  4, pips: 3, y: 2. / 6., x: 1. / 6. },
    { roll:  5, pips: 4, y: 2. / 6., x: 2. / 6. },
    { roll:  6, pips: 6, y: 2. / 6., x: 3. / 6. },
    { roll:  3, pips: 2, y: 2. / 6., x: 4. / 6. },
    { roll: 11, pips: 2, y: 2. / 6., x: 5. / 6. }
  ];
  
  image.addEventListener("load", (event) => {
    console.log(`Done loading ${file}`);
    window.requestAnimationFrame(board.drawFrame);
  });
  image.addEventListener("error", (event) => {
    alert(`Error loading ${file}`);
  });
  image.src = `assets/original/${file}`;

  return {
    image: image,
    pips: pips
  };
};

const Border = (board, border) => {
  const image = new Image(), file = border.file
  border.image = image;
  image.addEventListener("load", (event) => {
    console.log(`Done loading ${file}`);
    window.requestAnimationFrame(board.drawFrame);
  });
  image.addEventListener("error", (event) => {
    alert(`Error loading ${file}`);
  });
  image.src = `assets/original/${file}`;
  return border;
};

const Table = (board) => {
  const image = new Image(), file = "table.png";
  image.addEventListener("load", (event) => {
    console.log(`Done loading ${file}`);
    window.requestAnimationFrame(board.drawFrame);
  });
  image.addEventListener("error", (event) => {
    alert(`Error loading ${file}`);
  });
  image.src = `assets/original/${file}`;
  return image;
};

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.componentDidMount = this.componentDidMount.bind(this);
    this.updateDimensions = this.updateDimensions.bind(this);
    this.drawFrame = this.drawFrame.bind(this);
    this.drawBorders = this.drawBorders.bind(this);
    this.drawPips = this.drawPips.bind(this);
    this.mouseMove = this.mouseMove.bind(this);
    this.randomize = this.randomize.bind(this);

    this.mouse = { x: 0, y: 0 };
    this.radius = 0.317;

    this.pips = Pips(this);
    this.tiles = Tiles(this);
    this.table = Table(this);

    this.closest = {
      info: {},
      tile: null,
      road: null,
      tradeToken: null,
      settlement: null
    };

    this.borders = [ {
      file: 'borders-1.6.png', left: "sheep", right: "bank"
    }, { 
      file: 'borders-2.1.png', center: "sheep"
    }, { 
      file: 'borders-3.2.png', left: "wheat", right: "bank"
    }, { 
      file: 'borders-4.3.png', center: "wood"
    }, { 
      file: 'borders-5.4.png', left: "sheep", right: "bank"
    }, { 
      file: 'borders-6.5.png', center: "bank"
    } ].map((file) => {
      return Border(this, file);
    });

    this.randomize();
  }

  randomize() {
    this.borders = shuffle(this.borders);
    this.tiles = shuffle(this.tiles);
    window.requestAnimationFrame(this.drawFrame);
  }

  mouseMove(event) {
    const rect = this.canvas.parentElement.getBoundingClientRect();
    let x, y;

    if (event.changedTouches && event.changedTouches.length > 0) {
      x = event.changedTouches[0].clientX;
      y = event.changedTouches[0].clientY;
    } else {
      x = event.clientX;
      y = event.clientY;
    }
    
    /* Scale mouse.x and mouse.y relative to board */
    this.mouse.x = (x - rect.left) / 
      (this.minSize / hexagonRatio) - 0.5 - tileHeight * 0.5,
    this.mouse.y = (y - rect.top) /
      (this.minSize / hexagonRatio) - 0.5 - tileHeight * 0.5;

    /* Hide the mouse cursor circle after 0.5s */
    if (this.mouse.timer) {
      window.clearTimeout(this.mouse.timer);
    }
    this.mouse.timer = window.setTimeout(() => {
      this.mouse.timer = null;
      window.requestAnimationFrame(this.drawFrame);
    }, 500);

    let closest = null;
    
    this.tiles.forEach((tile) => {
      const dX = tile.pos.x - this.mouse.x,
        dY = tile.pos.y - this.mouse.y;
      const distance = Math.sqrt(dX * dX + dY * dY);
      if (distance > tileHeight * 0.75) {
        return;
      }
      if (!closest || closest.distance > distance) {
        closest = {
          tile: tile,
          distance: distance,
          angle: (distance != 0.0) ? Math.atan2(dY, dX) : 0
        }
      }
    });

    if (!closest) {
      this.closest.tile = null;
      this.closest.info.distance = -1;
      this.closest.road = null;
      this.closest.angle = 0;
      this.closest.settlement = null;
      this.closest.tradeToken = null;
    } else {
      if (this.closest.tile != closest.tile) {
        this.closest.tile = closest.tile;
      }

      this.closest.info.distance = closest.distance,
      this.closest.info.angle = closest.angle;
    }

    window.requestAnimationFrame(this.drawFrame);
  }

  updateDimensions() {
    if (this.updateSizeTimer) {
      clearTimeout(this.updateSizeTimer);
    }

    this.updateSizeTimer = setTimeout(() => {
      this.canvas.width = this.canvas.offsetWidth;
      this.canvas.height = this.canvas.offsetHeight;
      this.width = this.canvas.parentElement.clientWidth;
      this.height = this.canvas.parentElement.clientHeight;
      this.updateSizeTimer = 0;
      this.drawFrame();
    }, 250);
  }

  drawFrame() {
    if (!this.canvas) {
      return;
    }

    const ctx = this.canvas.getContext("2d");

    ctx.save();
    ctx.strokeStyle = 'white';
    ctx.filleStyle = 'rgba(0, 0, 0, 0)';

    this.minSize = Math.min(this.canvas.height, this.canvas.width);
    ctx.drawImage(this.table,
      0, 0,
      this.table.width, this.table.height,
      0, 0, this.minSize, this.minSize);

    ctx.scale(this.minSize / hexagonRatio, this.minSize / hexagonRatio);
    ctx.translate(0.5 * hexagonRatio, 0.5 * hexagonRatio);
    ctx.lineWidth = 1. / this.minSize;

    /* Board dimensions:
     *     ________
     *    /___1__| \
     *   / /      \6\
     *  /2/        \ \
     * / /          \/\
     * \/\          / /
     *  \ \        /5/
     *   \3\______/_/
     *    \_|__4___/
     * 0  0.3     0.6  1
     */

    ctx.save();
    this.drawBorders(ctx);
    ctx.restore();

    ctx.save();
    this.drawPips(ctx);
    ctx.restore();

    if (this.closest.tile) {
      ctx.save();
      ctx.translate(this.closest.tile.pos.x, this.closest.tile.pos.y);
      ctx.strokeStyle = "red";
      ctx.beginPath();
      ctx.arc(0, 0, tileHeight * 0.5, 0, Math.PI * 2.);
      ctx.stroke();
      /* road */
      if (1 || this.closest.info.distance > 0.1 * tileWidth * 0.5) {
        const angle = Math.round(this.closest.info.angle / (Math.PI / 3.)) * (Math.PI / 3.);
        ctx.strokeStyle = "green";
        ctx.rotate(angle);
        ctx.translate(-tileHeight * 0.5, 0);
        ctx.beginPath();
        ctx.rect(-roadSize * 0.125, -roadSize * 0.5, roadSize * 0.25, roadSize);
        ctx.stroke(); 
        ctx.translate(tileHeight * 0.5, 0);
        ctx.rotate(-angle);
      }
      /* village */
      if (1 || this.closest.info.distance > 0.1 * tileHeight * 0.5) { 
        let angle = (this.closest.info.angle - Math.PI / 6.);
        angle = Math.round(angle / (Math.PI / 3.)) * (Math.PI / 3.);
        angle += Math.PI / 6.;
        ctx.strokeStyle = "blue";
        ctx.rotate(angle);
        ctx.translate(-tileWidth * 0.5, 0);
        ctx.rotate(-angle);
        ctx.beginPath();
        ctx.rect(-settlementSize * 0.5, -settlementSize * 0.5, settlementSize, settlementSize);
        ctx.stroke(); 
        ctx.rotate(angle);
        ctx.translate(+tileWidth * 0.5, 0);
        ctx.rotate(-angle);
      }
      ctx.restore();
    }

    /* For 0.5 after mouse movement, there is an on
     * screen mouse helper. */
    if (this.mouse.timer) {
      ctx.strokeStyle = "rgba(0, 255, 255)";
      ctx.fillStyle = "rgba(0, 255, 255, 0.25)";
      ctx.beginPath();
      ctx.arc(this.mouse.x, this.mouse.y,
        tileHeight * 0.5, 0, Math.PI * 2.);
      ctx.stroke();
      ctx.fill();
    }

    ctx.restore();
  }

  drawPips(ctx) {
    const image = this.pips.image, pipSize = 0.06;

    function drawTile(tile, angle, radius) {
      tile.pos.x = Math.sin(angle) * radius;
      tile.pos.y = Math.cos(angle) * radius;
      const image = tile.image;
      ctx.save();
      ctx.rotate(angle);
      ctx.translate(0., radius);
      ctx.rotate(-angle + Math.PI * 1. / 6.);
      ctx.drawImage(image,
        tile.x * image.width, tile.y * image.height,
        image.width, image.height / 4.,
        -tileWidth * 0.5, -tileHeight * 0.5,
        tileWidth, tileHeight);
      ctx.restore();
    }

    function drawPip(pip, angle, radius) {
      ctx.save();
      ctx.rotate(angle);
      ctx.translate(0., radius);
      ctx.rotate(-angle);
      ctx.drawImage(image,
        pip.x * image.width, pip.y * image.height,
        image.width / 6., image.height / 6.,
        -pipSize * 0.5, -pipSize * 0.5,
        pipSize, pipSize);
      ctx.restore();
    }

    let angle,
      radius = this.radius,
      index = 1, pip;

    /* Outer row */
    angle = 0;
    for (let i = 0; i < 12; i++) {
      angle -= Math.PI * 2. / 12.;
      if (this.tiles[i].type == "robber") {
        pip = this.pips.pips[0]
      } else {
        pip = this.pips.pips[index++];
      }
      drawTile(this.tiles[i], angle, radius - (i % 2) * 0.04);
      drawPip(pip, angle, radius - (i % 2) * 0.04);
    }

    /* Middle row */
    angle = Math.PI * 2. / 12.;
    radius = this.radius * 0.5;
    for (let i = 12; i < 18; i++) {
      angle -= Math.PI * 2. / 6.;
      if (this.tiles[i].type == "robber") {
        pip = this.pips.pips[0]
      } else {
        pip = this.pips.pips[index++];
      }
      drawTile(this.tiles[i], angle, radius);
      drawPip(pip, angle, radius);
    }

    /* Center */
    let i = 18;
    if (this.tiles[i].type == "robber") {
      pip = this.pips.pips[0]
    } else {
      pip = this.pips.pips[index++];
    }
    drawTile(this.tiles[i], 0, 0);
    drawPip(pip, 0, 0);
  }

  drawBorders(ctx) {
    ctx.rotate(Math.PI);

    const offset = 0.18;
    this.borders.forEach((border, index) => {
      ctx.translate(0, this.radius);

      const image = border.image;

      ctx.drawImage(image, 
        -offset, 0,
        0.5, 0.5 * image.height / image.width);

      ctx.translate(0, -this.radius);
      ctx.rotate(Math.PI * 2. / 6.);
    });
  }

  componentDidMount() {
    this.start = new Date();

    window.addEventListener("mousedown", this.randomize);
    window.addEventListener("touchmove", this.mouseMove);
    window.addEventListener("mousemove", this.mouseMove);
    window.addEventListener("resize", this.updateDimensions);

    this.updateDimensions();
  }

  componentWillUnmount() {
    if (this.updateSizeTimer) {
      clearTimeout(this.updateSizeTimer);
      this.updateSizeTimer = 0;
    }
    window.removeEventListener("mousedown", this.randomize);
    window.removeEventListener("mousemove", this.mouseMove);
    window.removeEventListener("touchmove", this.mouseMove);
    window.removeEventListener("resize", this.updateDimensions);
  }

  render() {
    return (
      <canvas className="Board" ref={el => this.canvas = el}></canvas>
    );
  }
}
export default Board;
