/*
Written by Michael Sokolewicz.  Use it whatever way you want, just give me credit!

Tests Buttons connected to a Shift Register.

You'll need Arduino, one shift register (eg, SH74HC595N), 3 buttons, 1 10 KOhm resistor.

Since we need 4 Arduino pins (3 output, 1 input), it really doesn't make any sense to implement this
approach unless you have 5 or more buttons.

However, for example purposes, this test only uses three buttons.

    SR1
        * pin 1 to Button 1 (see below)
        * pin 2 to Button 2 (see below)
        * pin 3 unused in this script (to Button 3)
        * pin 4 unused in this script (to Button 4)
        * pin 5 unused in this script (to Button 5)
        * pin 6 unused in this script (to Button 6)
        * pin 7 unused in this script (to Button 7)
        * pin 8 to Gnd
        * pin 9 unused
        * pin 10 to 5v
        * pin 11 to CLOCK_PIN
        * pin 12 to LATCH_PIN
        * pin 13 to Gnd
        * pin 14 to DATA_PIN
        * pin 15 to Button 0 (see below)
        * pin 16 to 5v
        
   BUTTONS: pins are numbered here clockwise: pin 0 is attached to appropriate SR1 pin (see above).
          ----
       1 -|  |- 2
       0 -|  |- 3
          ----

   BUTTON 0:
        * pin 0 to SR1 pin 15
        * pin 1 to 10 KOhm resistor
        * pin 2 to INPUT_PIN
        * pin 3 not used
   BUTTON 1:
        * pin 0 to SR1 pin 1
        * pin 1 to 10 KOhm resistor
        * pin 2 to INPUT_PIN
        * pin 3 not used
   BUTTON 2:
        * pin 0 to SR1 pin 2
        * pin 1 to 10 KOhm resistor
        * pin 2 to INPUT_PIN
        * pin 3 not used
        
   more buttons would follow the same pattern.
        
   10 KOhm Resistor:
        * one side to all buttons' pin 2
        * other side to ground.
*/

#include <Wire.h>
#include <Arduino.h>


// ****************************************
// Low-Level Serial driver.
#define DEBUG_SERIAL     1

#define SERIAL_BAUD 115200

void setupSerial() {
    Serial.begin(SERIAL_BAUD);
#ifdef DEBUG_SERIAL
    Serial.println("Serial ready!");
#endif
}


// ****************************************
// Low-Level Button driver.

// define this to turn on serial output.
#define DEBUG_BUTTONS  1

// number of shift registers you have chained together.
#define NUM_SHIFT_REGISTERS  1

// this code can only handle 8 buttons because it's set up to use only
// one shift register.  You can add more shift registers by chaining them 
// to each other, though (see shift register datasheet).  This means, 
// theoretically, with small modifications to the circuit and this code, 
// there's no limit to the  number of buttons you can support, while 
// still using only 4 Arduino pins. In reality, though, you'll probably 
// start having problems detecting button presses at some point because 
// the loop will become too slow.
#define MAX_BUTTONS (8 * NUM_SHIFT_REGISTERS)

// this is the default number of millis between button checks.
// if you're having trouble detecting button presses,
// set this to a lower number or even just 0.
// If you need more CPU for other parts of your
// script, make this a higher number.
#define DEFAULT_DELAY_MILLIS  50L

enum ButtonAction { None, Up, Down };

// used to store context for each button.
typedef struct ButtonContext {
	uint8_t button;
	uint8_t state;  // HIGH or LOW
	unsigned long lastStateChangeMillis;
	ButtonAction action;  // last ButtonAction
} ButtonContext;


class Buttons {
    public:
        // numButtons: number of buttons to strobe. Each button is addressed from 0 ... numButtons.
        // inputPin: button input pin.
        // latchPin Shift Register overhead
        // dataPin Shift Register overhead
        // clockPin Shift Register overhead
        // default delayMillis
        Buttons(uint8_t numButtons, uint8_t inputPin, uint8_t latchPin, uint8_t dataPin, uint8_t clockPin, unsigned long delay = DEFAULT_DELAY_MILLIS);

        // destructor.
        // not sure if this is ever used in Arduino world.
        ~Buttons(void);
		
        // this MUST BE CALLED EVERY LOOP in your script, or
        // none of the features of this class will work properly!
        void callEveryLoop();
        
        // returns a ButtonAction.
        // when you read the current button action, the action
        // is reset to NONE for that button, until the user
        // does something else with it.
        // if whichButton is illegal, returns NONE.
        ButtonAction getButtonAction(uint8_t whichButton);

    private:
        uint8_t numButtons;
        uint8_t inputPin;
        uint8_t latchPin;
        uint8_t dataPin;
        uint8_t clockPin;
        uint8_t strobeCurButton;
        unsigned long delayMillis;
        unsigned long checkButtonTimeout;
        // malloc'd dynamically in constructor.
        ButtonContext *buttonContexts;
        uint8_t *strobeNextButton;
};


