//
// Referring to https://forum.arduino.cc/t/how-to-build-a-byte-from-bits/1313284/36
//
#include <Button_SL.hpp>   // Description: https://github.com/DoImant/Button_SL
#include <Streaming.h>     // Description: https://github.com/janelia-arduino/Streaming
#include <LedControl.h>
Print &cout {Serial};

using namespace Btn;

//
// Global constants
//
constexpr uint8_t PIN_BLE_LEDS[] {22, 23, 24, 25};
constexpr uint8_t PIN_GRN_LEDS[] {37, 38, 39, 40, 41, 42, 43};

constexpr uint8_t BLE_BTN_START {0};
constexpr uint8_t GRN_BTN_START {sizeof(PIN_BLE_LEDS) / sizeof(PIN_BLE_LEDS[0])};
constexpr uint8_t GRN_BTN_END {(GRN_BTN_START + sizeof(PIN_GRN_LEDS) / sizeof(PIN_GRN_LEDS[0])) - 1};

constexpr uint8_t ADJUST_SW_PIN[] {11,10};  // Adj 1 and 2

constexpr uint8_t DIN {6};
constexpr uint8_t CS {7};
constexpr uint8_t CLK {8};

// constexpr uint8_t SMILE[8] {0x3C, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C};
// constexpr uint8_t SAD[8] {0x3C, 0x42, 0xA5, 0x81, 0x99, 0xA5, 0x42, 0x3C};
constexpr uint8_t EMOTION_DATA[2][8] {
  {0x3C, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C},   // smile
  {0x3C, 0x42, 0xA5, 0x81, 0x99, 0xA5, 0x42, 0x3C}    // sad
};

//
// Global variables/ objects
//
enum Emotion : uint8_t {smile, sad, none};

uint8_t stateBleLeds {0};
uint8_t stateGrnLeds {0};

// Define an array of button objects for querying the blue and green buttons.
ButtonSL btnArray[] {{26}, {27}, {28}, {29}, {30}, {31}, {32}, {33}, {34}, {35}, {36}};
LedControl lc(DIN, CLK, CS, 0);

//
// Button query
// Returns number of pressed button or -1 if no button is pressed
//
template <size_t N> int8_t checkButtons(ButtonSL (&btns)[N]) {
  for (uint8_t i = 0; i < N; ++i) {
    if (btns[i].tick() != ButtonState::notPressed) { return i; }
  }
  return -1;
}

//
// Switch the LEDs depending on the set bits
// of the status byte for the respective LED color
//
template <size_t N> void switchLeds(const uint8_t (&pin)[N], uint16_t bitMask) {
  for (size_t bits {0}; bits < N; ++bits) { digitalWrite(pin[bits], bitMask & (1 << bits)); }
}

//
// Set the bits in the status byte for the LEDs
//                                                                        
uint8_t getLedState(uint8_t state, uint8_t bitMask) { 
  //     If bit already set  delete it           otherwise set it
  return (state & bitMask) ? state &= ~bitMask : state |= bitMask; 
}

//
// Set a given pattern on the LED Dot Matrix
// 
template <size_t N> void setDotMatrix(const uint8_t (&pattern)[N] ) { 
  for (size_t i = 0; i < N; i++) { lc.setRow(0, i, pattern[i]); }
}

void setup() {
  Serial.begin(115200);
  // INPUTS
  //  Init Buttons
  for (auto &btn : btnArray) {
    btn.begin();
    btn.setDebounceTime_ms(40);
  }
  for (auto &pin : ADJUST_SW_PIN) { pinMode(pin, INPUT_PULLUP); }

  // OUTPUTS
  for (auto &pin : PIN_GRN_LEDS) { pinMode(pin, OUTPUT); }
  for (auto &pin : PIN_BLE_LEDS) { pinMode(pin, OUTPUT); }
  cout << "Start..." << endl;
  setDotMatrix(EMOTION_DATA[Emotion::sad]);
}

void loop() {
  static uint8_t prevEmotion {Emotion::none};

  int8_t btnNum = checkButtons(btnArray);
  switch (btnNum) {
    case BLE_BTN_START ... GRN_BTN_START - 1:   // Blue buttons array index 0 ... 3
      stateBleLeds = getLedState(stateBleLeds, 1 << btnNum); // Shift bit x (btnNum) positions to the left
      switchLeds(PIN_BLE_LEDS, stateBleLeds);
      cout << " Blue Button Nr: " << _WIDTH(btnNum, 2) << " ";
      cout << " Blue LED Status: " << _WIDTHZ(_BIN(stateBleLeds), 8) << " 0x" << _WIDTHZ(_HEX(stateBleLeds),2) << endl;
      break;
    case GRN_BTN_START ... GRN_BTN_END:   // Green buttons array index 4 ... 10
    { // The curly bracket block is necessary if a new (local) variable is defined within the switch statement.
      uint8_t greenBtnNum = btnNum - GRN_BTN_START;  // Subtract the offset so that the button number starts at 0
      stateGrnLeds = getLedState(stateGrnLeds, 1 << greenBtnNum);
      switchLeds(PIN_GRN_LEDS, stateGrnLeds);
      cout << "Green Button Nr: " << _WIDTH(greenBtnNum, 2) << " ";
      cout << "Green LED Status: " << _WIDTHZ(_BIN(stateGrnLeds), 8) << " 0x" << _WIDTHZ(_HEX(stateGrnLeds),2) << endl;
    } break;
    default: break;
  }
  
  uint8_t emotion = (!digitalRead(ADJUST_SW_PIN[smile]) && stateBleLeds == 0x01 && stateGrnLeds == 0x40) ? Emotion::smile : Emotion::sad;
  if (emotion != prevEmotion) {
    prevEmotion == emotion;
    setDotMatrix(EMOTION_DATA[emotion]);
  }
}