// LwRx.cpp
//
// LightwaveRF 434MHz receiver interface for Arduino
//
// Author: Bob Tidey (robert@tideys.net)

#ifdef USE_ESP8266

#include "LwRx.h"
#include <cstring>
#include "esphome/core/helpers.h"

namespace esphome::lightwaverf {

/**
  Pin change interrupt routine that identifies 1 and 0 LightwaveRF bits
  and constructs a message when a valid packet of data is received.
**/

void IRAM_ATTR LwRx::rx_process_bits(LwRx *args) {
  uint8_t event = args->rx_pin_isr_.digital_read();  // start setting event to the current value
  uint32_t curr = micros();                          // the current time in microseconds

  uint16_t dur = (curr - args->rx_prev);  // unsigned int
  args->rx_prev = curr;
  // set event based on input and duration of previous pulse
  if (dur < 120) {         // 120 very short
  } else if (dur < 500) {  // normal short pulse
    event += 2;
  } else if (dur < 2000) {  // normal long pulse
    event += 4;
  } else if (dur > 5000) {  // gap between messages
    event += 6;
  } else {      // 2000 > 5000
    event = 8;  // illegal gap
  }
  // state machine transitions
  switch (args->rx_state) {
    case RX_STATE_IDLE:
      switch (event) {
        case 7:  // 1 after a message gap
          args->rx_state = RX_STATE_MSGSTARTFOUND;
          break;
      }
      break;
    case RX_STATE_MSGSTARTFOUND:
      switch (event) {
        case 2:  // 0 160->500
                 // nothing to do wait for next positive edge
          break;
        case 3:  // 1 160->500
          args->rx_num_bytes = 0;
          args->rx_state = RX_STATE_BYTESTARTFOUND;
          break;
        default:
          // not good start again
          args->rx_state = RX_STATE_IDLE;
          break;
      }
      break;
    case RX_STATE_BYTESTARTFOUND:
      switch (event) {
        case 2:  // 0 160->500
          // nothing to do wait for next positive edge
          break;
        case 3:  // 1 160->500
          args->rx_state = RX_STATE_GETBYTE;
          args->rx_num_bits = 0;
          break;
        case 5:  // 0 500->1500
          args->rx_state = RX_STATE_GETBYTE;
          // Starts with 0 so put this into uint8_t
          args->rx_num_bits = 1;
          args->rx_buf[args->rx_num_bytes] = 0;
          break;
        default:
          // not good start again
          args->rx_state = RX_STATE_IDLE;
          break;
      }
      break;
    case RX_STATE_GETBYTE:
      switch (event) {
        case 2:  // 0 160->500
          // nothing to do wait for next positive edge but do stats
          if (args->lwrx_stats_enable) {
            args->lwrx_stats[RX_STAT_HIGH_MAX] = std::max(args->lwrx_stats[RX_STAT_HIGH_MAX], dur);
            args->lwrx_stats[RX_STAT_HIGH_MIN] = std::min(args->lwrx_stats[RX_STAT_HIGH_MIN], dur);
            args->lwrx_stats[RX_STAT_HIGH_AVE] =
                args->lwrx_stats[RX_STAT_HIGH_AVE] - (args->lwrx_stats[RX_STAT_HIGH_AVE] >> 4) + dur;
          }
          break;
        case 3:  // 1 160->500
          // a single 1
          args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 1 | 1;
          args->rx_num_bits++;
          if (args->lwrx_stats_enable) {
            args->lwrx_stats[RX_STAT_LOW1_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW1_MAX], dur);
            args->lwrx_stats[RX_STAT_LOW1_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW1_MIN], dur);
            args->lwrx_stats[RX_STAT_LOW1_AVE] =
                args->lwrx_stats[RX_STAT_LOW1_AVE] - (args->lwrx_stats[RX_STAT_LOW1_AVE] >> 4) + dur;
          }
          break;
        case 5:  // 1 500->1500
          // a 1 followed by a 0
          args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 2 | 2;
          args->rx_num_bits++;
          args->rx_num_bits++;
          if (args->lwrx_stats_enable) {
            args->lwrx_stats[RX_STAT_LOW0_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW0_MAX], dur);
            args->lwrx_stats[RX_STAT_LOW0_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW0_MIN], dur);
            args->lwrx_stats[RX_STAT_LOW0_AVE] =
                args->lwrx_stats[RX_STAT_LOW0_AVE] - (args->lwrx_stats[RX_STAT_LOW0_AVE] >> 4) + dur;
          }
          break;
        default:
          // not good start again
          args->rx_state = RX_STATE_IDLE;
          break;
      }
      if (args->rx_num_bits >= 8) {
        args->rx_num_bytes++;
        args->rx_num_bits = 0;
        if (args->rx_num_bytes >= RX_MSGLEN) {
          uint32_t curr_millis = millis();
          if (args->rx_repeats > 0) {
            if ((curr_millis - args->rx_prevpkttime) / 100 > args->rx_timeout) {
              args->rx_repeatcount = 1;
            } else {
              // Test message same as last one
              int16_t i = RX_MSGLEN;  // int
              do {
                i--;
              } while ((i >= 0) && (args->rx_msg[i] == args->rx_buf[i]));
              if (i < 0) {
                args->rx_repeatcount++;
              } else {
                args->rx_repeatcount = 1;
              }
            }
          } else {
            args->rx_repeatcount = 0;
          }
          args->rx_prevpkttime = curr_millis;
          // If last message hasn't been read it gets overwritten
          memcpy(args->rx_msg, args->rx_buf, RX_MSGLEN);
          if (args->rx_repeats == 0 || args->rx_repeatcount == args->rx_repeats) {
            if (args->rx_pairtimeout != 0) {
              if ((curr_millis - args->rx_pairstarttime) / 100 <= args->rx_pairtimeout) {
                if (args->rx_msg[3] == RX_CMD_ON) {
                  args->rx_addpairfrommsg_();
                } else if (args->rx_msg[3] == RX_CMD_OFF) {
                  args->rx_remove_pair_(&args->rx_msg[2]);
                }
              }
            }
            if (args->rx_report_message_()) {
              args->rx_msgcomplete = true;
            }
            args->rx_pairtimeout = 0;
          }
          // And cycle round for next one
          args->rx_state = RX_STATE_IDLE;
        } else {
          args->rx_state = RX_STATE_BYTESTARTFOUND;
        }
      }
      break;
  }
}

