import * as firingArc from './game/firing_arc.js';
import * as sightLine from './game/sight_line.js';
import * as soldier from './game/soldier.js';

let svg, distanceCallback, proneFlagCallback, selectCallback,
  selected,
  placing = [];

function getCellContents(cell) {
  return cell.querySelectorAll('*:not(use[href="#hex"])');
}

function getGridIndex({ parentElement: { dataset: { x }, parentElement: { dataset: { y }}}}) {
  return { x: +x, y: +y };
}

function getHex(cell) {
  return cell.querySelector('use[href="#hex"]');
}

function getCellOccupant(cell) {
  return cell.querySelector('.counter');
}

function getCells(svg) {
  return svg.querySelectorAll('g.grid > g[data-y] > g[data-x]');
}

function getLockedSightLine(svg) {
  return svg.querySelector('line.sight-line:not(.active)');
}

function getActiveSightLine(svg) {
  return svg.querySelector('line.sight-line.active');
}

function isGrenade(el) {
  return el && el.getAttribute('href') === '#counter-grenade';
}

function isClone(counter) {
  const isClone = counter.classList.contains('clone'),
    { allegiance: clAl, number: clNum } = counter.dataset;

  return {
    of: function ({ dataset: { allegiance, number }}) {
      return isClone && clAl == allegiance && clNum == number;
    }
  };
}

function getCellPosition(cell) {
  let pt = new DOMPoint(0, 0),
    transform = getComputedStyle(cell).transform.match(/-?\d+\.?\d*/g),
    mtx = new DOMMatrix(transform);
    pt = pt.matrixTransform(mtx);

  transform = getComputedStyle(cell.parentElement).transform.match(/-?\d+\.?\d*/g);
  mtx = new DOMMatrix(transform);
  pt = pt.matrixTransform(mtx);

  return pt;
}

function getCell(x, y) {
  return svg.querySelector(`g.grid > g[data-y="${y}"] > g[data-x="${x}"]`);
}

function getCounterAtGridIndex(x, y) {
  return getCell(x, y).querySelector('.counter');
}

function getSelected() {
  return svg.querySelector(`.counter.selected[data-allegiance][data-number]`);
}

function clearSightLine() {
  sightLine.setHexes([]);
  sightLine.clear();
  distanceCallback && distanceCallback();
}

function updateSightLine(cell) {
  const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = cell,
    { dataset: { x: tX }, parentElement: { dataset: { y: tY }}} = sightLine.getLockTarget();

  const selector = sightLine.calcIndexes(+sX, +sY, +tX, +tY)
    .map(([x, y]) => `g.grid g[data-y="${y}"] g[data-x="${x}"] use[href="#hex"]`)
    .join(', ');

  const hexes = svg.querySelectorAll(selector);
  sightLine.setHexes(hexes);
  sightLine.update(getCellPosition(cell));
  distanceCallback && distanceCallback(hexes.length - 1);
}

function drawSightLine(sourceCell, targetCell) {
  const { dataset: { x: sX }, parentElement: { dataset: { y: sY }}} = sourceCell,
    { dataset: { x: tX }, parentElement: { dataset: { y: tY }}} = targetCell;

  const selector = sightLine.calcIndexes(+sX, +sY, +tX, +tY)
    .map(([x, y]) => `g.grid g[data-y="${y}"] g[data-x="${x}"] use[href="#hex"]`)
    .join(', ');

  const hexes = svg.querySelectorAll(selector);
  sightLine.setHexes(hexes);
  const line = sightLine.create(getCellPosition(sourceCell), getCellPosition(targetCell));
  svg.querySelector('.gameboard').appendChild(line);
  distanceCallback && distanceCallback(hexes.length - 1);
}

function moveBackOneStepInHistory(counter) {
  const trace = soldier.getTrace(svg, counter);
  counter.remove();
  counter = getCounterAtGridIndex(...counter.dataset.previous.split(','));
  counter.classList.remove('clone');
  counter.classList.add(soldier.getSelectedClass());
  if (!('previous' in counter.dataset)) {
    trace.remove();
  } else {
    const points = trace.getAttribute('points').split(' ');
    points.pop();
    trace.setAttributeNS(null, 'points', points.join(' '));
  }

  return counter;
}

function clearMoveHistory(clone, counter) {
  clone.classList.remove('clone');
  clone.classList.add(soldier.getSelectedClass());
  counter.remove();
  counter = clone;
  soldier.removeClones(svg, counter);
  soldier.getTrace(svg, counter).remove();

  return counter;
}

function deleteClone(occupant, counter, cell) {
  const index = getGridIndex(occupant),
    trace = soldier.getTrace(svg, counter),
    pos = getCellPosition(cell),
    points = trace.getAttribute('points').split(' ').filter(p => p != `${pos.x},${pos.y}`).join(' ');;

  let current = counter;
  trace.setAttributeNS(null, 'points', points);

  while (current.dataset.previous != `${index.x},${index.y}`) {
    current = getCounterAtGridIndex(...current.dataset.previous.split(','));
  }

  current.dataset.previous = occupant.dataset.previous;
  occupant.remove();
}

function hasPreviousMoveInHistory(counter) {
  return 'previous' in counter.dataset;
}

