const FIELD_WIDTH = [11, 11, 15]; // for each phase
const FIELD_HEIGHT = FIELD_WIDTH;
const FIELD_LINE_WIDTH = 2;
const FIELD_MARGIN = 2;
const FIELD_COUNT_X = 10;
const FIELD_COUNT_Y = FIELD_COUNT_X;
const MARGIN_LEFT = 16;
const MARGIN_TOP = 42;
const HEADING_COLOR = ['#FF7070', '#7070FF']; // for each player
const FIELD_LINE_COLOR = '#FFFFFF';
const FIELD_BG_COLOR_REGULAR = '#808080';
const FIELD_BG_COLOR_SELECTED = '#FFFFFF';
const SHIP_COLOR_PLACED = '#507090';
const SHIP_COLOR_AVAIL = '#204070';
const STATE_HIT_COLOR = ['#B00000', '#0000B0']; // for each player
const STATE_MISS_COLOR = '#404040';
const SHIP_CAPS = [
  1, // Carrier (type 0, size 5)
  2, // Battleship (type 1, size 4)
  3, // Destroyer (type 2, size 3)
  4 // Patrol Boat (type 3, size 2)
];
const FULL_HITS = SHIP_CAPS.reduce((a, c, i) => a + c*(5 -i), 0);
const INDICATOR_LAYOUT = [
  [0, 1, 1, 3],
  [2, 2, 2, 3, 3, 3]
];
const INDICATORS = INDICATOR_LAYOUT.reduce((a, c, i) => {
  let y = FIELD_COUNT_Y + 1 + i;
  let x1 = 0;
  c.forEach(type => {
    let size = 5 - type;
    let x2 = x1 + size - 1;
    a.push({ "type": type, "position": [x1, y, x2, y] });
    x1 += size;
  });
  return a;
}, []).sort((l, r) => (l.type - r.type)*FIELD_COUNT_X*FIELD_COUNT_Y
            + (l.position[0] + l.position[1]*FIELD_COUNT_X
               - (r.position[0] + r.position[1]*FIELD_COUNT_X)));

let phase = 0;
let player = 0;
let selected = [-10, -10];
let to_add = null;
let to_rem = null;
let placements = [[],[]];
let field_states = [new Array(100).fill(0), new Array(FIELD_COUNT_X*FIELD_COUNT_Y).fill(0)];
let current = [[0, 0],[0, 0]];
let behaviours = []; // depending on phase

function getLeftOffset(x) {
  return MARGIN_LEFT + x*(FIELD_WIDTH[phase] + FIELD_MARGIN + 1);
}

function getTopOffset(y) {
  return MARGIN_TOP + y*(FIELD_HEIGHT[phase] + FIELD_MARGIN + 1);
}

function getFieldState(x, y) {
  return field_states[player][x + FIELD_COUNT_X*y];
}

function setFieldState(x, y, value) {
  field_states[player][x + FIELD_COUNT_X*y] = value;
}

function updateFieldStates() {
  placements.forEach((ps, i) => {
    ps.forEach(p => {
      let pos = p.position;
      for (let x = pos[0]; x <= pos[2]; x++)
      for (let y = pos[1]; y <= pos[3]; y++) {
        field_states[i][x + FIELD_COUNT_X*y] = 1;
      }
    });
  });
}

function getHitCount() {
  return field_states[player].reduce(
    (v, state) => state == 3 ? v + 1 : v,
    0);
}

