/**
 * Secret Bookshelf
 * Copyright (c) 2022 Playful Technology
 * 
 * One or more microswitches are mounted on the shelves of a bookcase, such that they
 * are pressed when a book is placed on top of them and released when the book is pivoted forward. 
 * Players must pull the correct pattern of books forward (and *only* those books) in order
 * to activate a relay, releasing a maglock and pushing the bookcase to one side, revealing a secret passage.
 * An accompanying sound effect is also triggered on a serial MP3 player.
 */

// INCLUDES
// For debouncing noisy input from the mechanical switches
#include <Bounce2.h>


// CONSTANTS
// Total number of switches that test which books have been pulled
const byte numInputs = 4;
// GPIO pins to which NC terminals of switches are connected (C terminal from each switch goes to GND)
const byte inputPins[numInputs] = {A0, A1, A2, A3}; 
// Define which combination of books need to be pulled/not pulled to solve the puzzle
// I'm using normally open switches, with pull-up resistors. That means
// HIGH is when the switch is not being pressed (i.e,. the book has been pulled)
// LOW means the book is pressing down on the switch
const bool solutionPattern[numInputs] = {HIGH, LOW, HIGH, HIGH};
// This pin will be driven LOW to release a maglock when puzzle is solved
const byte lockPin = 4;

// GLOBALS
// Assume the default state of each switch is LOW. That is to say that the book is in place,
// and the button is being pressed.
bool lastInputState[numInputs] = {LOW, LOW, LOW, LOW};
// Create an array of Bounce objects
Bounce bookSwitches[numInputs] = {Bounce(), Bounce(), Bounce(), Bounce()};
// Keep track of current state of the controller
enum Mode {Initialising, Locked, Unlocked};
Mode mode = Mode::Initialising;

// This function runs once when the program first starts
void setup() {

  // Initialise a serial connection, used only for debugging
  Serial.begin(9600);
  Serial.println(__FILE__ __DATE__);

  // Attach a debouncer to every book switch
  for(int i=0; i<numInputs; i++){
    bookSwitches[i].attach(inputPins[i], INPUT_PULLUP);
  }
  // Set the lock pin as output and secure the lock
  pinMode(lockPin, OUTPUT);
  digitalWrite(lockPin, HIGH);
  // Update the state of the controller
  mode = Mode::Locked;
  
}

// This function runs over and over
void loop() {

  // Set a flag to test whether all switches are in the correct state.
  // We'll start off by assuming this is true
  bool puzzleSolved = true;

  // Loop through all the inputs
  for(int i=0; i<numInputs; i++){    
    // Update the state of this switch
    bookSwitches[i].update();
    // And retrieve its current value
    int currentInputState = bookSwitches[i].read();
    // If the input has changed, log a little debug info
    if(currentInputState != lastInputState[i]) {
      Serial.print(F("Input #"));
      Serial.print(i);
      Serial.print(F(" changed to "));
      Serial.println(currentInputState);
      // Update the stored value
      lastInputState[i] = currentInputState;
    }
    // If any input is not in the same state as defined in the solution pattern..
    if(currentInputState != solutionPattern[i]) {
      // ... then update the flag to show the puzzle has not been solved
      puzzleSolved = false;
    }
  }

  // If the puzzle has just been solved
  if(mode == Mode::Locked && puzzleSolved) {
    // Send debug info to serial monitor
    Serial.println(F("Puzzle Solved"));
    // Release the lock
    digitalWrite(lockPin, LOW);
    // Update the controller status
    mode = Mode::Unlocked;
  }
  // If the puzzle has just been "unsolved"
  else if(mode == Mode::Unlocked && !puzzleSolved) {
    // Send debug info to serial monitor
    Serial.println(F("Puzzle Reset"));
    // Secure the lock
    digitalWrite(lockPin, HIGH);
    // Update the controller status
    mode = Mode::Locked;
  }
}