#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Fonts/TomThumb.h>
#ifndef PSTR
 #define PSTR // Make Arduino Due happy
#endif

#define MTRX_PIN 8
#define BTN_A_PIN 3
#define J_X A1
#define J_Y A0
#define J_SW 7

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(16, 16, MTRX_PIN ,
  NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
  NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
  NEO_GRB            + NEO_KHZ800);

const uint16_t colors[7] = {
  // 0 red
  matrix.Color(255, 0, 0),
  // 1 green
  matrix.Color(0, 255, 0),
  // 2 blue
  matrix.Color(0, 0, 255),
  // 3 border
  matrix.Color(100, 120, 100),
  // 4 enemy
  matrix.Color(230, 57, 70),
  // 5 friendly
  matrix.Color(80, 140, 207),
  // 6 margin
  matrix.Color(115, 27, 35),
};

const short CELL_EMPTY = 0;
const short CELL_BORDER = 1;
const short CELL_SHIP = 2;
const short CELL_HIT = 3;
const short CELL_MISS = 5;

short GAME_MODE = 0;
const short GAME_SET_SHIPS = 1;
const short GAME_PLAYER_TURN = 2;
const short GAME_ENEMY_TURN = 3;
const short GAME_END = 4;

const uint16_t gameColors[6] = {
  // 0 empty
  matrix.Color(100, 0, 0),
  // 1 border
  matrix.Color(0, 10, 0),
  // 2 ship
  matrix.Color(80, 140, 207),
  // 3 hit or invalid
  matrix.Color(230, 57, 70),
  // 4 hit or invalid
  matrix.Color(230, 57, 70),
  // 5 miss
  matrix.Color(255, 255, 0),
};

const int OFFSET = 3;
const int JOYSTICK_AXIS_DELAY = 30;

bool btn_a = false;

int j_read_x = 0;
int j_read_y = 0;
int j_read_sw = 0;

int x = 0;
int y = 0;

bool joystickValUpdated = false;
bool shipVertical = false;

bool transition = false;
String text[10] = "";
short textIndex = 0;
// const String texts[5] = {
//   "",
//   "GO!",
//   "FOE",
//   "HIT",
//   "MISS"
// };
const short TEXT_GO = 1;
const short TEXT_FOE = 2;
const short TEXT_HIT = 3;
const short TEXT_MISS = 4;

bool logShipPosition = false;

//// Ships Matrix
// All cells have a number with meaning
// 0 - empty
// 1 - ship border (transparent blue)
// 2 - ship (blue)
// 3 - invalid ship position or hit (red)
// 4 - invalid ship position or hit (red)
short ownShips[10][10] = {};
short enemyShips[10][10] = {};
short ownShots[10][10] = {};
short displayMatrix[10][10] = {};
// temp
short newShipMargin[10][10] = {};
short currentNewShipIndex = 9;
short ownShipsHitPoints[10][2] = {
  {4, 4},
  {3, 3},
  {3, 3},
  {2, 2},
  {2, 2},
  {2, 2},
  {1, 1},
  {1, 1},
  {1, 1},
  {1, 1},
};

bool newShipPositionValid = true;

void fillNewShip() {
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int endX = shipVertical ? x : x - 1 + shipSize;
  int endY = !shipVertical ? y : y - 1 + shipSize;
  if (shipVertical) {
    for (short i = y; i <= endY; i++) {
      ownShips[i][x] = CELL_SHIP;
    }
  } else {
    for (short i = x; i <= endX; i++) {
      ownShips[y][i] = CELL_SHIP;
      Serial.println("fillNewShip");
    }
  }

  currentNewShipIndex++;
  Serial.print("currentNewShipIndex: ");
  Serial.println(currentNewShipIndex);
  if (currentNewShipIndex > 9) {
    GAME_MODE = GAME_PLAYER_TURN;
    transition = true;
    textIndex = TEXT_GO;
    Serial.println("GAME_MODE: GAME_PLAYER_TURN");
    currentNewShipIndex = 0;
  }
}

