/*
This sketch demonstrates controlling the Arduboy's RGB LED,
in both analog and digital modes.
*/

/*
To the extent possible under law, Scott Allen has waived all copyright and
related or neighboring rights to this BeepDemo program.
*/

#include <Arduboy2.h>

// The frame rate determines the button auto-repeat rate
#define FRAME_RATE 25

// The increment/decrement amount when auto-repeating
#define REPEAT_AMOUNT 3

// Delay time before button auto-repeat starts, in milliseconds
#define REPEAT_DELAY 700

// Calculation of the number of frames to wait before button auto-repeat starts
#define DELAY_FRAMES (REPEAT_DELAY / (1000 / FRAME_RATE))

#define ANALOG false
#define DIGITAL true

#define ANALOG_MAX 255

// Color array index
enum class Color {
  RED,
  GREEN,
  BLUE,
  COUNT
};

// Map LED color index to LED name
const byte LEDpin[(byte)(Color::COUNT)] = {
  RED_LED,
  GREEN_LED,
  BLUE_LED
};

Arduboy2 arduboy;

// Analog LED values
byte analogValue[3] = { 0, 0, 0};
// Digital LED states
byte digitalState[3] = { RGB_OFF, RGB_OFF, RGB_OFF };

byte analogSelected = (byte)(Color::RED);
byte digitalSelected = (byte)(Color::RED);

boolean controlMode = ANALOG;

// Button repeat handling
unsigned int delayCount = 0;
boolean repeating = false;

// ============================= SETUP ===================================
void setup() {
  arduboy.begin();
  arduboy.setFrameRate(FRAME_RATE);
  analogSet();
}
// =======================================================================


// =========================== MAIN LOOP =================================
void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }

  arduboy.pollButtons();

  // Toggle analog/digital control mode
  if (arduboy.justPressed(A_BUTTON)) {
    if ((controlMode = !controlMode) == DIGITAL) {
      arduboy.freeRGBled();
      digitalSet();
    }
    else {
      analogSet();
    }
  }

  // Reset to Analog mode and all LEDs off
  if (arduboy.justPressed(B_BUTTON)) {
    reset();
  }

  // Handle D-pad buttons for current mode
  if (controlMode == ANALOG) {
    modeAnalog();
  }
  else {
    modeDigital();
  }

  // Handle delay before button auto-repeat starts
  if ((delayCount != 0) && (--delayCount == 0)) {
    repeating = true;
  }

  renderScreen(); // Render and display the entire screen
}
// =======================================================================


// Analog control
void modeAnalog() {
  if (arduboy.justPressed(RIGHT_BUTTON)) {
    valueInc(1);
    startButtonDelay();
  }
  else if (arduboy.justPressed(LEFT_BUTTON)) {
    valueDec(1);
    startButtonDelay();
  }
  else if (repeating && arduboy.pressed(RIGHT_BUTTON)) {
    valueInc(REPEAT_AMOUNT);
  }
  else if (repeating && arduboy.pressed(LEFT_BUTTON)) {
    valueDec(REPEAT_AMOUNT);
  }
  else if (arduboy.justPressed(DOWN_BUTTON)) {
    analogSelectInc();
  }
  else if (arduboy.justPressed(UP_BUTTON)) {
    analogSelectDec();
  }
  else if (repeating) {
    stopButtonRepeat();
  }
}