/**
  Test if a message has arrived
**/
bool LwRx::lwrx_message() { return (this->rx_msgcomplete); }

/**
  Set translate mode
**/
void LwRx::lwrx_settranslate(bool rxtranslate) { this->rx_translate = rxtranslate; }
/**
  Transfer a message to user buffer
**/
bool LwRx::lwrx_getmessage(uint8_t *buf, uint8_t len) {
  bool ret = true;
  int16_t j = 0;  // int
  if (this->rx_msgcomplete && len <= RX_MSGLEN) {
    // Copy message under interrupt lock to prevent ISR overwriting rx_msg mid-read
    uint8_t msg_copy[RX_MSGLEN];
    {
      InterruptLock lock;
      memcpy(msg_copy, this->rx_msg, RX_MSGLEN);
      this->rx_msgcomplete = false;
    }
    for (uint8_t i = 0; ret && i < RX_MSGLEN; i++) {
      if (this->rx_translate || (len != RX_MSGLEN)) {
        j = this->rx_find_nibble_(msg_copy[i]);
        if (j < 0)
          ret = false;
      } else {
        j = msg_copy[i];
      }
      switch (len) {
        case 4:
          if (i == 9)
            buf[2] = j;
          if (i == 2)
            buf[3] = j;
          [[fallthrough]];
        case 2:
          if (i == 3)
            buf[0] = j;
          if (i == 0)
            buf[1] = j << 4;
          if (i == 1)
            buf[1] += j;
          break;
        case 10:
          buf[i] = j;
          break;
      }
    }
  } else {
    ret = false;
  }
  return ret;
}

/**
  Return time in milliseconds since last packet received
**/
uint32_t LwRx::lwrx_packetinterval() { return millis() - this->rx_prevpkttime; }

/**
  Set up repeat filtering of received messages
**/
void LwRx::lwrx_setfilter(uint8_t repeats, uint8_t timeout) {
  this->rx_repeats = repeats;
  this->rx_timeout = timeout;
}

/**
  Add a pair to filter received messages
  pairdata is device,dummy,5*addr,room
  pairdata is held in translated form to make comparisons quicker
**/
uint8_t LwRx::lwrx_addpair(const uint8_t *pairdata) {
  if (this->rx_paircount < RX_MAXPAIRS) {
    for (uint8_t i = 0; i < 8; i++) {
      this->rx_pairs[rx_paircount][i] = RX_NIBBLE[pairdata[i]];
    }
    this->rx_paircommit_();
  }
  return this->rx_paircount;
}

/**
  Make a pair from next message successfully received
**/
void LwRx::lwrx_makepair(uint8_t timeout) {
  this->rx_pairtimeout = timeout;
  this->rx_pairstarttime = millis();
}

/**
  Get pair data (translated back to nibble form
**/
uint8_t LwRx::lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber) {
  if (pairnumber < this->rx_paircount) {
    int16_t j;  // int
    for (uint8_t i = 0; i < 8; i++) {
      j = this->rx_find_nibble_(this->rx_pairs[pairnumber][i]);
      if (j >= 0)
        pairdata[i] = j;
    }
  }
  return this->rx_paircount;
}

