// Include Arduino library
#include <Arduino.h>

// Include math library
#include <math.h>

// Include Adafruit MAX31865 library
#include <Adafruit_MAX31865.h>

// Define the MAX31865 pins: CS, DI, DO, CLK
Adafruit_MAX31865 thermo = Adafruit_MAX31865(17, 16, 4, 2);

//Define the PTD100 settings
#define RREF      430.0
#define RNOMINAL  100.0

// Enable debug logging to Serial at 115200
//#define REMOTEXY__DEBUGLOG    

// Define RemoteXY connection mode 
#define REMOTEXY_MODE__ESP32CORE_BLE

// Include BLEDevice Library
#include <BLEDevice.h>

// Include EEPROM library
#include <EEPROM.h>

// RemoteXY connection settings 
#define REMOTEXY_BLUETOOTH_NAME "PCRGo_Orange"

// Include the RemoteXY library
#include <RemoteXY.h>

// RemoteXY GUI configuration  
#pragma pack(push, 1)  
uint8_t const PROGMEM RemoteXY_CONF_PROGMEM[] =   // 592 bytes V21 
  { 254,23,0,24,0,0,0,11,0,0,2,0,2,2,0,4,2,0,6,2,
  0,8,2,0,10,2,0,12,2,0,14,2,0,16,2,0,18,2,0,20,
  2,0,36,2,21,0,0,0,80,67,82,71,111,0,24,2,106,200,200,84,
  2,1,0,21,0,129,33,95,71,29,2,28,43,10,0,16,68,101,110,97,
  116,117,114,101,0,129,9,78,129,29,2,38,47,10,0,16,65,110,110,101,
  97,108,105,110,103,0,129,249,116,129,29,2,48,47,10,0,16,69,120,116,
  101,110,115,105,111,110,0,129,237,24,75,29,2,2,40,12,64,16,80,67,
  82,71,111,0,129,228,55,96,29,75,2,55,12,0,16,84,101,109,112,32,
  40,39,67,41,0,129,251,59,131,29,135,2,67,12,0,16,84,105,109,101,
  32,40,115,101,99,115,41,0,129,230,90,131,29,2,18,27,10,0,16,73,
  110,105,116,105,97,108,0,131,80,164,21,33,184,68,14,14,0,6,6,31,
  78,101,120,116,0,6,129,204,174,134,29,2,68,31,10,0,16,67,121,99,
  108,101,115,0,7,40,48,24,24,75,20,40,8,118,64,16,24,26,7,41,
  78,24,24,74,30,41,8,118,64,16,24,26,7,39,114,24,24,75,40,40,
  8,118,64,16,24,26,7,39,145,24,24,75,50,40,8,118,64,16,24,26,
  7,71,45,24,24,133,20,40,8,118,64,16,24,26,7,70,78,24,24,133,
  30,40,8,118,64,16,24,26,7,71,112,24,24,133,40,40,8,118,64,16,
  24,26,7,71,150,24,24,133,50,40,8,118,64,16,24,26,7,41,171,24,
  24,75,70,40,8,118,64,16,24,26,129,218,138,111,24,2,58,23,10,0,
  16,70,105,110,97,108,0,7,41,129,21,19,75,60,40,8,118,64,16,24,
  26,7,70,129,21,19,133,60,40,8,118,64,16,24,26,11,0,67,74,88,
  21,24,142,29,40,10,86,16,24,67,76,107,21,24,142,42,40,10,86,16,
  24,67,62,136,21,24,142,16,40,10,86,16,24,67,82,9,21,24,142,3,
  40,10,86,16,24,68,10,17,21,95,3,2,97,80,4,8,26,26,12,1,
  131,70,100,14,33,184,68,14,14,0,6,6,31,66,97,99,107,0,9,129,
  6,19,123,29,102,2,31,12,0,16,67,121,99,108,101,0,129,1,55,136,
  29,102,15,60,12,0,16,82,101,109,97,105,110,105,110,103,0,129,2,90,
  134,29,102,28,36,12,0,16,84,97,114,103,101,116,0,1,50,19,57,57,
  103,55,78,27,3,12,31,80,67,82,71,111,0,129,22,121,87,29,102,41,
  42,12,0,16,67,117,114,114,101,110,116,0 };
  
  // This structure defines all the variables and events of your control interface 
struct {
  // Input variables
  int16_t InitialTemp; // -32768 .. +32767
  int16_t DenatureTemp; // -32768 .. +32767
  int16_t AnnealingTemp; // -32768 .. +32767
  int16_t ExtensionTemp; // -32768 .. +32767
  int16_t InitialTime; // -32768 .. +32767
  int16_t DenatureTime; // -32768 .. +32767
  int16_t AnnealingTime; // -32768 .. +32767
  int16_t ExtensionTime; // -32768 .. +32767
  int16_t Cycles; // -32768 .. +32767
  int16_t FinalTemp; // -32768 .. +32767
  int16_t FinalTime; // -32768 .. +32767
  uint8_t button_01; // =1 if button pressed, else =0, from 0 to 1

  // Output variables
  int16_t TargetTemp; // -32768 .. +32767
  int16_t CurrentTemp; // -32768 .. +32767
  int16_t TimeRemaining; // -32768 .. +32767
  int16_t CycleCount; // -32768 .. +32767
  float Lower;
  float Upper;
  float GraphTargetTemp;
  float GraphCurrentTemp;

