
/**
 * @file encoder.ino
 * @brief Rotary encoder and button handling for PacoMouseCYD throttle.
 * @author F. Cañada
 * @date 2025-2026
 * @copyright https://usuaris.tinet.cat/fmco/
 *
 * This file contains interrupt service routines and functions for reading and processing
 * rotary encoder and button input, including debouncing and value management.
 */

////////////////////////////////////////////////////////////
// API Documentation
////////////////////////////////////////////////////////////
/**
 * @brief Interrupt Service Routine for the encoder.
 *
 * Sets a flag indicating the encoder needs service.
 */
void IRAM_ATTR encoderISR();

/**
 * @brief Handles encoder state changes and updates encoder value.
 */
void encoderService();

/**
 * @brief Reads the encoder button and updates its state.
 */
void readButtons();

/**
 * @brief Processes encoder movement and updates UI or state accordingly.
 */
void controlEncoder();

////////////////////////////////////////////////////////////
// End API Documentation
////////////////////////////////////////////////////////////

/*    PacoMouseCYD throttle -- F. Cañada 2025-2026 --  https://usuaris.tinet.cat/fmco/
*/

////////////////////////////////////////////////////////////
// ***** ENCODER *****
////////////////////////////////////////////////////////////

IRAM_ATTR void encoderISR () {                                    // Encoder interrupt
  encoderNeedService = true;
}


void encoderService () {                                          // Encoder interrupt service
  encoderNeedService = false;
  lastTimeEncoder = millis();
  outA = digitalRead (ENCODER_A);
  outB = digitalRead (ENCODER_B);
  if (outA != copyOutA) {                                         // evitamos rebotes
    copyOutA = outA;
    if (copyOutB == 0x80) {
      copyOutB = outB;
    }
    else {
      if ( outB != copyOutB) {
        copyOutB = 0x80;
        if (outA == outB)                                         // comprueba sentido de giro
          encoderValue = (encoderValue < encoderMax) ? ++encoderValue : encoderMax ;  // CW, hasta maximo
        else
          encoderValue = (encoderValue > 0) ? --encoderValue : 0;                     // CCW, hasta 0
        encoderChange = true;
      }
    }
  }
}


void readButtons () {
  byte inputButton;

  timeButtons = millis();                                         // lee cada cierto tiempo
  inputButton = digitalRead (ENCODER_SW);                         // comprueba cambio en boton del encoder
  if (statusSwitch != inputButton) {
    statusSwitch = inputButton;
    if (statusSwitch == LOW)
      switchOn = true;
  }
}


void controlEncoder() {                                           // encoder movement
  encoderChange = false;
  aliveAndKicking();
  DEBUG_MSG("Encoder: %d", encoderValue);
  switch (objStack[lastWinStack].objID) {
    case WIN_SSID:
      scrSSID = encoderValue;
      scanWiFiFill();
      drawObject(OBJ_TXT, TXT_SSID1);
      drawObject(OBJ_TXT, TXT_SSID2);
      drawObject(OBJ_TXT, TXT_SSID3);
      drawObject(OBJ_TXT, TXT_SSID4);
      drawObject(OBJ_TXT, TXT_SSID5);
      drawObject(OBJ_TXT, TXT_SSID6);
      break;
    case WIN_THROTTLE:
    case WIN_SPEEDO:
    case WIN_STA_PLAY:
      updateMySpeed();
      break;
    case WIN_CHG_FUNC:
      fncData[FNC_CHG].idIcon = encoderValue * 2;
      drawObject(OBJ_FNC, FNC_CHG);
      break;
    case WIN_SEL_LOCO:
      populateLocoList();
      drawObject(OBJ_TXT, TXT_SEL_ADDR1);
      drawObject(OBJ_TXT, TXT_SEL_NAME1);
      drawObject(OBJ_TXT, TXT_SEL_ADDR2);
      drawObject(OBJ_TXT, TXT_SEL_NAME2);
      drawObject(OBJ_TXT, TXT_SEL_ADDR3);
      drawObject(OBJ_TXT, TXT_SEL_NAME3);
      drawObject(OBJ_TXT, TXT_SEL_ADDR4);
      drawObject(OBJ_TXT, TXT_SEL_NAME4);
      drawObject(OBJ_TXT, TXT_SEL_ADDR5);
      drawObject(OBJ_TXT, TXT_SEL_NAME5);
      drawObject(OBJ_TXT, TXT_SEL_ADDR6);
      drawObject(OBJ_TXT, TXT_SEL_NAME6);
      break;
    case WIN_STEAM:
      showSpeedSteam((encoderValue << 1) + 240);
      break;
    case WIN_ACC_TYPE:
      fncData[FNC_ACC_TYPE].num = accDef[encoderValue].num;
      fncData[FNC_ACC_TYPE].idIcon = accDef[encoderValue].icon[0].fncIcon;
      fncData[FNC_ACC_TYPE].color = accDef[encoderValue].icon[0].color;
      fncData[FNC_ACC_TYPE].colorOn = accDef[encoderValue].icon[0].colorOn;
      drawObject(OBJ_FNC, FNC_ACC_TYPE);
      break;
  }
}


