/*
 * ========================== timeout support ==============================
 */

#include "timeouts.h"
#include "textBuffer.h"
#include "loopScanner.h"

#define NUMTICKERS    5

#ifdef ENV_ESP32
  hw_timer_t * timer = NULL;
#elif defined(ENV_RP2040)
  struct repeating_timer timer;
#else
#error unrecognized ENV for timer variable
#endif

DFuncParPtr tickers[NUMTICKERS];
int tickerparams[NUMTICKERS];
byte tickerCount = 0;
long ticks = 0;

//
// stop a timeout
//
void stopTimeout(int num) {
  timeouts[num].isRunning = false;
  timeouts[num].ticks = 0;
  // SERIAL_PRINTF("stopTimeout.ID=%d.stopped\n",num);
}

//
// register a ticker, a function to be called at every basic tick (1 ms) in interrupt mode
//
void registerTicker(DFuncParPtr newTicker, int newParam) {
  if (tickerCount>=NUMTICKERS) return;
  tickers[tickerCount] = newTicker;
  tickerparams[tickerCount++] = newParam;
}

//
// find a free timeout slot, -1 if there is no available slot 
//
int findTimeoutSlot() {
  for (int i=0; i<NUM_TIMEOUTS; i++) {
    if (!timeouts[i].isRunning) {
      return i;
    }
  }
  return -1;
}

//
// start a timeout of <ticksToRun>, on completion <callback> is invoked with <callbackParam> as parameter
//
int startTimeout(int ticksToRun, TimeoutCompletionFunction callback, int callbackParam) {
  int slot = findTimeoutSlot();
  if (slot<0) {
    return slot;
  }
  timeouts[slot].completionParam = callbackParam;
  timeouts[slot].callOnCompletion = callback;
  timeouts[slot].ticks = ticksToRun;
  timeouts[slot].isRunning = true;
  // SERIAL_PRINTF("startTimeout.millis=%s.ticks=%d.ID=%d.started\n",formatLongGrouped(millis()).c_str(),ticksToRun,slot);
  return slot;
}

//
// start a timeout issuing an event on completion
//
int startEventTimeout(int ticksToRun, int completionEvent) {
  return startTimeout(ticksToRun,eventTimeoutComplete,completionEvent);
}

//
// restart a timeout issuing an event on completion
//
int restartEventTimeout(int num, int ticksToRun, int completionEvent) {
  stopTimeout(num);
  return startTimeout(ticksToRun,eventTimeoutComplete,completionEvent);
}


//
// completion function for an event timeout
//
void eventTimeoutComplete(int completionEvent) {
  putEventQueueEntry(completionEvent,0);
}

//
// basic tick timer interrupt service function, this basic tick timer is called by a arduino system timer every BASIC_TICK_US microseconds
//
#if defined(ENV_ESP32)
void IRAM_ATTR tickTimer()
#elif defined(ENV_RP2040)
bool tickTimer(struct repeating_timer * timer)
#else
#error unrecognized ENV for tick_timer()
#endif
{ ticks++;
  if ((ticks%1000)==0) {
    SERIAL_PRINTF("tickTimer.ticks=%d.millis=%s\n",ticks,formatLongGrouped(millis()).c_str());
  }
  for (int i=0; i<NUM_TIMEOUTS; i++) {                                    // visit all timeouts
    if (timeouts[i].isRunning) {                                          // handle only running timeouts:
      if (timeouts[i].ticks>0) {                                            // if current timeout still has ticks left ...
        timeouts[i].ticks--;                                                  // decrement left ticks count for the current timeout
        // if ((ticks%1000)==0) SERIAL_PRINTF("tickTimer.timeout[%d].ticks=%d\n",i,timeouts[i].ticks);
        if (timeouts[i].ticks<1) {                                            // if current timeout now has arrived at 0 (it has expired) ...
          timeouts[i].isRunning = false;                                      // mark it as no more running
          if (timeouts[i].callOnCompletion) {                                 // if there is a function to be called on completion ...
            // SERIAL_PRINTF("timeoutExpired.ID=%d.hasExpired.millis=%s.callingCompletionFunction=0x%08X.param=%d\n",i,formatLongGrouped(millis()).c_str(),timeouts[i].callOnCompletion,timeouts[i].completionParam);
            timeouts[i].callOnCompletion(timeouts[i].completionParam);          // .. execute the completion function, => so all completion functions run in interrupt mode!
          }
        }
      }
    }
  }
  for (byte tickernum = 0; tickernum<tickerCount; tickernum++ ) {       // now invoke all registered ticker functions
    tickers[tickernum](tickerparams[tickernum]);
  }
  return true;
}

//
// ---------- set up timeout mechanism ------------------------------------------------
//
void initTimeouts() {
  for (int i=0; i<NUM_TIMEOUTS; i++) {              // bring all possible timeouts into stopped state
    stopTimeout(i);
  }
#if defined(ENV_AVR)                                      // on AVR-Arduino, basic tick timer is TimerOne
  Timer1.initialize(BASIC_TICK_US);   
  Timer1.attachInterrupt(tickTimer);
#elif defined(ENV_ESP32)
  // PRE API 3.0:  timer = timerBegin(0, 80, true);                  // in ESP32, basic tick timer is timer #0
  timer = timerBegin(1000000);                      // ESP32 Arduino API 3.0: initialize timer to run at 1 MHz
  // PRE API 3.0: timerAttachInterrupt(timer, &tickTimer, true);    // Attach tickTimer function to our timer.
  timerAttachInterrupt(timer, &tickTimer);    // ESP32 Arduino API 3.0: Attach tickTimer function to our timer.
  /* PRE API 3.0:
  timerAlarmWrite(timer, BASIC_TICK_US, true);      // Set alarm to call basic tick timer (value in microseconds). Repeat the alarm (third parameter)
  timerAlarmEnable(timer);
  */
  /* ESP32 Arduino API 3.0: */
  timerAlarm(timer, BASIC_TICK_US, true, 0);        // set alarm timer (1st param) to 20ms (2nd param), automatic repeat (3rd param), unlimited repeats (4th param)
  ticks = 0;
#elif defined(ENV_RP2040)
  add_repeating_timer_us(0-BASIC_TICK_US,tickTimer,NULL,&timer);
#else 
#error unrecognized ENV for initTimeouts()
#endif
  SERIAL_PRINTLN("timeouts initialized");
}
