#include <Streaming.h>
#include <SPI.h>
#include <Adafruit_ILI9341.h>
#include <Button_SL.hpp>

Print &cout {Serial};

class Player {
public:
  Player(unsigned int color) : color {color} {}

  void setDiceValue(byte value) {
    diceValue = value;
    sum += value;
  }
  byte getDiceValue() const { return diceValue; }
  unsigned int getColor() const { return color; }
  unsigned int getSum() const { return sum; }

private:
  unsigned int color;
  byte diceValue;
  unsigned int sum;
};

struct DicePip {
  unsigned int xPos;
  unsigned int yPos;
  unsigned int radius;
};

// Display coordinates. Must be adjusted for the respective display resolution.
constexpr DicePip DICE_PIPS[] {
    {280, 40,  20}, // dot top right
    {280, 200, 20}, // dot bottom right
    {160, 40,  20}, // dot top middle
    {160, 120, 20}, // dot middle middle (1)
    {160, 200, 20}, // dot bottom middle
    {40,  40,  20}, // dot top left
    {40,  200, 20}  // dot bottom left
};

// Maps the current number of "dice pip count" to the drawing coordinates in the DicePip structure.
constexpr byte PIP_VALS[6][6] {
    {3}, // 1
    {0, 6}, // 2
    {0, 3, 6}, // 3
    {0, 1, 5, 6}, // 4
    {0, 1, 3, 5, 6}, // 5
    {0, 1, 2, 4, 5, 6}  // 6
};

constexpr byte MIN_DICE_VALUE {1};
constexpr byte MAX_DICE_VALUE {6};
constexpr byte MIN_ANIMATION_COUNT {6};
constexpr byte MAX_ANIMATION_COUNT {24};
constexpr byte ANIMATIONDELAY_MS {50};
constexpr unsigned int BG_COLOR {ILI9341_BLACK};
constexpr unsigned int TXT_COLOR {ILI9341_WHITE};
constexpr unsigned int COLORS[4] {ILI9341_RED, ILI9341_BLUE, ILI9341_YELLOW, ILI9341_GREEN};

const char *START_TEXT[3] {"Start", "->", "Taste"};
byte BUTTON_PIN {2};
constexpr byte PIN_ADC {A0};

Adafruit_ILI9341 tft = Adafruit_ILI9341(9, 10);

// Initialize the player objects for the four possible player colours
Player player[4] {ILI9341_RED, ILI9341_BLUE, ILI9341_YELLOW, ILI9341_GREEN};

// The playerSelect variable determines which player colors have been selected
// and in which order they are played.
byte playerSelect[] {0, 1, 3};   // Init for three player (red, blue, green)
// byte playerSelect[] {0, 2};      // Init for two player (red,yellow)
// byte playerSelect[] {0, 1, 2, 3};  // Init for four player (all colors)
constexpr byte MAX_PLAYER {sizeof(playerSelect) / sizeof(playerSelect[0])};

// Bounce initialisieren
Btn::Button diceBtn(BUTTON_PIN);

void deletePips() {
  const byte idx {MAX_DICE_VALUE-1};
  tft.fillCircle(DICE_PIPS[PIP_VALS[0][0]].xPos, DICE_PIPS[PIP_VALS[0][0]].yPos, DICE_PIPS[PIP_VALS[0][0]].radius,
                 BG_COLOR);
  for (byte i = 0; i < MAX_DICE_VALUE; ++i) {
    tft.fillCircle(DICE_PIPS[PIP_VALS[idx][i]].xPos, DICE_PIPS[PIP_VALS[idx][i]].yPos,
                   DICE_PIPS[PIP_VALS[idx][i]].radius, BG_COLOR);
  }
}

void drawPips(byte number, unsigned int color, bool doShow = true) {
  byte idx = number - 1;
  for (byte i = 0; i < number; ++i) {
    tft.fillCircle(DICE_PIPS[PIP_VALS[idx][i]].xPos, DICE_PIPS[PIP_VALS[idx][i]].yPos,
                   DICE_PIPS[PIP_VALS[idx][i]].radius, color);
  }
  delay(100);
  if (false == doShow) { deletePips(); }
}

void showFrame(unsigned int color) {
  tft.drawRoundRect(3, 2, tft.width() - 6, tft.height() - 6, 10, color);
  tft.drawRoundRect(5, 4, tft.width() - 10, tft.height() - 10, 10, color);
}

void showHint(unsigned int yPos, unsigned int yOffset) {
  showFrame(TXT_COLOR);
  tft.setCursor(0, 0);
  tft.setTextColor(TXT_COLOR);
  tft.setTextSize(4);
  for (auto item : START_TEXT) {
    tft.setCursor((tft.width() - (strlen(item) * 24)) / 2, yPos);
    tft.println(item);
    yPos += yOffset;
  }
}

void setup() {
  Serial.begin(115200);
  diceBtn.begin();   // Assign the button to the bounce object
  tft.begin();
  tft.setRotation(1);
  showHint(50, 50);   // yPos, yOffset of Text
}

void doDice(Player &player) {
  byte diceValue {MIN_DICE_VALUE};
  randomSeed(analogRead(PIN_ADC));
  byte animationCount = static_cast<byte>(random(MIN_ANIMATION_COUNT, (MAX_ANIMATION_COUNT + 1)));
  for (byte i = 0; i < animationCount; ++i) {   // Dice effect: Display random numbers in quick succession.
    drawPips(diceValue, player.getColor(), false);
    diceValue = (diceValue < MAX_DICE_VALUE) ? diceValue + 1 : 1;
    delay(ANIMATIONDELAY_MS);
  }
  player.setDiceValue(static_cast<byte>(random(MIN_DICE_VALUE, (MAX_DICE_VALUE + 1))));
  drawPips(player.getDiceValue(), player.getColor(), true);
}

void loop() {
  static byte index {0};   // Index to the player
  static bool isStarted {false};

  if (diceBtn.tick()) {
    if (false == isStarted) {
      tft.fillRect(50, 50, tft.width() - 100, tft.height() - 100, BG_COLOR);   // Clear Screen
      showFrame(player[playerSelect[index]].getColor());
      isStarted = true;
      delay(1000);
    } else {
      deletePips();
    }
    doDice(player[playerSelect[index]]);
    
    cout << F("Player: ") << index + 1 << F(" ") << F("Pips: ") <<
    player[playerSelect[index]].getDiceValue() << F(" ") << F("Pip sum: ") <<
    _WIDTH(player[playerSelect[index]].getSum(),5) << endl;
 
    // If a six is rolled, do not change Player.
    if (player[playerSelect[index]].getDiceValue() != MAX_DICE_VALUE) {
      index = (index + 1) % MAX_PLAYER;
      if (0 == index) { cout << F("--------------------------------\n"); }
    }   
    showFrame(player[playerSelect[index]].getColor());
  }
}