#include <FastLED.h>

class Mux {
private:
    uint8_t _pins[4];
    uint8_t _enablePin = 255;
    uint8_t _channel   = 0;
    uint8_t _comm;
public:
    Mux(uint8_t s0, uint8_t s1, uint8_t s2, uint8_t s3, uint8_t comm) {
        _pins[0] = s0;
        _pins[1] = s1;
        _pins[2] = s2;
        _pins[3] = s3;
        _comm = comm;

        for (uint8_t i=0; i<4; i++) {
            pinMode(_pins[i], OUTPUT);
            digitalWrite(_pins[i], LOW);
        }
        pinMode(_comm, INPUT);
        _channel = 0;
    }

    bool setChannel(uint8_t channel) {
        if (channel > 15) return false;
        uint8_t _new = channel;
        if (_new != _channel) {
            uint8_t _changed = _new ^ _channel;
            uint8_t mask = 0x08;
            uint8_t i = 3;
            while (mask) {
                //  only write changed pins.
                if (mask & _changed) digitalWrite(_pins[i], (mask & _new));
                i--;
                mask >>= 1;
            }
            _channel = _new;
        }
        return true;
    }

    bool digitalR(uint8_t chann) {
        setChannel(chann);
        return digitalRead(_comm);
    }

    int analogR(uint8_t chann) {
        setChannel(chann);
        return analogRead(_comm);
    }
};

class Button {
private:

    uint8_t _chann;
    Mux * _mux;
    bool _state;
    bool _debounceState;
    uint32_t _lastDebounce = 0;
    uint16_t _debounceDelay;

public:

    const char* name;

    Button(const char * newName ,uint8_t chann, Mux * mux, uint16_t debounceDelay=10) {
        _chann = chann;
        _mux = mux;
        _debounceDelay = debounceDelay;
        name = newName;
    }

    bool stateChanged() {
        bool tmpState = !_mux->digitalR(_chann);
        if (tmpState != _debounceState) _lastDebounce = millis();
        if (millis() - _lastDebounce > _debounceDelay) {
            if (tmpState != _state) {
                _state = tmpState;
                return true;
            }
        }
        _debounceState = tmpState;
        return false;
    }

    // Returns true if something has changed
    bool update() {
        if (stateChanged()) {
            Serial.print(name);
            Serial.print(": ");
            if (_state == true) Serial.println("pressed");
            else Serial.println("Released");
            return true;
        }
        return false;
    }

    // Eventos
    // - state (down or up)
    // - press (up-down)
    // - release (down-up)
    // - single click (press-release en menos de n millis)
    // - double click (single-single en menos de n millis)
    // - triple click (single-single-single en menos de n millis)
    //
};

class Pot {
private:

    uint8_t _chann;
    Mux * _mux;
    int _prevValue;
    uint8_t _diff; // A change bigger than this indicates that the pot has moved

public:

    const char* name;
    int value;

    Pot(const char * newName ,uint8_t chann, Mux * mux, uint8_t diff=5) {
        _chann = chann;
        _mux = mux;
        _diff = diff;
        name = newName;
    }

    bool valueChanged() {
        value = _mux->analogR(_chann);
        if (abs(value - _prevValue) > _diff) {
            _prevValue = value;
            return true;
        }
        return false;
    }

    // Returns true if something has changed
    bool update() {
        if (valueChanged()) {
            Serial.print(name);
            Serial.print(": ");
            Serial.println(value);
            return true;
        }
        return false;
    }

    // TODO 
    // Un rolling average que suvice un poco los movimientos
};

Mux mux_A(26, 25, 27, 32, 33);  // (s0, s1, s2, s3, comm)
// 6, 7, 8, 9, 10, 15 -> NC

Mux mux_B(16, 17, 18, 19, 34);  // (s0, s1, s2, s3, comm)
// 3 -> ANO_ENC_A
// 4 -> ANO_ENC_B
Button ACenter("A Center", 5, &mux_B); // SW1
Button ADown("A Down", 6, &mux_B); // SW2
Button ARight("A Right", 7, &mux_B); // SW3
Button AUp("A Up", 8, &mux_B); // SW4
Button ALeft("A Left", 9, &mux_B); // SW5