/**
  Clear all pairing
**/
void LwRx::lwrx_clearpairing_() { rx_paircount = 0; }

/**
  Return stats on high and low pulses
**/
bool LwRx::lwrx_getstats_(uint16_t *stats) {  // unsigned int
  if (this->lwrx_stats_enable) {
    memcpy(stats, this->lwrx_stats, 2 * RX_STAT_COUNT);
    return true;
  } else {
    return false;
  }
}

/**
  Set stats mode
**/
void LwRx::lwrx_setstatsenable_(bool rx_stats_enable) {
  this->lwrx_stats_enable = rx_stats_enable;
  if (!this->lwrx_stats_enable) {
    // clear down stats when disabling
    memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT));
  }
}
/**
  Set pairs behaviour
**/
void LwRx::lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only) {
  this->rx_pairEnforce = pair_enforce;
  this->rx_pairBaseOnly = pair_base_only;
}

/**
  Set things up to receive LightWaveRF 434Mhz messages
  pin must be 2 or 3 to trigger interrupts
  !!! For Spark, any pin will work
**/
void LwRx::lwrx_setup(InternalGPIOPin *pin) {
  // rx_pin = pin;
  pin->setup();
  this->rx_pin_isr_ = pin->to_isr();
  pin->attach_interrupt(&LwRx::rx_process_bits, this, gpio::INTERRUPT_ANY_EDGE);

  memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT));
}

/**
  Check a message to see if it should be reported under pairing / mood / all off rules
  returns -1 if none found
**/
bool LwRx::rx_report_message_() {
  if (this->rx_pairEnforce && this->rx_paircount == 0) {
    return false;
  } else {
    bool all_devices;
    // True if mood to device 15 or Off cmd with Allof paramater
    all_devices = ((this->rx_msg[3] == RX_CMD_MOOD && this->rx_msg[2] == RX_DEV_15) ||
                   (this->rx_msg[3] == RX_CMD_OFF && this->rx_msg[0] == RX_PAR0_ALLOFF));
    return (rx_check_pairs_(&this->rx_msg[2], all_devices) != -1);
  }
}
/**
  Find nibble from byte
  returns -1 if none found
**/
int16_t LwRx::rx_find_nibble_(uint8_t data) {  // int
  int16_t i = 15;                              // int
  do {
    if (RX_NIBBLE[i] == data)
      break;
    i--;
  } while (i >= 0);
  return i;
}

/**
  add pair from message buffer
**/
void LwRx::rx_addpairfrommsg_() {
  if (this->rx_paircount < RX_MAXPAIRS) {
    memcpy(this->rx_pairs[this->rx_paircount], &this->rx_msg[2], 8);
    this->rx_paircommit_();
  }
}

/**
  check and commit pair
**/
void LwRx::rx_paircommit_() {
  if (this->rx_paircount == 0 || this->rx_check_pairs_(this->rx_pairs[this->rx_paircount], false) < 0) {
    this->rx_paircount++;
  }
}

/**
  Check to see if message matches one of the pairs
    if mode is pairBase only then ignore device and room
    if allDevices is true then ignore the device number
  Returns matching pair number, -1 if not found, -2 if no pairs defined
**/
int16_t LwRx::rx_check_pairs_(const uint8_t *buf, bool all_devices) {  // int
  if (this->rx_paircount == 0) {
    return -2;
  } else {
    int16_t pair = this->rx_paircount;  // int
    int16_t j = -1;                     // int
    int16_t jstart, jend;               // int
    if (this->rx_pairBaseOnly) {
      // skip room(8) and dev/cmd (0,1)
      jstart = 7;
      jend = 2;
    } else {
      // include room in comparison
      jstart = 8;
      // skip device comparison if allDevices true
      jend = (all_devices) ? 2 : 0;
    }
    while (pair > 0 && j < 0) {
      pair--;
      j = jstart;
      while (j > jend) {
        j--;
        if (j != 1) {
          if (this->rx_pairs[pair][j] != buf[j]) {
            j = -1;
          }
        }
      }
    }
    return (j >= 0) ? pair : -1;
  }
}

/**
  Remove an existing pair matching the buffer
**/
void LwRx::rx_remove_pair_(uint8_t *buf) {
  int16_t pair = this->rx_check_pairs_(buf, false);  // int
  if (pair >= 0) {
    while (pair < this->rx_paircount - 1) {
      for (uint8_t j = 0; j < 8; j++) {
        this->rx_pairs[pair][j] = this->rx_pairs[pair + 1][j];
      }
      pair++;
    }
    this->rx_paircount--;
  }
}

}  // namespace esphome::lightwaverf
#endif