// Digital control
void modeDigital() {
  if (arduboy.justPressed(RIGHT_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) {
    digitalState[digitalSelected] = (digitalState[digitalSelected] == RGB_ON) ?
                                     RGB_OFF : RGB_ON;
    arduboy.digitalWriteRGB(LEDpin[digitalSelected],
                            digitalState[digitalSelected]);
  }
  else if (arduboy.justPressed(DOWN_BUTTON)) {
    digitalSelectInc();
  }
  else if (arduboy.justPressed(UP_BUTTON)) {
    digitalSelectDec();
  }
}

// Reset to analog mode and turn all LEDs off
void reset() {
  digitalState[(byte)(Color::RED)] = RGB_OFF;
  digitalState[(byte)(Color::GREEN)] = RGB_OFF;
  digitalState[(byte)(Color::BLUE)] = RGB_OFF;
  digitalSet();

  analogValue[(byte)(Color::RED)] = 0;
  analogValue[(byte)(Color::GREEN)] = 0;
  analogValue[(byte)(Color::BLUE)] = 0;
  analogSet();

  digitalSelected = (byte)(Color::RED);
  analogSelected = (byte)(Color::RED);

  controlMode = ANALOG;
}

// Increment the selected analog LED value by the specified amount
// and update the LED
void valueInc(byte amount) {
  if ((ANALOG_MAX - analogValue[analogSelected]) <= amount) {
    analogValue[analogSelected] = ANALOG_MAX;
  }
  else {
    analogValue[analogSelected] += amount;
  }

  arduboy.setRGBled(LEDpin[analogSelected], analogValue[analogSelected]);
}

// Decrement the selected analog LED value by the specified amount
// and update the LED
void valueDec(byte amount) {
  if (analogValue[analogSelected] <= amount) {
    analogValue[analogSelected] = 0;
  }
  else {
    analogValue[analogSelected] -= amount;
  }

  arduboy.setRGBled(LEDpin[analogSelected], analogValue[analogSelected]);
}

// Select the next analog color index with wrap
void analogSelectInc() {
  selectInc(analogSelected);
}

// Select the previous analog color index with wrap
void analogSelectDec() {
  selectDec(analogSelected);
}

// Select the next digital color index with wrap
void digitalSelectInc() {
  selectInc(digitalSelected);
}

// Select the previous digital color index with wrap
void digitalSelectDec() {
  selectDec(digitalSelected);
}

// Select the next color index with wrap
void selectInc(byte &index) {
  if (++index == (byte)(Color::COUNT)) {
    index = 0;
  }
}

// Select the previous color index with wrap
void selectDec(byte &index) {
  if (index == 0) {
    index = ((byte)(Color::COUNT) - 1);
  }
  else {
    index--;
  }
}

// Update all LEDs in analog mode
void analogSet() {
  arduboy.setRGBled(analogValue[(byte)(Color::RED)],
                    analogValue[(byte)(Color::GREEN)],
                    analogValue[(byte)(Color::BLUE)]);
}

// Update all LEDs in digital mode
void digitalSet() {
  arduboy.digitalWriteRGB(digitalState[(byte)(Color::RED)],
                          digitalState[(byte)(Color::GREEN)],
                          digitalState[(byte)(Color::BLUE)]);
}

// Start the button auto-repeat delay
void startButtonDelay() {
  delayCount = DELAY_FRAMES;
  repeating = false;
}

// Stop the button auto-repeat or delay
void stopButtonRepeat() {
  delayCount = 0;
  repeating = false;
}

// Render and display the screen
void renderScreen() {
  arduboy.setCursor(12, 0);
  arduboy.print(F("RGB LED"));
  arduboy.setCursor(15, 56);
  arduboy.print(F("A:Mode   B:Reset"));
  arduboy.setCursor(74, 0);

  if (controlMode == ANALOG) {
    arduboy.print(F(" Analog"));
    drawAnalog(9, Color::RED, "Red:");
    drawAnalog(25, Color::GREEN, "Green:");
    drawAnalog(41, Color::BLUE, "Blue:");
  }
  else { // Digital
    arduboy.print(F("Digital"));
    drawDigital(9, Color::RED, "Red:");
    drawDigital(25, Color::GREEN, "Green:");
    drawDigital(41, Color::BLUE, "Blue:");
  }

  arduboy.display(CLEAR_BUFFER);
}

// Draw the information for one analog color
void drawAnalog(int y, Color color, const char* name) {
  byte value = analogValue[(byte)color];

  arduboy.setCursor(0, y);
  arduboy.print(name);
  arduboy.setCursor(42, y);
  printValue(value);
  if (analogSelected == (byte)color) {
    arduboy.print(F(" <--"));
  }
  drawBar(y + 8, color, value);
}

// Draw the value bar for an analog color
void drawBar(int y, Color color, byte value) {
  byte barLength = value / 2;

  if (barLength == 0) {
    return;
  }

  if (analogSelected == (byte)color) {
    arduboy.fillRect(0, y, barLength, 5);
  }
  else {
    arduboy.drawRect(0, y, barLength, 5);
  }
}

// Draw the informaton for one digital color
void drawDigital(int y, Color color, const char* name) {
  byte state = digitalState[(byte)color];

  arduboy.setCursor(34, y + 3);
  arduboy.print(name);
  arduboy.setCursor(76, y + 3);
  if (state == RGB_ON) {
    arduboy.print(F("ON "));
    arduboy.fillCircle(22, y + 6, 4);
  }
  else {
    arduboy.print(F("OFF"));
    arduboy.drawCircle(22, y + 6, 4);
  }

  if (digitalSelected == (byte)color) {
    arduboy.print(F(" <--"));
    arduboy.drawRect(16, y, 13, 13);
  }
}

// Print a byte in decimal and hex
void printValue(byte val) {
  if (val < 100) {
    arduboy.print(' ');
  }
  if (val < 10) {
    arduboy.print(' ');
  }
  arduboy.print(val);

  arduboy.print(F("  0x"));
  if (val < 0x10) {
    arduboy.print('0');
  }
  arduboy.print(val, HEX);
}