void rotateShip() {
  shipVertical = !shipVertical;
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int startX = x;
  int startY = y;
  int endX = shipVertical ? startX : startX - 1 + shipSize;
  int endY = !shipVertical ? startY : startY - 1 + shipSize;

  Serial.println("rotateShip; vertical " + (String)shipVertical);
  if (shipVertical) {
    if (endY > 9) {
      y = y - 1;
    }
  } else {
    if (endX > 9) {
      x = x - 1;
    }
  }
  updateShipPosition();
}

// updates position of a ship before it being added
void updateShipPosition() {
  // Serial.println("updateShipPosition index "  + currentNewShipIndex);
  Serial.print("updateShipPosition xy");
  joystickValUpdated = false;
  // Serial.print(currentNewShipIndex);
  Serial.print(x);
  Serial.println(y);

  newShipPositionValid = true;
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int startX = x;
  int startY = y;
  int endX = shipVertical ? startX : startX - 1 + shipSize;
  int endY = !shipVertical ? startY : startY - 1 + shipSize;

  // clean temp array
  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      newShipMargin[row][col] = CELL_EMPTY;
    }
  }
  if (shipVertical) {
    // vertical
    short lowEdgeX = max(startX - 1, 0);
    short highEdgeX = min(endX + 1, 9);
    short lowEdgeY = max(startY - 1, 0);
    short highEdgeY = min(startY + 1, 9);

    for (short i = lowEdgeY; i <= highEdgeY; i++) {
      newShipMargin[i][lowEdgeX] = CELL_BORDER;
      newShipMargin[i][highEdgeX] = CELL_BORDER;
    }
    newShipMargin[max(startY - 1, 0)][startX] = CELL_BORDER;
    newShipMargin[min(endY + 1, 9)][startX] = CELL_BORDER;

    // ship
    for (short i = startY; i <= endY; i++) {
      newShipMargin[i][startX] = CELL_SHIP;
    }
  } else {
    // horizontal
    short lowEdgeX = max(startX - 1, 0);
    short highEdgeX = min(endX + 1, 9);
    short lowEdgeY = max(startY - 1, 0);
    short highEdgeY = min(startY + 1, 9);

    for (short i = lowEdgeX; i <= highEdgeX; i++) {
      newShipMargin[lowEdgeY][i] = CELL_BORDER;
      newShipMargin[highEdgeY][i] = CELL_BORDER;
    }
    newShipMargin[startY][max(startX - 1, 0)] = CELL_BORDER;
    newShipMargin[startY][min(endX + 1, 9)] = CELL_BORDER;

    // ship
    for (short i = startX; i <= endX; i++) {
      newShipMargin[startY][i] = CELL_SHIP;
    }
  }

  for(short row = 0; row < 10; row++) {
    // Serial.println("");
    for(short col = 0; col < 10; col++) {
      short cell = ownShips[row][col];
      displayMatrix[row][col] = cell + newShipMargin[row][col];
      // Serial.print(displayMatrix[row][col]);
      if (displayMatrix[row][col] > 2) {
        newShipPositionValid = false;
      }
    }
  }

}

void addNewShip() {
  fillNewShip();
  updateShipPosition();
  delay(500);
}

int processCoordinateChange(int prevValue, int sensorValue, bool sizeByAxisMatters) {
  int newValue = prevValue;
  bool sizeMatters = sizeByAxisMatters && GAME_MODE == GAME_SET_SHIPS;

  if (sensorValue > 900) {
    // increase
    joystickValUpdated = true;
    int maxVal = 9;
    if (sizeMatters) {
      // one point doesn't matter
      maxVal = maxVal + 1 - ownShipsHitPoints[currentNewShipIndex][0];
    }
    newValue = min(++newValue, maxVal);
  } else if (sensorValue < 120) {
    // decrease
    joystickValUpdated = true;
    newValue = max(--newValue, 0);
  }
  return newValue;
}

void updateAimPosition() {
  Serial.println("updateAimPosition");
  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      displayMatrix[row][col] = CELL_EMPTY;
    }
  }
  displayMatrix[y][x] = CELL_HIT;
}