function drawField(x, y, selected) {
  let x1 = getLeftOffset(x);
  let y1 = getTopOffset(y);
  let x2 = x1 + FIELD_WIDTH[phase];
  let y2 = y1 + FIELD_HEIGHT[phase];
  let field_state = getFieldState(x, y);
  g.setColor(selected ? FIELD_BG_COLOR_SELECTED : FIELD_BG_COLOR_REGULAR);
  g.fillRect(x1, y1, x2, y2);
  g.setColor(FIELD_LINE_COLOR);
  g.drawRect(x1, y1, x2, y2);
  switch (field_state) {
    case 2:
      g.setColor(STATE_MISS_COLOR);
      g.fillCircle(x1 + FIELD_WIDTH[phase]/2 + 1, y1 + FIELD_HEIGHT[phase]/2 + 1, FIELD_WIDTH[phase]/2 - 3);
      break;
    case 3:
      g.setColor(STATE_HIT_COLOR[player]);
      g.fillCircle(x1 + FIELD_WIDTH[phase]/2 + 1, y1 + FIELD_HEIGHT[phase]/2 + 1, FIELD_WIDTH[phase]/2 - 1);
      break;
    default:
      break;
  }
}

function drawFields(x1, y1, x2, y2) {
  let l = getLeftOffset(x1);
  let t = getTopOffset(y1);
  let r = getLeftOffset(x2) + FIELD_WIDTH[phase] + FIELD_MARGIN;
  let b = getTopOffset(y2) + FIELD_HEIGHT[phase] + FIELD_MARGIN;
  g.clearRect(l, t, r, b);
  for (let x = x1; x <= x2; x++)
  for (let y = y1; y <= y2; y++) {
    drawField(x, y, x == current[player][0] && y == current[player][1]);
  }
}

function drawShip(x1, y1, x2, y2, color) {
  g.setColor(color);
  let diam = Math.min(FIELD_HEIGHT[phase], FIELD_WIDTH[phase]) - 3;
  let rad = diam/2;
  let cx1 = getLeftOffset(x1) + FIELD_WIDTH[phase]/2 + 1;
  let cy1 = getTopOffset(y1) + FIELD_HEIGHT[phase]/2 + 1;
  let cx2 = getLeftOffset(x2) + FIELD_WIDTH[phase]/2 + 1;
  let cy2 = getTopOffset(y2) + FIELD_HEIGHT[phase]/2 + 1;
  if (x1 == x2) {
    g.fillRect(cx1 - rad, cy1, cx1 + rad, cy2);
  } else {
    g.fillRect(cx1, cy1 - rad, cx2, cy1 + rad);
  }
  g.fillCircle(cx1, cy1, rad);
  g.fillCircle(cx2, cy2, rad);
}

function hasCollision(pos) {
  return placements[player].some(
    p => pos[0] <= p.position[2]
      && pos[2] >= p.position[0]
      && pos[1] <= p.position[3]
      && pos[3] >= p.position[1]);
}

function isAvailable(type) {
  let count = placements[player].reduce(
    (v, p) => p.type == type ? v + 1 : v,
    0);
  return count < SHIP_CAPS[type];
}

function determineChanges() {
  to_rem = to_add;
  to_add = null;
  if (selected[0] == current[player][0] && selected[1] == current[player][1]) return;
  if (selected[0] == current[player][0]) {
    let size = Math.abs(selected[1] - current[player][1]) + 1;
    if (size < 2 || size > 5 ) return;
    let y1 = Math.min(selected[1], current[player][1]);
    let y2 = Math.max(selected[1], current[player][1]);
    let pos = [current[player][0], y1, current[player][0], y2];
    let type = 5 - size;
    if (!hasCollision(pos) && isAvailable(type)) {
      to_add = { "type": type, "position": pos };
    }
  }
  if (selected[1] == current[player][1]) {
    let size = Math.abs(selected[0] - current[player][0]) + 1;
    if (size < 2 || size > 5 ) return;
    let x1 = Math.min(selected[0], current[player][0]);
    let x2 = Math.max(selected[0], current[player][0]);
    let pos = [x1, current[player][1], x2, current[player][1]];
    let type = 5 - size;
    if (!hasCollision(pos) && isAvailable(type)) {
      to_add = { "type": type, "position": pos };
    }
  }
}

function addPlacement(descriptor) {
  placements[player].push(descriptor);
  placements[player].sort((l, r) => l.type - r.type);
}

