/*
   Code for tracking Life Total, Poison Counters, and Commander Damage
      in EDH/Commander. Uses a SSD1306 OLED Display and a rotary encoder
         with push-button.
*/
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <RotaryEncoder.h>
#include "Counter.h"         // Class for tracking counters
#include "Splash.h"

// U8G2_SSD1306_128X64_NONAME_F_HW_I2C(<rotation>, [reset [, clock, data]]); For full framebuffer mode
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// HW Pins required
#define PIN_BTN   13 // RotaryEncoder SW Pin
#define PIN_ROTA  14 // RotaryEncoder DT Pin
#define PIN_ROTB  15 // RotaryEncoder CLK Pin
#define PIN_LED   25 // Builtin LED

RotaryEncoder encoder(PIN_ROTA, PIN_ROTB, RotaryEncoder::LatchMode::FOUR3);

#define MAX_OPPONENTS 5

Counter lifeTotal("Life Total", 40, 0, 100, 0, true);
Counter poisonCounters("Poison Counters", 0, 0, 10, 10);
Counter* commanderDamage = nullptr; // Placeholder before selecting number of opponents

Counter** counters = nullptr;  // Pointer to an array of pointers to Counter objects
uint8_t numCounters = 2;  // Initially, we have lifeTotal and poisonCounters
uint8_t currentCounterIndex = 0;
uint8_t numOpponents = 0;

// For non-blocking button handling
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 200;  // Debounce delay in milliseconds

// Function prototypes
int8_t handleRotation();
void selectNumOpponents();
void setupCommanderDamageCounters();
void displayCounter();

// Setup
void setup() {
  // Initialize button
  pinMode(PIN_BTN, INPUT_PULLUP);
  pinMode(PIN_LED, OUTPUT);

  // Initialize display
  display.begin();
  display.clearBuffer();
  display.drawBitmap(0, 0, 64/8, 64, splash_data);
  display.setFont(u8g2_font_luBIS08_tf);
  display.setCursor(64, 20);
  display.println("Commander");
  display.println("Tracker");
  display.sendBuffer();
  delay(3000); 
  display.clear();
  display.setFont(u8g2_font_ncenB08_tr); // Choose a suitable font
  display.drawStr(0, 12, "Select Opponents");
  display.setFont(u8g2_font_ncenB14_tr);
  display.setCursor(0, 30);
  display.print(numOpponents);
  display.sendBuffer();

  // Select number of opponents
  selectNumOpponents();

  // Display the initial counter
  displayCounter();
}

// Main loop
void loop() {
  int8_t rotation = handleRotation();
  if (rotation != 0) {
    if (rotation == 1) {
      // Update the current counter based on rotation direction
      counters[currentCounterIndex]->increment();
    } else if (rotation == -1) {
      counters[currentCounterIndex]->decrement();
    }
    displayCounter();
  }

  if (digitalRead(PIN_BTN) == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    lastDebounceTime = millis();
    currentCounterIndex = (currentCounterIndex + 1) % numCounters;
    displayCounter();
  }
}

// Non-blocking rotation handling
int8_t handleRotation() {
  encoder.tick();
  switch (encoder.getDirection()) {
    case RotaryEncoder::Direction::CLOCKWISE:
      return 1;
    case RotaryEncoder::Direction::COUNTERCLOCKWISE:
      return -1;
    case RotaryEncoder::Direction::NOROTATION:
    default:
      return 0;
  }
}

// Loop for selecting number of opponents
void selectNumOpponents() {
  bool selectingOpponents = true;

  while (selectingOpponents) {
    int8_t rotation = handleRotation();
    if (rotation != 0) {
      if (rotation == 1 && numOpponents < MAX_OPPONENTS) {
        numOpponents++;
      } else if (rotation == -1 && numOpponents > 0) {
        numOpponents--;
      }

      display.clearBuffer();
      display.setFont(u8g2_font_ncenB08_tr);
      display.drawStr(0, 12, "Select Opponents");
      display.setFont(u8g2_font_ncenB14_tr);
      display.setCursor(0, 30);
      display.print(numOpponents);
      display.sendBuffer();
    }

    // Check for button press to confirm the selection
    if (digitalRead(PIN_BTN) == LOW && (millis() - lastDebounceTime) > debounceDelay) {
      lastDebounceTime = millis();
      selectingOpponents = false;
    }
  }
  // Apply opponents after selection
  setupCommanderDamageCounters();
}

void setupCommanderDamageCounters() {
  numCounters = 2 + numOpponents;  // Update total number of counters

  // Allocate memory for all counters, including lifeTotal, poisonCounters, and commanderDamage counters
  counters = new Counter*[numCounters];

  // Assign lifeTotal and poisonCounters to the counters array
  counters[0] = &lifeTotal;
  counters[1] = &poisonCounters;

  static char names[MAX_OPPONENTS][12]; // Name buffer

  // Allocate memory for commander damage counters and assign them to the counters array
  commanderDamage = new Counter[numOpponents];
  for (uint8_t i = 0; i < numOpponents; i++) {
    snprintf(names[i], sizeof(names[i]), "Cmdr Dmg %d", i + 1); // Safely format the name into the buffer
    commanderDamage[i] = Counter(names[i], 0, 0, 100, 21);  // Initialize each with commander damage rules
    counters[2 + i] = &commanderDamage[i];  // Assign to the counters array
  }
}

// Function to check if any counter is dead
bool checkIfAnyCounterIsDead() {
  for (uint8_t i = 0; i < numCounters; i++) {
    if (counters[i]->isDead) {
      return true;
    }
  }
  return false;
}

void displayCounter() {
  display.clearBuffer();

  Counter* counter = counters[currentCounterIndex];

  display.setFont(u8g2_font_ncenB08_tr);
  display.setCursor(0, 12);
  display.print(counter->getName());

  display.setFont(u8g2_font_ncenB14_tr);  // Larger text size for the counter value
  display.setCursor(0, 30);
  display.print(counter->currentCount);

  display.sendBuffer();  // Update the display with the new information

  // Check if any of the counters are lethal and power LED if so
  if (checkIfAnyCounterIsDead()) {
    digitalWrite(PIN_LED, HIGH);  // Turn on LED if any counter is dead
  } else {
    digitalWrite(PIN_LED, LOW);   // Turn off LED if no counter is dead
  }
}
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT
pico:GP0
pico:GP1
pico:GND.1
pico:GP2
pico:GP3
pico:GP4
pico:GP5
pico:GND.2
pico:GP6
pico:GP7
pico:GP8
pico:GP9
pico:GND.3
pico:GP10
pico:GP11
pico:GP12
pico:GP13
pico:GND.4
pico:GP14
pico:GP15
pico:GP16
pico:GP17
pico:GND.5
pico:GP18
pico:GP19
pico:GP20
pico:GP21
pico:GND.6
pico:GP22
pico:RUN
pico:GP26
pico:GP27
pico:GND.7
pico:GP28
pico:ADC_VREF
pico:3V3
pico:3V3_EN
pico:GND.8
pico:VSYS
pico:VBUS
oled1:GND
oled1:VCC
oled1:SCL
oled1:SDA
encoder1:CLK
encoder1:DT
encoder1:SW
encoder1:VCC
encoder1:GND