#include "rc_switch_protocol.h"
#include "esphome/core/log.h"

namespace esphome {
namespace remote_base {

static const char *const TAG = "remote.rc_switch";

const RCSwitchBase RC_SWITCH_PROTOCOLS[9] = {RCSwitchBase(0, 0, 0, 0, 0, 0, false),
                                             RCSwitchBase(350, 10850, 350, 1050, 1050, 350, false),
                                             RCSwitchBase(650, 6500, 650, 1300, 1300, 650, false),
                                             RCSwitchBase(3000, 7100, 400, 1100, 900, 600, false),
                                             RCSwitchBase(380, 2280, 380, 1140, 1140, 380, false),
                                             RCSwitchBase(3000, 7000, 500, 1000, 1000, 500, false),
                                             RCSwitchBase(10350, 450, 450, 900, 900, 450, true),
                                             RCSwitchBase(300, 9300, 150, 900, 900, 150, false),
                                             RCSwitchBase(250, 2500, 250, 1250, 250, 250, false)};

RCSwitchBase::RCSwitchBase(uint32_t sync_high, uint32_t sync_low, uint32_t zero_high, uint32_t zero_low,
                           uint32_t one_high, uint32_t one_low, bool inverted)
    : sync_high_(sync_high),
      sync_low_(sync_low),
      zero_high_(zero_high),
      zero_low_(zero_low),
      one_high_(one_high),
      one_low_(one_low),
      inverted_(inverted) {}

void RCSwitchBase::one(RemoteTransmitData *dst) const {
  if (!this->inverted_) {
    dst->mark(this->one_high_);
    dst->space(this->one_low_);
  } else {
    dst->space(this->one_high_);
    dst->mark(this->one_low_);
  }
}
void RCSwitchBase::zero(RemoteTransmitData *dst) const {
  if (!this->inverted_) {
    dst->mark(this->zero_high_);
    dst->space(this->zero_low_);
  } else {
    dst->space(this->zero_high_);
    dst->mark(this->zero_low_);
  }
}
void RCSwitchBase::sync(RemoteTransmitData *dst) const {
  if (!this->inverted_) {
    dst->mark(this->sync_high_);
    dst->space(this->sync_low_);
  } else {
    dst->space(this->sync_high_);
    dst->mark(this->sync_low_);
  }
}
void RCSwitchBase::transmit(RemoteTransmitData *dst, uint64_t code, uint8_t len) const {
  dst->set_carrier_frequency(0);
  this->sync(dst);
  for (int16_t i = len - 1; i >= 0; i--) {
    if (code & ((uint64_t) 1 << i))
      this->one(dst);
    else
      this->zero(dst);
  }
}

bool RCSwitchBase::expect_one(RemoteReceiveData &src) const {
  if (!this->inverted_) {
    if (!src.peek_mark(this->one_high_))
      return false;
    if (!src.peek_space(this->one_low_, 1))
      return false;
  } else {
    if (!src.peek_space(this->one_high_))
      return false;
    if (!src.peek_mark(this->one_low_, 1))
      return false;
  }
  src.advance(2);
  return true;
}
bool RCSwitchBase::expect_zero(RemoteReceiveData &src) const {
  if (!this->inverted_) {
    if (!src.peek_mark(this->zero_high_))
      return false;
    if (!src.peek_space(this->zero_low_, 1))
      return false;
  } else {
    if (!src.peek_space(this->zero_high_))
      return false;
    if (!src.peek_mark(this->zero_low_, 1))
      return false;
  }
  src.advance(2);
  return true;
}
bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const {
  if (!this->inverted_) {
    if (!src.peek_mark(this->sync_high_))
      return false;
    if (!src.peek_space(this->sync_low_, 1))
      return false;
  } else {
    if (!src.peek_space(this->sync_high_))
      return false;
    if (!src.peek_mark(this->sync_low_, 1))
      return false;
  }
  src.advance(2);
  return true;
}
bool RCSwitchBase::decode(RemoteReceiveData &src, uint64_t *out_data, uint8_t *out_nbits) const {
  // ignore if sync doesn't exist
  this->expect_sync(src);

  *out_data = 0;
  for (*out_nbits = 0; *out_nbits < 64; *out_nbits += 1) {
    if (this->expect_zero(src)) {
      *out_data <<= 1;
      *out_data |= 0;
    } else if (this->expect_one(src)) {
      *out_data <<= 1;
      *out_data |= 1;
    } else {
      return *out_nbits >= 8;
    }
  }
  return true;
}
optional<RCSwitchData> RCSwitchBase::decode(RemoteReceiveData &src) const {
  RCSwitchData out;
  uint8_t out_nbits;
  for (uint8_t i = 1; i <= 8; i++) {
    src.reset();
    const RCSwitchBase *protocol = &RC_SWITCH_PROTOCOLS[i];
    if (protocol->decode(src, &out.code, &out_nbits) && out_nbits >= 3) {
      out.protocol = i;
      return out;
    }
  }
  return {};
}

void RCSwitchBase::simple_code_to_tristate(uint16_t code, uint8_t nbits, uint64_t *out_code) {
  *out_code = 0;
  for (int8_t i = nbits - 1; i >= 0; i--) {
    *out_code <<= 2;
    if (code & (1 << i))
      *out_code |= 0b01;
    else
      *out_code |= 0b00;
  }
}
void RCSwitchBase::type_a_code(uint8_t switch_group, uint8_t switch_device, bool state, uint64_t *out_code,
                               uint8_t *out_nbits) {
  uint16_t code = 0;
  code = switch_group ^ 0b11111;
  code <<= 5;
  code |= switch_device ^ 0b11111;
  code <<= 2;
  code |= state ? 0b01 : 0b10;
  simple_code_to_tristate(code, 12, out_code);
  *out_nbits = 24;
}
void RCSwitchBase::type_b_code(uint8_t address_code, uint8_t channel_code, bool state, uint64_t *out_code,
                               uint8_t *out_nbits) {
  uint16_t code = 0;
  code |= (address_code == 1) ? 0 : 0b1000;
  code |= (address_code == 2) ? 0 : 0b0100;
  code |= (address_code == 3) ? 0 : 0b0010;
  code |= (address_code == 4) ? 0 : 0b0001;
  code <<= 4;
  code |= (channel_code == 1) ? 0 : 0b1000;
  code |= (channel_code == 2) ? 0 : 0b0100;
  code |= (channel_code == 3) ? 0 : 0b0010;
  code |= (channel_code == 4) ? 0 : 0b0001;
  code <<= 4;
  code |= 0b1110;
  code |= state ? 0b1 : 0b0;
  simple_code_to_tristate(code, 12, out_code);
  *out_nbits = 24;
}
void RCSwitchBase::type_c_code(uint8_t family, uint8_t group, uint8_t device, bool state, uint64_t *out_code,
                               uint8_t *out_nbits) {
  uint16_t code = 0;
  code |= (family & 0b0001) ? 0b1000 : 0;
  code |= (family & 0b0010) ? 0b0100 : 0;
  code |= (family & 0b0100) ? 0b0010 : 0;
  code |= (family & 0b1000) ? 0b0001 : 0;
  code <<= 4;
  code |= ((device - 1) & 0b01) ? 0b1000 : 0;
  code |= ((device - 1) & 0b10) ? 0b0100 : 0;
  code |= ((group - 1) & 0b01) ? 0b0010 : 0;
  code |= ((group - 1) & 0b10) ? 0b0001 : 0;
  code <<= 4;
  code |= 0b0110;
  code |= state ? 0b1 : 0b0;
  simple_code_to_tristate(code, 12, out_code);
  *out_nbits = 24;
}
void RCSwitchBase::type_d_code(uint8_t group, uint8_t device, bool state, uint64_t *out_code, uint8_t *out_nbits) {
  *out_code = 0;
  *out_code |= (group == 0) ? 0b11000000 : 0b01000000;
  *out_code |= (group == 1) ? 0b00110000 : 0b00010000;
  *out_code |= (group == 2) ? 0b00001100 : 0b00000100;
  *out_code |= (group == 3) ? 0b00000011 : 0b00000001;
  *out_code <<= 6;
  *out_code |= (device == 1) ? 0b110000 : 0b010000;
  *out_code |= (device == 2) ? 0b001100 : 0b000100;
  *out_code |= (device == 3) ? 0b000011 : 0b000001;
  *out_code <<= 6;
  *out_code |= 0b000000;
  *out_code <<= 4;
  *out_code |= state ? 0b1100 : 0b0011;
  *out_nbits = 24;
}

uint64_t decode_binary_string(const std::string &data) {
  uint64_t ret = 0;
  for (char c : data) {
    ret <<= 1UL;
    ret |= (c != '0');
  }
  return ret;
}

uint64_t decode_binary_string_mask(const std::string &data) {
  uint64_t ret = 0;
  for (char c : data) {
    ret <<= 1UL;
    ret |= (c != 'x');
  }
  return ret;
}

bool RCSwitchRawReceiver::matches(RemoteReceiveData src) {
  uint64_t decoded_code;
  uint8_t decoded_nbits;
  if (!this->protocol_.decode(src, &decoded_code, &decoded_nbits))
    return false;

  return decoded_nbits == this->nbits_ && (decoded_code & this->mask_) == (this->code_ & this->mask_);
}
bool RCSwitchDumper::dump(RemoteReceiveData src) {
  for (uint8_t i = 1; i <= 8; i++) {
    src.reset();
    uint64_t out_data;
    uint8_t out_nbits;
    const RCSwitchBase *protocol = &RC_SWITCH_PROTOCOLS[i];
    if (protocol->decode(src, &out_data, &out_nbits) && out_nbits >= 3) {
      char buffer[65];
      for (uint8_t j = 0; j < out_nbits; j++)
        buffer[j] = (out_data & ((uint64_t) 1 << (out_nbits - j - 1))) ? '1' : '0';

      buffer[out_nbits] = '\0';
      ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer);

      // only send first decoded protocol
      return true;
    }
  }
  return false;
}

}  // namespace remote_base
}  // namespace esphome
