/*  Sketch uses 3984 bytes (12%) of program storage space. Maximum is 32256 bytes.
  Global variables use 53 bytes (2%) of dynamic memory,
  leaving 1995 bytes for local variables. Maximum is 2048 bytes.

  telnum 0 = beacon off (white or blue)
  telnum 1 = beacon on (white or blue)
  telnum 2 = beacon flash (amber) // not working
  telnum 3 = beacon pulse (white or blue) // not working, hangs in endless loop
  telnum 4 = beacon colour white
  telnum 5 = beacon colour blue

  2024-01-20 48:17 // glitchy when first dialling after power-on, and when exiting PULSE
*/

// --- Declarations and Variables --- Phone ---

const byte phoneHookPin  = 0;
const byte phoneSpoolPin = 1;
const byte phonePulsePin = 2;

const byte phoneHookLED  = 3;
const byte phoneSpoolLED = 4;
const byte phonePulseLED = 5;

const byte phoneState_HOOK_READY_LED = 6;
const byte phoneState_DIAL_TONE_LED = 7;
const byte phoneState_DIALS_NUM_LED = 8;
const byte phoneState_CONNECTED_LED = 9;

const byte beaconBluePin  = 10;
const byte beaconWhitePin = 11;
const byte beaconAmberPin = 12;

bool ringPhone = false;

byte newHookPinReading;
byte oldHookPinReading;

byte newSpoolPinReading;
byte newPulsePinReading;
byte oldPulsePinReading;

unsigned long timeStamp;
unsigned long currentTime;
unsigned long noActivityStartTime;
const unsigned long ringerTimeExpired = 15000;
const unsigned long debounceDelay = 5;
const unsigned long maxPulseInterval = 250;
const unsigned long maxDiallingPeriod = 1000;

const int telNumLength = 1;
char telNumArray[telNumLength + 1];
byte currentDigitCount;
byte pulsesCounted;

enum phoneListedStates { HOOK_RESET, HOOK_READY, HOOK_RINGING, DIAL_TONE, DIALS_NUM, ANSWERED, CONNECTED };
enum phoneListedStates nextPhoneState;
byte lastPhoneState;

bool incomingRing = false;

// --- Declarations and Variables --- Beacon ---

enum beaconListedColours { WHITE, BLUE };
enum beaconListedColours beaconNextColour;
byte beaconOutputPin;

enum beaconListedModes {ON, OFF, PULSE, FLASH} ;
enum beaconListedModes nextBeaconMode;


unsigned long beaconCurrentTime;
unsigned long beaconTimeStamp;
const int beaconPulseCycleLength = 10;
int beaconLoopCount;

bool beaconBrightness; // for LED output pins

// --- Prototypes --- Phone ---

void phone_Setup();
void phone_Hook_Pin_State();
void phone_State_HOOK_RESET();
void phone_State_HOOK_READY();
void phone_State_DIAL_TONE();
void phone_State_DIALS_NUM();
void phone_State_CONNECTED();
void phone_Set_State_LED_Indicators(byte brightness);
void switch_Phone_States();

// --- Prototypes --- Beacon ---

void beacon_Setup();
void beacon_Pulse_Timing();
void beaconSwitchColour();
void beacon_Switch_Modes();
void beacon_Mode_ON_or_OFF();
void beacon_Mode_PULSE();
void beacon_Mode_FLASH();

// --- Prototypes --- The Big Picture ---

void setup();
void loop();

// --- Functions --- Phone ---

void phone_Setup() {

  pinMode(phoneHookPin,  INPUT_PULLUP);
  pinMode(phoneSpoolPin, INPUT_PULLUP);
  pinMode(phonePulsePin, INPUT_PULLUP);

  pinMode(phoneHookLED,  OUTPUT);
  pinMode(phoneSpoolLED, OUTPUT);
  pinMode(phonePulseLED, OUTPUT);

  pinMode(phoneState_HOOK_READY_LED, OUTPUT);
  pinMode(phoneState_DIAL_TONE_LED, OUTPUT);
  pinMode(phoneState_DIALS_NUM_LED, OUTPUT);
  pinMode(phoneState_CONNECTED_LED, OUTPUT);

  currentTime = millis();
  timeStamp = currentTime;

  delay(5); // to debounce HookPin

  newHookPinReading = digitalRead(phoneHookPin);

  if (newHookPinReading == 1) {
    nextPhoneState = HOOK_RESET;
    phone_State_HOOK_RESET();
  }

  if (newHookPinReading == 0) {
    nextPhoneState = DIAL_TONE;
    phone_State_DIAL_TONE();
  }

}

