// https://wokwi.com/projects/394997170662617089

#include <mechButton.h>
#include <Servo.h>
#include <squareWave.h>
#include <mapper.h>

#define lEDG1             6     // Pin numbers..
#define lEDG2             9
#define lEDG3             8
#define lEDP1             7
#define bz1               3
#define SERVO_PIN         11

#define MODE1_SEC         45    // Durations of the different modes.
#define MODE2_SEC         30
#define MODE3_SEC         15

#define MAX_PERIOD        750   // How long & close are beeps at start.
#define MIN_PERIOD        200   // At the end.
#define FREQ              600   // What frequency in Hz are they.

#define SERVO_HOME_POS    0     // When the servo is sitting idle put it here.
#define SERVO_ACTIVE_POS  90    // When active, here is good.
#define SERVO_WAIT_MS     1000  // Wait this long before returning it home.



// ***********************************************************
//
//    An object that beeps slow then fast depending on time.
//
// ***********************************************************


class alarm : public squareWave {

  public :
            alarm(void);
    virtual ~alarm(void);

            void  start(float inSec);
            void  stop(void);

    virtual void  pulseOn(void);
	  virtual void  pulseOff(void);

    mapper*  signalMapper;    // Know the fraction left? Gives period.
    timeObj* signalTimer;     // Used to watch the looping time. 
};


// Constructor. Creates it's mapper and timer objects.
alarm::alarm(void) {

  signalMapper = new mapper(1,0,MAX_PERIOD,MIN_PERIOD);
  signalTimer = new timeObj(10,false);
}


// Destructor, recycles the mapper and timer objects.
alarm::~alarm(void) {

  delete(signalMapper);
  delete(signalTimer);
}


// User calls this to start all this nonsense.
void alarm::start(float inSec) {

  float newPeriod;

  signalTimer->setTime(inSec*1000); // Set & start timer in Ms.
  newPeriod = signalMapper->map(1); // Map period of a full tank of time.
  setPeriod(newPeriod);             // Set the period to our underlying squarewave.
  setPercent(50);                   // GIve it 50% pulsewidth.
  setOnOff(true);                   // Give 'er a kick and start the machine!
}


// User call this to shut down the machine.
void alarm::stop(void) {

  noTone(bz1);              // If it's beeping, for lord sakes shut it up!
  digitalWrite(lEDP1,LOW);  // Purple LED off!
  setOnOff(false);          // Pull the plug on the machinery.
}


// When the underlying square wave goes high, it calls this for us.
// Us, as in we are the alarm.
void alarm::pulseOn(void) {

  float         fractionLeft;
  float         newPeriod;
  unsigned long duration;

  if (signalTimer->ding()) {                      // Are we out of time?
    stop();                                       // Shut everything down.
    return;                                       // And exit.
  }
  digitalWrite(lEDP1,HIGH);                       // Still here? Fire up the LED.
  fractionLeft = signalTimer->getFraction();      // How much is in the time tank?
  newPeriod = signalMapper->map(fractionLeft);    // Using that, map to a new period value.
  setPeriod(newPeriod);                           // Set in the new period.
  setPercent(50);                                 // Set in the 50% duty cycle.
  duration = newPeriod/2;                         // Calculate duration of sound.
  tone(bz1,FREQ,duration);                        // Fire up the noise!
}


// When the underlying square wave goes low, it calls this for us.
void alarm::pulseOff(void) { 
  
  digitalWrite(lEDP1,LOW);          // LED off!
  noTone(bz1);                      // Sound off! (Should'nt be necessary)
  if (signalTimer->ding()) stop();  // If we've run out of time.. SHut 'er down!
}



// ***********************************************************
//
//                  Main program starts here.
//
// ***********************************************************


enum state  { waiting, looping, doingServo };           // What CAN we focus on.
state       ourState;                                   // What ARE we focused on. 
enum mode   { mode1, mode2, mode3 };                    // What modes do we have.
mode        ourMode;                                    // What mode has been choosen.
mechButton  bt1(4);                                     // Can cancel the timer loop
mechButton  bt2(5);                                     // Selects the mode.
mechButton  bt3(10);                                    // begins the loop
Servo       servo_11;                                   // Servo for.. something?
timeObj     btn1Timer(1000,false);                      // How long in ms IS holding it down?
alarm       ourSignal;                                  // Chopped tone generator.
timeObj     servoTimer(SERVO_WAIT_MS,false);            // How long to wait to reset servo.