Pot MLevel("M Level", 0, &mux_A);
Pot LLevel("L Level", 1, &mux_A); // Slide
Pot LHigh("L High", 2, &mux_A);
Pot LMiddle("L Middle", 3, &mux_A);
Pot LLow("L Low", 4, &mux_A);
Pot LFx("L FX", 5, &mux_A);

Pot RLevel("R Level", 14, &mux_B); // Slide
Pot RHigh("R High", 13, &mux_B);
Pot RMiddle("R Middle", 12, &mux_B);
Pot RLow("R Low", 11, &mux_B);
Pot RFx("R FX", 10, &mux_B);

Pot * pots[11];

Button LPlay("L Play", 12, &mux_A);
Button LCue("L Cue", 11, &mux_A);
Button LSync("L Sync", 13, &mux_A);
Button LPhones("L Phones", 14, &mux_A);

Button RPlay("R Play", 0, &mux_B);
Button RCue("R Cue", 15, &mux_B); // BUG: Al mover el slider (channel 14) se mete ruido al boton (pareciera ser un problema de hardware)
Button RSync("R Sync", 1, &mux_B);
Button RPhones("R Phones", 2, &mux_B);

Button * botones[8];

const int BATT_CHG_SIGNAL_PIN = 14;
const int BATT_VOLT_PIN = 35;

// 5% steps
float battTable[] = { 
  4.2,  // 100
  4.15, // 95
  4.11, // 90
  4.08, // 85
  4.02, // 80
  3.98, // 75
  3.95, // 70
  3.91, // 65
  3.87, // 60
  3.85, // 55
  3.84, // 50
  3.82, // 45
  3.80, // 40 (Storage recomended)
  3.79, // 35
  3.77, // 30
  3.75, // 25
  3.73, // 20
  3.71, // 15 (Sane limit)
  3.69  // 10 (Danger limit)
};

uint8_t battLevel() {

  // Vin = Vout * ((R1 + R2) / R2)
  // ((R1 + R2) / R2) = (1,200,000 / 200,000) = 6
  int Vout = analogRead(BATT_VOLT_PIN);
  float Vin =  Vout * 6.0;

  uint8_t percent = 100;
  for (uint8_t l=0; l<19; l++) {
    if (Vin >= battTable[l]) return percent;
    percent -= 5;
  }
  return 0;
  // TODO
  // Interpolar los porcentajes
}

bool battIsCharging() {
  return !digitalRead(BATT_CHG_SIGNAL_PIN);
}

#define NUM_LEDS 17
CRGB leds[NUM_LEDS];
uint32_t ledChanged = 0;
uint8_t ledIndex = 0;

void refreshLeds() {
    if (millis() - ledChanged < 500) return;
    ledChanged = millis();
    if (ledIndex == NUM_LEDS) ledIndex = 0;

    fill_solid(&(leds[0]), NUM_LEDS, CRGB::Black);
    leds[ledIndex] = CRGB::Red;
    FastLED.show();
    ledIndex++;
}

void setup() {

    FastLED.addLeds<NEOPIXEL, 13>(leds, NUM_LEDS);

    pinMode(BATT_VOLT_PIN, INPUT);
    pinMode(BATT_CHG_SIGNAL_PIN, INPUT);

    Serial.begin(115200);

    botones[0] = &LPlay;
    botones[1] = &LCue;
    botones[2] = &LSync;
    botones[3] = &LPhones;
    botones[4] = &RPlay;
    botones[5] = &RCue;
    botones[6] = &RSync;
    botones[7] = &RPhones;

    pots[0] =  &MLevel;
    pots[1] =  &LLevel; // Slide
    pots[2] =  &LHigh;
    pots[3] =  &LMiddle;
    pots[4] =  &LLow;
    pots[5] =  &LFx;
    pots[6] =  &RLevel; // Slide
    pots[7] =  &RHigh;
    pots[8] =  &RMiddle;
    pots[9] =  &RLow;
    pots[10] = &RFx;

}

void loop() {

    for (uint8_t i=0; i<8; i++) {
        botones[i]->update();
    }
    for (uint8_t i=0; i<11; i++) {
        pots[i]->update();
    }

    refreshLeds();
}