function drawShipPlacements() {
  if (to_rem) {
    drawFields.apply(null, to_rem.position);
  }
  placements[player].forEach(
    p => drawShip.apply(null, p.position.concat([SHIP_COLOR_PLACED])));
  if (to_add) {
    drawShip.apply(null, to_add.position.concat([SHIP_COLOR_PLACED]));
  }
}

function drawShipIndicator() {
  let p = to_add
    ? placements[player].concat(to_add).sort((l, r) => l.type - r.type)
    : placements[player];
  let pi = 0;
  INDICATORS.forEach(indicator => {
    let color = SHIP_COLOR_AVAIL;
    if (pi < p.length && p[pi].type == indicator.type) {
        pi += 1;
        color = SHIP_COLOR_PLACED;
    }
    drawShip.apply(null, indicator.position.concat(color));
  });
}

function drawHeading(text) {
  g.clearRect(0, 20, 100, 32);
  g.setColor(HEADING_COLOR[player]);
  g.setFont('4x6', 2.8);
  g.drawString(text, MARGIN_LEFT, 20);
}

function reset() {
  g.clear();
  drawHeading('Player ' + (player + 1));
  drawFields(0, 0, 9, 9);
}

function showResults() {
  let text1 = 'Player ' + (player + 1) + ' won!';
  let text2 = 'Congratulations!';
  g.clear();
  g.clearRect(0, 20, 100, 32);
  g.setColor(HEADING_COLOR[player]);
  g.setFont('Vector', 20);
  g.drawString(text1, MARGIN_LEFT, 80);
  g.drawString(text2, MARGIN_LEFT, 120);
}

function moveSelection(dx, dy) {
  let x = current[player][0];
  let y = current[player][1];
  drawField(x, y, false);
  current[player][0] = x = (x + dx + FIELD_COUNT_X)%FIELD_COUNT_X;
  current[player][1] = y = (y + dy + FIELD_COUNT_Y)%FIELD_COUNT_Y;
  drawField(x, y, true);
}

behaviours.push({
  "move": (dx, dy) => {
    moveSelection(dx, dy);
    determineChanges();
    drawShipPlacements();
    drawShipIndicator();
  },
  "action": _ => {
    if (to_add) {
      addPlacement(to_add);
      to_add = null;
      selected = [-10, -10];
      if (placements[player].length == 10) {
        behaviours[phase].transition();
      }
    } else {
      selected = [current[player][0], current[player][1]];
    }
  },
  "transition": _ => {
    current[0] = [0, 0];
    player = 1;
    phase = 1;
    reset();
    drawShipIndicator();
  }
});

behaviours.push({
  "move": behaviours[0].move,
  "action": behaviours[0].action,
  "transition": _ => {
    current[1] = [0, 0];
    player = 0;
    phase = 2;
    updateFieldStates();
    reset();
  }
});

behaviours.push({
  "move": (dx, dy) => moveSelection(dx, dy),
  "action": _ => {
    let x = current[player][0];
    let y = current[player][1];
    let field_state = getFieldState(x, y);
    if (field_state > 1) return;
    setFieldState(x, y, field_state + 2);
    drawField(x, y, true);
    Bangle.buzz(200 + field_state*800, 0.5 + field_state*0.5);
    if (getHitCount() < FULL_HITS) {
      player = (player + 1)%2;
      setTimeout(reset, 1000);
    } else {
      setTimeout(behaviours[phase].transition, 1000);
    }
  },
  "transition": _ => {
    phase = 3;
    showResults();
  }
});

behaviours.push({
  "move": _ => {},
  "action": _ => {}
});

reset();
drawShipIndicator();

setWatch(_ => behaviours[phase].move(0, -1), BTN1, {repeat: true, debounce: 100});
setWatch(_ => behaviours[phase].move(0, 1), BTN3, {repeat: true, debounce: 100});
setWatch(_ => behaviours[phase].move(-1, 0), BTN4, {repeat: true, debounce: 100});
setWatch(_ => behaviours[phase].move(1, 0), BTN5, {repeat: true, debounce: 100});
setWatch(_ => behaviours[phase].action(), BTN2, {repeat: true, debounce: 100});
