import React from 'react';
import Cell from './Cell';
import Controls from './Controls';
import { generateNewGrid, updateGridDimensions } from './lib';

const DEFAULT_WIDTH = 30;
const DEFAULT_HEIGHT = 20;
const DEFAULT_SPEED = 100;
const DEFAULT_CELL_SIZE = 30;

const INITIAL_GRID = Array(DEFAULT_HEIGHT).fill(
  Array(DEFAULT_WIDTH).fill(false),
);

const INITIAL_STATE: State = {
  grid: [...INITIAL_GRID],
  running: false,
  generations: 0,
  cellSize: DEFAULT_CELL_SIZE,
  speed: DEFAULT_SPEED,
  width: DEFAULT_WIDTH,
  height: DEFAULT_HEIGHT,
};

interface State {
  grid: boolean[][];
  running: boolean;
  cellSize: number;
  speed: number;
  width: number;
  height: number;
  generations: number;
}

class Board extends React.Component<{}, State> {
  timer: NodeJS.Timeout | null;

  constructor(props: {}) {
    super(props);
    this.state = INITIAL_STATE;
    this.timer = null;
  }

  start = () => {
    this.timer = setInterval(() => {
      const grid = generateNewGrid(this.state.grid);
      this.setState({ grid, generations: this.state.generations + 1 });
    }, this.state.speed);
  };

  stop = () => {
    if (this.timer) clearInterval(this.timer);
  };

  reset = () => {
    if (window.confirm('Are you sure?')) {
      this.stop();
      this.setState(INITIAL_STATE);
    }
  };

  toggleCell = (x: number, y: number) => {
    const newGrid = [...this.state.grid];
    const newRow = [...newGrid[y]];
    newRow[x] = !newGrid[y][x];
    newGrid[y] = newRow;
    this.setState({ grid: newGrid });
  };

  toggleRunning = () => {
    if (this.state.running) this.stop();
    if (!this.state.running) this.start();
    this.setState({ running: !this.state.running });
  };

  updateSpeed = (speed: number) => {
    this.setState({ speed });
    if (this.state.running) {
      this.stop();
      this.start();
    }
  };

  updateCellSize = (f: (size: number) => number) => {
    this.setState({ cellSize: f(this.state.cellSize) });
  };

  updateDimensions = (dimensions: { width?: number; height?: number }) => {
    if (dimensions.width) {
      this.setState({ width: dimensions.width });
    }

    if (dimensions.height) {
      this.setState({ height: dimensions.height });
    }

    const grid = updateGridDimensions(
      { width: this.state.width, height: this.state.height },
      dimensions,
      this.state.grid,
    );

    this.setState({ grid });
  };

  download = () => {
    const state = JSON.stringify(this.state);
    const element = document.createElement('a');
    const file = new Blob([state], { type: 'text/plain' });
    element.href = URL.createObjectURL(file);
    element.download = 'export.json';
    document.body.appendChild(element);
    element.click();
  };

  upload = () => {
    if (
      window.confirm('This will replace any changes you have made. Continue?')
    ) {
      const element = document.createElement('input');
      element.type = 'file';
      element.click();
      element.addEventListener('change', (e) => {
        const files = (e.currentTarget as HTMLInputElement).files;
        if (files && files.length > 0) {
          const file = files[0];
          file.text().then((text) => {
            const newState = JSON.parse(text);
            this.setState(newState);
          });
        }
      });
    }
  };

  render() {
    const {
      cellSize,
      generations,
      grid,
      height,
      running,
      speed,
      width,
    } = this.state;

    return (
      <div className="container">
        <Controls
          running={running}
          speed={speed}
          width={width}
          height={height}
          toggle={this.toggleRunning}
          updateCellSize={this.updateCellSize}
          updateDimensions={this.updateDimensions}
          updateSpeed={this.updateSpeed}
          reset={this.reset}
          download={this.download}
          upload={this.upload}
        />
        <div className="board-container">
          <div className="board">
            {grid.map((row, y) => (
              <div className="row" key={y} style={{ height: cellSize }}>
                {row.map((cell, x) => (
                  <Cell
                    disabled={running}
                    x={x}
                    y={y}
                    cellSize={cellSize}
                    alive={cell}
                    toggle={this.toggleCell}
                    key={x}
                  />
                ))}
              </div>
            ))}
          </div>
        </div>
        <div className="counter">
          <span>
            {generations} generation{generations === 1 ? '' : 's'}
          </span>
        </div>
      </div>
    );
  }
}

export default Board;