// ---------------------------------------------------------------------------
// Buttons constructor
// ---------------------------------------------------------------------------
Buttons::Buttons(uint8_t nb, uint8_t ip, uint8_t lp, uint8_t dp, uint8_t cp, unsigned long delay) {
    numButtons = nb;
    
    if (numButtons > MAX_BUTTONS) {
#ifdef DEBUG_BUTTONS
        Serial.print("Too many buttons: ");
        Serial.println(numButtons);
#endif
        while (true) ;
    }
    
    inputPin = ip;
    latchPin = lp;
    dataPin = dp;
    clockPin = cp;
    delayMillis = delay;
    
    // can't know ahead of time how many buttons are needed,
    // so have to use malloc() for the vars which depend on
    // numButtons.
    buttonContexts = (ButtonContext*)malloc(numButtons * sizeof(ButtonContext));
    // next button strobe array (avoids an if stmt in time-critical function).
    strobeNextButton = (uint8_t*)malloc(numButtons * sizeof(uint8_t));
    // initialize the arrays.
    for (uint8_t i=0; i<numButtons; i++) {
        buttonContexts[i].button = i;
        buttonContexts[i].state = LOW;  // means "not pressed".
        buttonContexts[i].lastStateChangeMillis = 0L;
        buttonContexts[i].action = None;
        
        // index of next button (avoids an if stmt).
        strobeNextButton[i] = i+1;
#ifdef DEBUG_BUTTONS
        Serial.print("Initialized Button ");
        Serial.println(i);
#endif
    }

    // last one leads back to index 0.
    strobeNextButton[numButtons-1] = 0;
    strobeCurButton = numButtons-1;
    
    // init the Arduino pins.
    pinMode(inputPin, INPUT);
    pinMode(latchPin, OUTPUT);
    pinMode(dataPin, OUTPUT);
    pinMode(clockPin, OUTPUT);

    // prep next strobe action timeout.
    checkButtonTimeout = 0;
    
#ifdef DEBUG_BUTTONS
    Serial.println("Buttons ready.");
#endif
}


// not sure if this is ever called in Arduino world.
Buttons::~Buttons(void) {
#ifdef DEBUG_BUTTONS
    Serial.print("Destructor of Buttons called.");
#endif
    free(buttonContexts);
}


// API (public) methods.

ButtonAction Buttons::getButtonAction(uint8_t whichButton) {
    // sanity check.
    if ((whichButton < 0) || (whichButton >= numButtons)) {
        return None;
    }

    ButtonAction action = buttonContexts[whichButton].action;
    // once the action is read, we reset it to NONE.
    buttonContexts[whichButton].action = None;
    
    return action;
}


// loop overhead.
void Buttons::callEveryLoop() {
    if (millis() > checkButtonTimeout) {
        // check the next button
        strobeCurButton = strobeNextButton[strobeCurButton];
        
        // tell the shift-registers we're sending a new value to shift.
        digitalWrite(latchPin, LOW);

        // if you've chained more shift registers, this is where you'd be outputting 
        // all zeroes to all the SRs which aren't in play, and doing the following 
        // just for the one which controls strobeCurButton.
        
        // shift out the pattern which activates only this button.
        shiftOut(dataPin, clockPin, MSBFIRST, (uint8_t)0x01 << strobeCurButton);
        
        // tell the shift register we're done shifting and it can now 
        // send the pattern out its output pins.
        digitalWrite(latchPin, HIGH);

        // at this point, all buttons are inactive EXCEPT for strobeCurButton,
        // which mean it is the only one which can be sending a signal to the 
        // input pin.  So, the the input pin reflects the state of just this one
        // button: HIGH or LOW.
	uint8_t buttonState = digitalRead(inputPin);

        // interpret the button state.
        if (buttonState != buttonContexts[strobeCurButton].state) {
            // button state has changed since last call.
            buttonContexts[strobeCurButton].action = (buttonState == LOW) ? Up : Down;
            
            // update these.
            buttonContexts[strobeCurButton].state = buttonState;
            buttonContexts[strobeCurButton].lastStateChangeMillis = millis();
        }
        // set timeout for next button check.
        checkButtonTimeout = millis() + delayMillis;
    }
}


// Application level.

// max of 8!
#define NUM_BUTTONS    3

// Arduino pins.  No matter how many buttons you have,
// you'll never need more Ard pins.
#define CLOCK_PIN      8  // 74HC595 SH_CP pin 11
#define LATCH_PIN      9  // 74HC595 ST_CP pin 12
#define DATA_PIN      10  // 74HC595 DS    pin 14
#define INPUT_PIN     14  // A0

Buttons *buttons;

void setupButtons() {
    buttons = new Buttons(NUM_BUTTONS, INPUT_PIN, LATCH_PIN, DATA_PIN, CLOCK_PIN);
}

// ****************************************
// API Level

//void readSensors() {
//}

void makeDecisions() {
    for (uint8_t i=0; i<NUM_BUTTONS; i++) {
        uint8_t action = buttons->getButtonAction(i);
        if (action != None) {
            Serial.print("Button ");
            Serial.print(i);
            switch (action) {
                case Up:
                    Serial.println(" Up!");
                    break;
                case Down:
                    Serial.println(" Down!");
                    break;
                default:
                    Serial.print(": ");
                    Serial.print(action);
                    Serial.println(": Huh?");
                    break;
            }
        }
    }
}


//void takeActions() {
//}


// Highest Level

void setup() {
    Wire.begin();

    // set up all the subsystems.    
    setupSerial();
    setupButtons();
}


void loop() {
//    readSensors();
    makeDecisions();
//    takeActions();

    // housekeeping
    buttons->callEveryLoop();
}

