// Uncomment to enable debug messages
//#define DEBUGKNOCK

#ifdef DEBUGKNOCK
#define debugPrint(x) Serial.print("\n"); Serial.print(x);
#define debugPrintVar(x,y) Serial.print("\n"); Serial.print(x); Serial.print(y, DEC);
#define debugPrintFailure(knockRuleIndex,dt,minDt,maxDt) \
Serial.print("\nFailed after rule "); Serial.print(knockRuleIndex, DEC); \
Serial.print(" ; dt = "); Serial.print(dt, DEC); \
Serial.print(" ; minDt = "); Serial.print(minDt, DEC); \
Serial.print(" ; maxDt = "); Serial.print(maxDt, DEC);
#else
#define debugPrint(x)
#define debugPrintVar(x,y)
#define debugPrintFailure(knockRuleIndex,dt,minDt,maxDt)
#endif

struct KnockRule {
  unsigned long minDt;
  unsigned long maxDt;
};

const int ANALOG_INPUT_THRESHOLD = 30;
const int MINIMUM_TIME_BETWEEN_KNOCKS = 150;
const int NUM_KNOCK_RULES = 4; //We can set this to 4 or 6 depending on how complex a knock we want
const unsigned long MIN_HALF = 600;
const unsigned long MAX_HALF = 900;
const unsigned long MIN_QUARTER = 300;
const unsigned long MAX_QUARTER = 550;
const unsigned long MIN_EIGHTH = 140;
const unsigned long MAX_EIGHTH = 240;
const int OPEN_DOOR_PIN = 2;

unsigned long lastKnockTime;
KnockRule knockRules[6];
int knockRuleIndex;
bool lastCheckWasKnock;

void setup() {
  lastCheckWasKnock = false;
  lastKnockTime = 0;
  knockRuleIndex = 0;
  knockRules[0].minDt = MIN_QUARTER;
  knockRules[0].maxDt = MAX_QUARTER;
  knockRules[1].minDt = MIN_EIGHTH;
  knockRules[1].maxDt = MAX_EIGHTH;
  knockRules[2].minDt = MIN_EIGHTH;
  knockRules[2].maxDt = MAX_EIGHTH;
  knockRules[3].minDt = MIN_QUARTER;
  knockRules[3].maxDt = MAX_QUARTER;
  knockRules[4].minDt = MIN_HALF;
  knockRules[4].maxDt = MAX_HALF;
  knockRules[5].minDt = MIN_QUARTER;
  knockRules[5].maxDt = MAX_QUARTER;
  
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(OPEN_DOOR_PIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  digitalWrite(OPEN_DOOR_PIN, LOW);
#ifdef DEBUGKNOCK
  Serial.begin(9600);
#endif

  blink(4);
}

bool checkAnalogPin() {
  if (lastCheckWasKnock) {
    lastCheckWasKnock = false;
    return analogRead(A0)>=ANALOG_INPUT_THRESHOLD;
  } else if (analogRead(A0)>=ANALOG_INPUT_THRESHOLD) {
    lastCheckWasKnock = true;
  }
  return false;
}

void loop() {
  unsigned long nowMs = millis();
  unsigned long dt = nowMs - lastKnockTime;

  if (dt>1000) {
    digitalWrite(LED_BUILTIN, LOW);
  }

  if (dt>MINIMUM_TIME_BETWEEN_KNOCKS && checkAnalogPin()) {
    digitalWrite(LED_BUILTIN, HIGH);
    lastKnockTime = nowMs;
    if (dt >= knockRules[knockRuleIndex].minDt && dt <= knockRules[knockRuleIndex].maxDt) {
      debugPrintVar("Successfully passed step ", knockRuleIndex);
      ++knockRuleIndex;
      if (knockRuleIndex == NUM_KNOCK_RULES) {
        knockRuleIndex = 0;
        debugPrint("Successfully opened door");
        digitalWrite(OPEN_DOOR_PIN, HIGH);
        blink(5);
        digitalWrite(OPEN_DOOR_PIN, LOW);
      }
    } else {
      debugPrintFailure(knockRuleIndex,dt,knockRules[knockRuleIndex].minDt,knockRules[knockRuleIndex].maxDt)
      knockRuleIndex = 0;
    }
  }
}



void blink(int times) {
  for (int i=0; i<times; ++i) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);
  }
}