void phone_Hook_Pin_State() {

  currentTime = millis();

  if (currentTime - timeStamp < debounceDelay) {
    return;
  }

  oldHookPinReading = newHookPinReading;
  newHookPinReading = digitalRead(phoneHookPin);
  digitalWrite(phoneHookLED, !newHookPinReading); // illuminate HookLED when HookPin is at LOW ie: "OFF_HOOK"

  if ((oldHookPinReading == 0) && (newHookPinReading == 0)) { // remains in current "off hook" state
    return;
  }
  // end of 0/01

  if ((oldHookPinReading == 0) && (newHookPinReading == 1)) { // change from any/all "off hook" states to "on hook reset" state
    nextPhoneState = HOOK_RESET;
    return;
  }
  // end of 0/1

  if ((oldHookPinReading == 1) && (newHookPinReading == 1)) {

    if (incomingRing == false) { // "on hook" and "not ringing"

      if (lastPhoneState == HOOK_RESET) { // change to "on hook ready" from "on hook reset"
        nextPhoneState = HOOK_READY;
        return;
      }

      if (lastPhoneState == HOOK_READY) { // remains in current "on hook" state
        return;
      }
    }


    if (incomingRing == true) { // "on hook" and "is ringing"

      currentTime = millis();

      if (lastPhoneState == HOOK_READY) {
        nextPhoneState = HOOK_RINGING;
        timeStamp = currentTime; // get fresh timestamp for "ring-no-answer"
        return;
      }

      if (lastPhoneState == HOOK_RINGING) {

        if (currentTime - timeStamp < ringerTimeExpired) {
          // timer countdown for "ring-no-answer"
          return;
        }

        if (currentTime - timeStamp >= ringerTimeExpired) { // exit countdown for "ring-no-answer"
          incomingRing = false; // should be moved into HOOK_RESET function
          nextPhoneState = HOOK_RESET; // reset parameters before nextPhoneState = HOOK_READY;
          return;
        }
      }
    }
  }
  // end of 1/1

  if ((oldHookPinReading == 1) && (newHookPinReading == 0)) {

    if (lastPhoneState == HOOK_READY) {
      nextPhoneState = DIAL_TONE;
      return;
    }

    if (lastPhoneState == HOOK_RINGING) {
      nextPhoneState = ANSWERED;
      return;
    }
  }
  // end of 1/0
} // end of phone_Hook_Pin_State()

void phone_State_HOOK_RESET() {
  if (lastPhoneState == nextPhoneState) {
    digitalWrite(phoneState_HOOK_READY_LED, 1);
    currentDigitCount = 0;
    pulsesCounted = 0;
    // telNumArray[0] = "/0"; // Will this clear the "telNumArray[]", and is it essential to clear it?
  }
}

void phone_State_HOOK_READY() {
  // to be determined later
  return;
}

void phone_State_DIAL_TONE() {

  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  if (lastPhoneState != nextPhoneState) { // set new timestamp when newly entering DIAL_TONE state     //
    phone_Set_State_LED_Indicators(0); //                                                              //
    digitalWrite(phoneState_DIAL_TONE_LED, 1); //                                                      //
    //currentTime = millis(); // use timeStamp from switch_Phone_States() //                           //
    timeStamp = currentTime; //                                                                        //
    //noActivityStartTime = currentTime; //                                                            //
    lastPhoneState = nextPhoneState; //                                                                //
    return; //                                                                                         //
  } //                                                                                                 //
  ///////////////////////////////////////////////////////////////////////////////////////////////////////

  if (currentTime - timeStamp < debounceDelay) {
    return;
  }
  if (currentTime - timeStamp >= debounceDelay) {
    newSpoolPinReading = digitalRead(phoneSpoolPin);
    digitalWrite(phoneSpoolLED, !newSpoolPinReading);
    if (newSpoolPinReading == 0) {
      nextPhoneState = DIALS_NUM;
    }
  }
}

