/*
  xdrv_26_ariluxrf.ino - Arilux Rf support for Tasmota

  Copyright (C) 2021  Theo Arends

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef USE_LIGHT
#ifdef USE_ARILUX_RF
/*********************************************************************************************\
 * Arilux LC11 Rf support stripped from RCSwitch library
\*********************************************************************************************/

#define XDRV_26              26

const uint32_t ARILUX_RF_TIME_AVOID_DUPLICATE = 1000;  // Milliseconds

const uint8_t ARILUX_RF_MAX_CHANGES = 51;              // Pulses (sync + 2 x 24 bits)
const uint32_t ARILUX_RF_SEPARATION_LIMIT = 4300;      // Microseconds
const uint32_t ARILUX_RF_RECEIVE_TOLERANCE = 60;       // Percentage

struct ARILUX {
  int rf_timings[ARILUX_RF_MAX_CHANGES];

  unsigned long rf_received_value = 0;
  unsigned long rf_last_received_value = 0;
  unsigned long rf_last_time = 0;
  unsigned long rf_lasttime = 0;

  unsigned int rf_change_count = 0;
  unsigned int rf_repeat_count = 0;

  uint8_t rf_toggle = 0;
} Arilux;

#ifndef USE_WS2812_DMA                         // Collides with Neopixelbus but solves RF misses
void AriluxRfInterrupt(void) IRAM_ATTR;  // As iram is tight and it works this way too
#endif  // USE_WS2812_DMA

void AriluxRfInterrupt(void)
{
  unsigned long time = micros();
  int duration = time - Arilux.rf_lasttime;

  if (duration > ARILUX_RF_SEPARATION_LIMIT) {
    if (abs(duration - Arilux.rf_timings[0]) < 200) {
      Arilux.rf_repeat_count++;
      if (Arilux.rf_repeat_count == 2) {
        unsigned long code = 0;
        const int delay = Arilux.rf_timings[0] / 31;
        const int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100;
        for (unsigned int i = 1; i < Arilux.rf_change_count -1; i += 2) {
          code <<= 1;
          if (abs(Arilux.rf_timings[i] - (delay *3)) < delayTolerance && abs(Arilux.rf_timings[i +1] - delay) < delayTolerance) {
            code |= 1;
          }
        }
        if (Arilux.rf_change_count > 49) {  // Need 1 sync bit and 24 data bits
          Arilux.rf_received_value = code;
        }
        Arilux.rf_repeat_count = 0;
      }
    }
    Arilux.rf_change_count = 0;
  }
  if (Arilux.rf_change_count >= ARILUX_RF_MAX_CHANGES) {
    Arilux.rf_change_count = 0;
    Arilux.rf_repeat_count = 0;
  }
  Arilux.rf_timings[Arilux.rf_change_count++] = duration;
  Arilux.rf_lasttime = time;
}

void AriluxRfHandler(void)
{
  unsigned long now = millis();
  if (Arilux.rf_received_value && !((Arilux.rf_received_value == Arilux.rf_last_received_value) && (now - Arilux.rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) {
    Arilux.rf_last_received_value = Arilux.rf_received_value;
    Arilux.rf_last_time = now;

    uint16_t hostcode = Arilux.rf_received_value >> 8 & 0xFFFF;
    if (Settings->rf_code[1][6] == Settings->rf_code[1][7]) {
      Settings->rf_code[1][6] = hostcode >> 8 & 0xFF;
      Settings->rf_code[1][7] = hostcode & 0xFF;
    }
    uint16_t stored_hostcode = Settings->rf_code[1][6] << 8 | Settings->rf_code[1][7];

//    DEBUG_DRIVER_LOG(PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value);
    AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value);

    if (hostcode == stored_hostcode) {
      char command[33];
      char value = '-';
      command[0] = '\0';
      uint8_t  keycode = Arilux.rf_received_value & 0xFF;
      switch (keycode) {
        case 1:  // Power On
        case 3:  // Power Off
          snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0);
          break;
        case 2:  // Toggle
          Arilux.rf_toggle++;
          Arilux.rf_toggle &= 0x3;
          snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + Arilux.rf_toggle);
          break;
        case 4:  // Speed +
          value = '+';
        case 7:  // Speed -
          snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value);
          break;
        case 5:  // Scheme +
          value = '+';
        case 8:  // Scheme -
          snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value);
          break;
        case 6:  // Dimmer +
          value = '+';
        case 9:  // Dimmer -
          snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value);
          break;
        default: {
          if ((keycode >= 10) && (keycode <= 21)) {
            snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9);
          }
        }
      }
      if (strlen(command)) {
        ExecuteCommand(command, SRC_LIGHT);
      }
    }
  }
  Arilux.rf_received_value = 0;
}

void AriluxRfInit(void)
{
  if (PinUsed(GPIO_ARIRFRCV) && PinUsed(GPIO_ARIRFSEL)) {
    if (TasmotaGlobal.module_changed) {
      Settings->rf_code[1][6] = 0;
      Settings->rf_code[1][7] = 0;
    }
    Arilux.rf_received_value = 0;

    digitalWrite(Pin(GPIO_ARIRFSEL), 0);  // Turn on RF
    attachInterrupt(Pin(GPIO_ARIRFRCV), AriluxRfInterrupt, CHANGE);
  }
}

void AriluxRfDisable(void)
{
  if (PinUsed(GPIO_ARIRFRCV) && PinUsed(GPIO_ARIRFSEL)) {
    detachInterrupt(Pin(GPIO_ARIRFRCV));
    digitalWrite(Pin(GPIO_ARIRFSEL), 1);  // Turn off RF
  }
}

/*********************************************************************************************\
 * Interface
\*********************************************************************************************/

bool Xdrv26(uint8_t function)
{
  bool result = false;

  switch (function) {
    case FUNC_EVERY_50_MSECOND:
      if (PinUsed(GPIO_ARIRFRCV)) { AriluxRfHandler(); }
      break;
    case FUNC_EVERY_SECOND:
      if (10 == TasmotaGlobal.uptime) { AriluxRfInit(); }  // Needs rest before enabling RF interrupts
      break;
  }
  return result;
}

#endif  // USE_ARILUX_RF
#endif  // USE_LIGHT