void setup() {
  
  Serial.begin(9600);               // Fire up the serial port.
  pinMode(lEDG1,OUTPUT);            // Set all the pinmodes that arn't automatic.
  pinMode(lEDG2,OUTPUT);            //
  pinMode(lEDG3,OUTPUT);            //
  pinMode(lEDP1,OUTPUT);            //
  pinMode(bz1,OUTPUT);              //
  servo_11.attach(SERVO_PIN);       // Set up the servo..
  servo_11.write(SERVO_HOME_POS);   // Send to to it's home position.
  bt1.setCallback(btn1Clk);         // Attach the buttons to their callback finctions.
  bt2.setCallback(btn2Clk);         //
  bt3.setCallback(btn3Clk);         //
  ourState = waiting;               // Set our initial state.
  ourMode = mode1;                  // Set our initial mode.
  showMode();                       // Show the mode on the LEDs
}


// Called when bt1 changes state.
void btn1Clk(void) {

  if (!bt1.getState()) {                  // If the button went down..
    btn1Timer.start();                    // We start it's long press timer.
    Serial.println("Button 1 clicked.");  // Amuse the user a little.
  } else {                                // Else, the button went up!
    if (!btn1Timer.ding()) {              // If the timer has not expired..
      btn1Timer.reset();                  // Reset the timer and move on.
    }
  }
}


// Called when bt2 changes state.
void btn2Clk(void) {

  if (!bt2.getState()) {                      // If the button went down..
    if (ourState==waiting) {                  // If we're just sittin/ around smokeing cigarettes.
      switch(ourMode) {                       // Check our mode of..
        case mode1 : ourMode = mode2; break;  // Go through and increment the mode.
        case mode2 : ourMode = mode3; break;  //
        case mode3 : ourMode = mode1; break;  //
      }                                       //
      showMode();                             // SHow the result.
    } else {                                  // Else, we are busy..
      Serial.println("Not changing modes while running.");
    }
    Serial.println("Button 2 clicked.");
  }
}


// Called when bt3 changes state.
void btn3Clk(void) {

  if (!bt3.getState()) {                      // If the button went down..        
    if (ourState==waiting) {                  // If we're just sittin' here causing trouble.
      startLooping();                         // Start the alarm looping thing!
    } else {                                  // Else, we're busy. Tell user to shush!
      Serial.println("Umm.. Already running a pattern.");
    }
    Serial.println("Button 3 clicked.");
  }
}


// Start the looping!
void startLooping(void) {

  switch(ourMode) {
    case mode1 : ourSignal.start(MODE1_SEC); break;
    case mode2 : ourSignal.start(MODE2_SEC); break;
    case mode3 : ourSignal.start(MODE3_SEC); break;
  }
  ourState = looping;
}


// Shuts off the noise!
void stopLooping(void) {
  
  ourSignal.stop();     // Shut off
  ourState = waiting;   // Mark us as currently waiting.
}


// Set the LEDs to match our mode. 1, 2 or 3
void showMode(void) {         

  switch(ourMode) {
  digitalWrite(lEDP1,LOW);
  case mode1  :
    digitalWrite(lEDG1,HIGH);
    digitalWrite(lEDG2,LOW);
    digitalWrite(lEDG3,LOW);
  break;
  case mode2  :
    digitalWrite(lEDG1,LOW);
    digitalWrite(lEDG2,HIGH);
    digitalWrite(lEDG3,LOW);
  break;
  case mode3  :
    digitalWrite(lEDG1,LOW);
    digitalWrite(lEDG2,LOW);
    digitalWrite(lEDG3,HIGH);
  break;
  }
}


void loop() {

  idle();                                 // Runs background things. Buttons, alarm buzzers..
  switch(ourState) {                      // If our state is..
    case waiting    : return;             // Waiting? Exit, nothing to do here.
    case looping    :                     // Looping?
      if (btn1Timer.ding()) {             // If they are holding down "stop" button..
        stopLooping();                    // Stop the looping!
        btn1Timer.reset();                // Reset the timer.
        ourState = waiting;               // We bailed, no servo, just waiting.
      } else if (!ourSignal.running()) {  // Else, if the alarm as finished..
        servo_11.write(SERVO_ACTIVE_POS); // Move the servo!  
        servoTimer.start();               // Set it's timer.
        ourState = doingServo;            // Now we are doing the servo!
      }                                   //
    break;                                //
    case doingServo :                     // doingServo?
      if (servoTimer.ding()) {            // If it's timer has gone 'ding'..
        servo_11.write(SERVO_HOME_POS);   // Send it back to home position.
        servoTimer.reset();               // Reset it's timer.
        ourState = waiting;               // We're back to cigarettes and coffee.
      }                                   //
    break;                                //
  }                                       //
}