function selectOffBoard() {
  this.classList.contains(soldier.getSelectedClass()) ? unSelect() : select(this);
}

export function getUnits() {
  return soldier.getAllCounters(svg);
}

export function setDistanceCallback(callback) {
  distanceCallback = callback;
}

export function setProneFlagCallback(callback) {
  proneFlagCallback = callback;
}

export function setSelectCallback(callback) {
  selectCallback = callback;
}

export function start(el) {
  svg = el;

  getUnits(svg).forEach(unit => unit.addEventListener('click', selectOffBoard));

  getCells(svg).forEach(cell => {
    cell.addEventListener('click', e => {
      const occupant = getCellOccupant(cell);
      let toPlace = placing.pop();

      if (isGrenade(toPlace)) {
        getHex(cell).after(toPlace);
      } else if (toPlace && !occupant) {
        soldier.place(svg, toPlace, cell);
        toPlace.removeEventListener('click', selectOffBoard);
        placing.push(toPlace);
        getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine();
      } else if (toPlace && occupant) {
        if (toPlace === occupant) {
          if (hasPreviousMoveInHistory(toPlace)) {
            toPlace = moveBackOneStepInHistory(toPlace);
            placing.push(toPlace);
            getLockedSightLine(svg) ? updateSightLine(toPlace.parentElement) : drawSightLine(toPlace.parentElement, cell);
          } else {
            unSelect();
          }
        } else if (!occupant.classList.contains('clone')) {
          select(occupant);
        } else {
          if (isClone(occupant).of(toPlace)) {
            if (hasPreviousMoveInHistory(occupant)) {
              deleteClone(occupant, toPlace, cell);
            } else {
              toPlace = clearMoveHistory(occupant, toPlace);
              getLockedSightLine(svg) ? updateSightLine(cell) : clearSightLine();
            }
          }
          placing.push(toPlace);
        }
      } else if (!toPlace && occupant) {
        select(occupant);
      } else {
        console.log('removing cell contents');
        getCellContents(cell).forEach(el => el.remove());
      }
    });

    cell.addEventListener('dblclick', e => {
      const toPlace = placing.pop(),
        occupant = getCellOccupant(cell);

      if (toPlace && occupant && toPlace === occupant) {
        const { number, allegiance } = toPlace.dataset,
          selector = `[data-allegiance="${allegiance}"][data-number="${number}"]`;

        svg.querySelectorAll(selector).forEach(el => el.remove());
        selectCallback();
      }
    });

    cell.addEventListener('contextmenu', e => {
      e.preventDefault();

      sightLine.toggleLock(cell);
      cell.dispatchEvent(new MouseEvent('pointerover'));
    });

    cell.addEventListener('pointerover', () => {
      const selected = getSelected();

      if (selected && svg.querySelector('.grid').contains(selected) && !getLockedSightLine(svg) && cell !== selected.parentElement) {
        clearSightLine();
        drawSightLine(selected.parentElement, cell);
      }

      let occupant = getCellOccupant(cell);

      if (occupant) {
        firingArc.toggleCounterVisibility(svg, occupant, true);
      }
    });

    cell.addEventListener('pointerout', () => {
      getActiveSightLine(svg) && clearSightLine();

      let occupant = getCellOccupant(cell);

      if (occupant) {
        firingArc.toggleCounterVisibility(svg, occupant, false);
      }
    });
  });

  console.log('gameboard.js loaded');
}

export function select(selected) {
  unSelect();
  let counter = soldier.getCounter(svg, selected);

  if (counter) {
    firingArc.get(svg, counter).forEach(el => el.removeAttribute('clip-path'));
  } else {
    counter = soldier.createCounter(selected);
  }

  placing.push(counter);
  counter.classList.add(soldier.getSelectedClass());
  selectCallback && selectCallback({ prone: soldier.hasProne(counter), ...counter.dataset });
}

export function unSelect() {
  const selected = getSelected();
  placing = [];

  if (selected) {
    getSelected().classList.remove(soldier.getSelectedClass());
    clearSightLine();
    firingArc.clipAll(svg);
  }
}

export function endMove() {
  const selected = getSelected();

  if (selected) {
    soldier.endMove(svg, selected);
    unSelect();
  }
}

export function clearFiringArcs(allegiance) {
  firingArc.clear(svg, allegiance);
}

export function toggleProne() {
  const selected = getSelected(),
    isOnBoard = selected && selected.parentElement.hasAttribute('data-x');

  if (selected && isOnBoard) {
    soldier.toggleProne(selected);
  }
}

export function toggleFiringArcVisibility() {
  firingArc.toggleVisibility(svg, this.dataset.allegiance);
}

export function setFiringArc() {
  const counter = getSelected(),
    isOnBoard = counter => counter && counter.parentElement.hasAttribute('data-x');

  if (isOnBoard(counter)) {
    firingArc.set(svg, this.dataset.size, counter, getCellPosition(counter.parentElement));
  }
}

export function setGrenade() {
  let counter = document.createElementNS(svgns, 'use');
  counter.setAttributeNS(null, 'href', '#counter-grenade');

  placing.push(counter);
}