  // Other variable
  uint8_t connect_flag;  // =1 if wire connected, else =0

} RemoteXY;   
#pragma pack(pop)

// Define the heater and fan pins
const int heaterPin = 13;
const int fanPin = 23;

// Define the PCR protocol variables
float initialDenaturationTemp;
float denaturationTemp;
float annealingTemp;
float extensionTemp;


int initialDenaturationTime;
int denaturationTime;
int annealingTime;
int extensionTime;
int numCycles;
int finalTemp;
int finalTime;

int currentCycle;
int remaining;
float targetTemp;
float currentTemp;
int pwmValue = 0;

// Forward declarations
void regulateTemperature(float targetTemp);
void runPCR();
void holdTemperature(float targetTemp, int durationSeconds);

// Define the booleans
bool pcrStarted = false;
bool pcrFinished = false;

void setup() {
  // Initiate RemoteXY
  RemoteXY_Init ();

  //Initiate EEPROM
  EEPROM.begin(RemoteXYEngine.getEepromSize());

  //Initiate MAX31865
  thermo.begin(MAX31865_2WIRE);

  //Initially turn off fan and heater
  analogWrite(fanPin, 0); 
  analogWrite(heaterPin, 0); 
}

void loop() {
  RemoteXY_Handler ();

  currentTemp = thermo.temperature(RNOMINAL, RREF);

  initialDenaturationTemp = RemoteXY.InitialTemp;
  denaturationTemp = RemoteXY.DenatureTemp;
  annealingTemp = RemoteXY.AnnealingTemp;
  extensionTemp = RemoteXY.ExtensionTemp;
  finalTemp = RemoteXY.FinalTemp;

  initialDenaturationTime = RemoteXY.InitialTime;
  denaturationTime = RemoteXY.DenatureTime;
  annealingTime = RemoteXY.AnnealingTime;
  extensionTime = RemoteXY.ExtensionTime;
  finalTime = RemoteXY.FinalTime;
  numCycles = RemoteXY.Cycles;

  RemoteXY.Lower = 0;
  RemoteXY.Upper = 100;
  RemoteXY.GraphTargetTemp = 25;
  RemoteXY.GraphCurrentTemp = currentTemp;

  RemoteXY.CycleCount = 0;
  RemoteXY.TimeRemaining = 0;
  RemoteXY.TargetTemp = 0;
  RemoteXY.CurrentTemp = abs(currentTemp);

  if (!pcrStarted && RemoteXY.button_01 == 1) {
    pcrStarted = true;
    runPCR();
    pcrFinished = true;
  }

  // After PCR finishes, cool down
  if (pcrFinished) {
    analogWrite(fanPin, 800);
    analogWrite(heaterPin, 0);
  }
}

void runPCR() {
  //Initial denaturation
  holdTemperature(initialDenaturationTemp, initialDenaturationTime);

  // PCR Cycles
  for (currentCycle = 1; currentCycle <= numCycles; currentCycle++) {
    holdTemperature(denaturationTemp, denaturationTime);
    holdTemperature(annealingTemp, annealingTime);
    holdTemperature(extensionTemp, extensionTime);
  }

  // Final extension
  holdTemperature(finalTemp, finalTime);
}

void holdTemperature(float targetTemp, int durationSeconds) {
  // Wait for temperature to be within ±0.5°C before starting the timer

  while (true) {
    RemoteXY_Handler();  // keep BLE alive

    float error = abs(currentTemp - targetTemp);

    regulateTemperature(targetTemp);  // Continue regulating while waiting

    if (error <= 0.5) {
      break;  // Start hold when stable
    }
  }

  // Once stable, begin hold timer
  unsigned long startTime =millis();
  while ((millis() - startTime) < (durationSeconds * 1000UL)) {
    RemoteXY_Handler();  // keep BLE alive
    regulateTemperature(targetTemp);
    unsigned long elapsed = (millis() - startTime) / 1000;
    remaining = durationSeconds - elapsed;
  }
}

void regulateTemperature(float targetTemp) {
currentTemp = thermo.temperature(RNOMINAL, RREF);

float heatError = targetTemp - currentTemp;   // positive when too cold
float coolError = currentTemp - targetTemp;   // positive when too hot

// Heater control
if (heatError > 6) {
  analogWrite(heaterPin, 800);
}
else if (heatError > 3) {
  analogWrite(heaterPin, 600);
}
else if (heatError > 0) {
  analogWrite(heaterPin, 400);
}
else {
  analogWrite(heaterPin, 0);
}

// Fan control
if (coolError > 2) {
  analogWrite(fanPin, 800);     // strong cooling
}
else if (coolError > 1) {
  analogWrite(fanPin, 400);     // medium cooling
}
else {
  analogWrite(fanPin, 0);
}

  RemoteXY.Lower = 0;
  RemoteXY.Upper = 100;
  RemoteXY.GraphTargetTemp = targetTemp;
  RemoteXY.GraphCurrentTemp = currentTemp;

  RemoteXY.CycleCount = currentCycle;
  RemoteXY.TimeRemaining = remaining;
  RemoteXY.TargetTemp = targetTemp;
  RemoteXY.CurrentTemp = abs(currentTemp);
}