void controlSwitch() {                                            // encoder switch
  uint16_t value, value2, txtID;
  uint32_t delta, dist;
  char msg[NAME_LNG + 1];
  switchOn = false;
  aliveAndKicking();
  DEBUG_MSG("Encoder Switch");
  switch (objStack[lastWinStack].objID) {
    case WIN_SSID:
      snprintf (wifiSetting.ssid, 32, WiFi.SSID(scrSSID).c_str());     //saveSSID(scrSSID);
      DEBUG_MSG("New SSID: %s", wifiSetting.ssid);
      eepromChanged = true;
      closeWindow(WIN_SSID);
      openWindow(WIN_WIFI);
      break;
    case WIN_THROTTLE:
    case WIN_STA_PLAY:
      if (encoderValue > 0) {
        encoderValue = 0;
        if (stopMode > 0)
          locoData[myLocoData].mySpeed = 1;
        else
          locoData[myLocoData].mySpeed = 0;
        locoOperationSpeed();
      }
      else {
        locoData[myLocoData].myDir ^= 0x80;
        changeDirection();
      }
      updateSpeedDir();
      break;
    case WIN_CHG_FUNC:
      fncData[FNC_F0 + paramChild].idIcon = fncData[FNC_CHG].idIcon;
      closeWindow(WIN_CHG_FUNC);
      break;
    case WIN_SEL_LOCO:
      releaseLoco();
      txtID = (encoderValue > 5) ? 5 : encoderValue;
      if (useID) {
        value2 = (encoderValue > 5) ? encoderValue - 5 : 0;
        value = sortedLocoStack[value2 + txtID];
      }
      else {
        value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
      }
      //value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
      DEBUG_MSG("Selected Loco %d", value);
      closeWindow(WIN_SEL_LOCO);
      getNewLoco(value);
      break;
    case WIN_SPEEDO:
      switch (speedoPhase) {                                      //enum speedo {SPD_WAIT, SPD_BEGIN, SPD_COUNT, SPD_ARRIVE, SPD_END};
        case SPD_WAIT:
          if (getCurrentStep() > 0) {
            speedoStartTime = millis();
            setSpeedoPhase(SPD_BEGIN);
            getLabelTxt(LBL_MEASURE, msg);
            snprintf(spdSpeedBuf, NAME_LNG + 1, "%s", msg);
            drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
            setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
          }
          else {
            locoData[myLocoData].myDir ^= 0x80;
            changeDirection();
            updateSpeedDir();
          }
          break;
        case SPD_BEGIN:
        case SPD_COUNT:
          speedoEndTime = millis();
          setSpeedoPhase(SPD_ARRIVE);
          setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
          dist = speedoLength * 36 * speedoScale;
          delta = (speedoEndTime - speedoStartTime) * 10;
          speedoSpeed = dist / delta;
          snprintf(spdSpeedBuf, NAME_LNG + 1, "%d km/h", speedoSpeed);
          drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
          break;
        case SPD_ARRIVE:
          break;
        case SPD_END:
          break;
      }
      break;
    case WIN_STEAM:
      steamThrottleStop();
      currentSteamSpeed = 0;
      locoData[myLocoData].mySpeed = 0;
      locoOperationSpeed();
      break;
    case WIN_ACC_TYPE:
      accTypeClick();
      break;
  }
}