void phone_State_DIALS_NUM() {

  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  if (lastPhoneState != nextPhoneState) { // set new timestamp when newly entering DIALS_NUM state    //
    phone_Set_State_LED_Indicators(0); //                                                              //
    digitalWrite(phoneState_DIALS_NUM_LED, 1); //                                                      //
    //currentTime = millis(); // use timeStamp from switch_Phone_States() //                           //
    timeStamp = currentTime; //                                                                        //
    //noActivityStartTime = currentTime; //                                                            //
    lastPhoneState = nextPhoneState; //                                                                //
    return; //                                                                                         //
  } //                                                                                                 //
  ///////////////////////////////////////////////////////////////////////////////////////////////////////

  if (currentTime - timeStamp < debounceDelay) {
    return;
  }

  if (currentTime - timeStamp >= debounceDelay) {

    newSpoolPinReading = digitalRead(phoneSpoolPin);
    digitalWrite(phoneSpoolLED, !newSpoolPinReading);

    newPulsePinReading = digitalRead(phonePulsePin);
    digitalWrite(phonePulseLED, !newPulsePinReading);

    if (newPulsePinReading != oldPulsePinReading) {

      if (newPulsePinReading == HIGH) {
        pulsesCounted++;
      }

      currentTime = millis(); // for end-of-dial timer upgrade
      timeStamp = currentTime;
      oldPulsePinReading = newPulsePinReading;

    }

  }

  if ((currentTime - timeStamp) >= maxPulseInterval && (pulsesCounted > 0)) {
    if (currentDigitCount < telNumLength) {
      if (pulsesCounted == 10) {
        pulsesCounted = 0;
      }
      telNumArray[currentDigitCount] = pulsesCounted | '0';
      currentDigitCount++;
      telNumArray[currentDigitCount] = 0;
    }

    if (currentDigitCount == telNumLength) {
      nextPhoneState = CONNECTED;
    }
    pulsesCounted = 0;
  }

  // add end-of-dial timer upgrade
}

void phone_State_CONNECTED() {

  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  if (lastPhoneState != nextPhoneState) { // set new timestamp when newly entering CONNECTED state     //
    phone_Set_State_LED_Indicators(0); //                                                              //
    digitalWrite(phoneState_CONNECTED_LED, 1); //                                                      //
    //currentTime = millis(); // use timeStamp from switch_Phone_States() //                           //
    timeStamp = currentTime; //                                                                        //
    //noActivityStartTime = currentTime; //                                                            //
    lastPhoneState = nextPhoneState; //                                                                //
    return; //                                                                                         //
  } //                                                                                                 //
  ///////////////////////////////////////////////////////////////////////////////////////////////////////

  if (strcmp(telNumArray, "0") == 0) {
    phone_Tel_Num_0 (); //nextBeaconMode = OFF
    phone_Set_State_LED_Indicators(1);
  }
  if (strcmp(telNumArray, "1") == 0) {
    phone_Tel_Num_1 (); // nextBeaconMode = ON
  }
  if (strcmp(telNumArray, "2") == 0) {
    phone_Tel_Num_2 (); // nextBeaconMode = FLASH
  }
  if (strcmp(telNumArray, "3") == 0) {
    phone_Tel_Num_3 (); // nextBeaconMode = PULSE
  }
  if (strcmp(telNumArray, "4") == 0) {
    phone_Tel_Num_4 (); // beaconNextColour = WHITE
  }
  if (strcmp(telNumArray, "5") == 0) {
    phone_Tel_Num_5 (); // beaconNextColour = BLUE
  }
  else {
    //
  }
}

void phone_Set_State_LED_Indicators(byte brightness) {

  digitalWrite(phoneState_HOOK_READY_LED, brightness);
  digitalWrite(phoneState_DIAL_TONE_LED, brightness);
  digitalWrite(phoneState_DIALS_NUM_LED, brightness);
  digitalWrite(phoneState_CONNECTED_LED, brightness);

}

void switch_Phone_States() {

  currentTime = millis();
  if (lastPhoneState != nextPhoneState) { // set new timestamp only when phoneState changes
    timeStamp = currentTime;
    phone_Set_State_LED_Indicators(0);
    lastPhoneState = nextPhoneState;
  }
  switch (nextPhoneState) {
    case HOOK_RESET:
      phone_State_HOOK_RESET();
      break;
    case HOOK_READY:
      phone_State_HOOK_READY();
      break;
    case DIAL_TONE:
      phone_State_DIAL_TONE();
      break;
    case DIALS_NUM:
      phone_State_DIALS_NUM();
      break;
    case CONNECTED:
      phone_State_CONNECTED();
      break;
  }
}