void shoot() {
  Serial.println("SHOT @ " + (String)x + (String)y);
  return;
  if (ownShots[y][x] == CELL_HIT || ownShots[y][x] == CELL_MISS)  {
    // can't shoot on previously shot
    Serial.println("SHOT NOT EMPTY" + (String)ownShots[y][x]);
    return;
  }
  if (enemyShips[y][x] == CELL_SHIP)  {
    ownShots[y][x] = CELL_HIT;
    transition = true;
    textIndex = TEXT_HIT;
    Serial.println("HIT!");
    // player turn continues
  } else {
    ownShots[y][x] = CELL_MISS;
    transition = true;
    textIndex = TEXT_MISS;
    Serial.println("MISS!");
    // enemy turn
  }

  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      short cell = 0 + ownShots[row][col];
      displayMatrix[row][col] = cell;
    }
  }
}

void joystickPosUpdated() {
  if (GAME_MODE == GAME_SET_SHIPS) {
    updateShipPosition();
  } else {
    updateAimPosition();
  }
}

void joystickPressed() {
  if (GAME_MODE == GAME_SET_SHIPS) {
    if (newShipPositionValid) {
      addNewShip();
    }
  } else if (GAME_MODE == GAME_PLAYER_TURN) {
    shoot();
  }
}

void btnAPressed() {
  Serial.println("BTN A PRESSED");
  if (GAME_MODE == GAME_SET_SHIPS) {
    rotateShip();
  }

}

void btnAReleased() {
  Serial.println("BTN A RELEASED");

}

void setup() {
  Serial.begin(9600);
  matrix.begin();
  matrix.setFont(&TomThumb);
  matrix.setTextWrap(false);
  matrix.setBrightness(255);          // was 3, now 255, better for simulation
  matrix.setTextColor(colors[0]);
  matrix.setCursor(0, 0);
  GAME_MODE = GAME_SET_SHIPS;
  pinMode(J_SW, INPUT_PULLUP);
  pinMode(BTN_A_PIN, INPUT);
  updateShipPosition();

  // test enemy ships
  enemyShips[0][0] = CELL_SHIP;
  enemyShips[3][3] = CELL_SHIP;
  enemyShips[5][5] = CELL_SHIP;

  matrix.fillScreen(0);
}

void loop() {
  if (joystickValUpdated) {
    joystickPosUpdated();
    logShipPosition = true;
  }

  bool btnAVal = digitalRead(BTN_A_PIN);
  if (btnAVal && !btn_a) {
    btn_a = true;
    btnAPressed();
  } else if (!btnAVal && btn_a) {
    btn_a = false;
    btnAReleased();
  }

  j_read_sw = digitalRead(J_SW);
  if (j_read_sw == LOW) {
    joystickPressed();
  } else {
    if (joystickValUpdated) {
      joystickValUpdated = false;
      delay(JOYSTICK_AXIS_DELAY);
    } else {
      j_read_x = analogRead(J_X);
      j_read_y = analogRead(J_Y);
      x = processCoordinateChange(x, j_read_x, !shipVertical);
      y = processCoordinateChange(y, j_read_y, shipVertical);
    }
  }

  // top left red
  matrix.drawPixel(0, 0, colors[0]);
  // bottom right green
  matrix.drawPixel(15, 15, colors[1]);
  // bottom left blue
  matrix.drawPixel(0, 15, colors[2]);

  if (transition == true) {
    Serial.println("TRANSITION");
    if (textIndex > 0) {
      Serial.println("TRANSITION TEXT: " + textIndex);
      matrix.setCursor(3, 8);
      matrix.setTextSize(0);
      matrix.setTextColor(gameColors[2]);
      matrix.print("GO!");
      matrix.show();
      delay(2500);
    }
    transition = false;
  } else {
    // border
    matrix.drawRect(0 + OFFSET - 1, 0 + OFFSET - 1, 12, 12, colors[0]);
    if (logShipPosition) {
      //#SHIPS
      for(int row = 0; row < 10; row++) {
        if (logShipPosition) {
          Serial.println("");
          Serial.print(row);
          Serial.print(" ");
        }
        for(int col = 0; col < 10; col++) {
          int cell = 0 + displayMatrix[row][col];
          matrix.drawPixel(col + OFFSET, row + OFFSET, gameColors[cell]);
          if (logShipPosition) {
            Serial.print(displayMatrix[row][col]);
          }
        }
      }
      logShipPosition = false;
    }
    matrix.show();
  }
}