// --- Functions --- Beacon ---

void beacon_Setup() {

  pinMode(beaconBluePin,  OUTPUT);
  pinMode(beaconAmberPin, OUTPUT);
  pinMode(beaconWhitePin, OUTPUT);

  beaconNextColour = WHITE;
  nextBeaconMode = ON;

  beaconSwitchColour();
  beacon_Switch_Modes();
}

void beacon_Pulse_Timing(byte beaconIntervalDuration) {

  // future upgrade to use generic timer function, instead of this dedicated timer
  beaconCurrentTime = millis();

  if (beaconCurrentTime - beaconTimeStamp < beaconIntervalDuration) {
    return;
  }

  if (beaconCurrentTime - beaconTimeStamp >= beaconIntervalDuration) {
    beaconLoopCount++;
    beaconTimeStamp = beaconCurrentTime;
  }
}

void beaconSwitchColour() {

  beacon_Mode_ON_or_OFF(LOW);

  switch (beaconNextColour) {

    case WHITE:
      beaconOutputPin = beaconWhitePin;
      break;
    case BLUE:
      beaconOutputPin = beaconBluePin;
      break;
    default:
      beaconOutputPin = beaconWhitePin;
      break;

  }

  beacon_Mode_ON_or_OFF(beaconBrightness);
}

void beacon_Switch_Modes() { // works as intended

  currentTime = millis();

  switch (nextBeaconMode) {

    case ON:
      beacon_Mode_ON_or_OFF(HIGH);
      break;
    case OFF:
      beacon_Mode_ON_or_OFF(LOW);
      break;
    case PULSE:
      beacon_Mode_PULSE(beaconOutputPin, beaconPulseCycleLength);
      break;
    case FLASH:
      beacon_Mode_FLASH();
      break;
    default:
      beacon_Mode_ON_or_OFF(HIGH);
      break;
  }
}

void beacon_Mode_ON_or_OFF(int beaconBrightness) {

  digitalWrite(beaconOutputPin, beaconBrightness);
}

void beacon_Mode_PULSE(byte beaconOutputPin, int beaconPulseInterval) { // works as intended

  beacon_Pulse_Timing(beaconPulseInterval);
  float radian = DEG_TO_RAD * beaconLoopCount;
  int sinOut = constrain ((sin (radian) * 128) + 128, 0, 255);
  analogWrite(beaconOutputPin, sinOut);

  if (beaconLoopCount == 360) {
    beaconLoopCount = 0;
  }
}

void beacon_Mode_FLASH() { // beacon flash AMBER with "chirp" sound fx
  // TBD
  return;
}

// --- The Big Picture ---

void phone_Tel_Num_0 () { // turn beacon OFF
  nextBeaconMode = OFF;
}

void phone_Tel_Num_1 () { // turn beacon ON
  nextBeaconMode = ON;
}

void phone_Tel_Num_2 () {
  nextBeaconMode = FLASH;
}

void phone_Tel_Num_3 () { // beacon fade-up / fade-down
  nextBeaconMode = PULSE;
}

void phone_Tel_Num_4 () { // change beacon colour to WHITE
  digitalWrite(beaconOutputPin, LOW); // turn OFF previous beacon colour
  beaconOutputPin = beaconWhitePin;
}

void phone_Tel_Num_5 () { // change beacon colour to BLUE
  digitalWrite(beaconOutputPin, LOW);
  beaconOutputPin = beaconBluePin;
}

void phone_Tel_Num_6 () { // dummy/placeholder
  return;
}

void phone_Tel_Num_7 () { // dummy/placeholder
  return;
}

void setup() {

  phone_Setup();
  beacon_Setup();

}

void loop() {


  beacon_Switch_Modes();

  phone_Hook_Pin_State();
  switch_Phone_States();

}

//
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
LED01:A
LED01:C
LED02:A
LED02:C
LED03:A
LED03:C
LED04:A
LED04:C
LED05:A
LED05:C
LED06:A
LED06:C
LED07:A
LED07:C
LED08:A
LED08:C
LED09:A
LED09:C
LED10:A
LED10:C
LED11:A
LED11:C
dial:GND
dial:DIAL
dial:PULSE
hook:1
hook:2
hook:3