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

#include <cinttypes>

namespace esphome {
namespace remote_base {

static const char *const TAG = "remote_base";

/* RemoteReceiveData */

bool RemoteReceiveData::peek_mark(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t lo = this->lower_bound_(length);
  const int32_t hi = this->upper_bound_(length);
  return value >= 0 && lo <= value && value <= hi;
}

bool RemoteReceiveData::peek_mark_at_least(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t lo = this->lower_bound_(length);
  return value >= 0 && lo <= value;
}

bool RemoteReceiveData::peek_mark_at_most(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t hi = this->upper_bound_(length);
  return value >= 0 && value <= hi;
}

bool RemoteReceiveData::peek_space(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t lo = this->lower_bound_(length);
  const int32_t hi = this->upper_bound_(length);
  return value <= 0 && lo <= -value && -value <= hi;
}

bool RemoteReceiveData::peek_space_at_least(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t lo = this->lower_bound_(length);
  return value <= 0 && lo <= -value;
}

bool RemoteReceiveData::peek_space_at_most(uint32_t length, uint32_t offset) const {
  if (!this->is_valid(offset))
    return false;
  const int32_t value = this->peek(offset);
  const int32_t hi = this->upper_bound_(length);
  return value <= 0 && -value <= hi;
}

bool RemoteReceiveData::expect_mark(uint32_t length) {
  if (!this->peek_mark(length))
    return false;
  this->advance();
  return true;
}

bool RemoteReceiveData::expect_space(uint32_t length) {
  if (!this->peek_space(length))
    return false;
  this->advance();
  return true;
}

bool RemoteReceiveData::expect_item(uint32_t mark, uint32_t space) {
  if (!this->peek_item(mark, space))
    return false;
  this->advance(2);
  return true;
}

bool RemoteReceiveData::expect_pulse_with_gap(uint32_t mark, uint32_t space) {
  if (!this->peek_space_at_least(space, 1) || !this->peek_mark(mark))
    return false;
  this->advance(2);
  return true;
}

/* RemoteReceiverBinarySensorBase */

bool RemoteReceiverBinarySensorBase::on_receive(RemoteReceiveData src) {
  if (!this->matches(src))
    return false;
  this->publish_state(true);
  yield();
  this->publish_state(false);
  return true;
}

/* RemoteReceiverBase */

void RemoteReceiverBase::register_dumper(RemoteReceiverDumperBase *dumper) {
  if (dumper->is_secondary()) {
    this->secondary_dumpers_.push_back(dumper);
  } else {
    this->dumpers_.push_back(dumper);
  }
}

void RemoteReceiverBase::call_listeners_() {
  for (auto *listener : this->listeners_)
    listener->on_receive(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_));
}

void RemoteReceiverBase::call_dumpers_() {
  bool success = false;
  for (auto *dumper : this->dumpers_) {
    if (dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_)))
      success = true;
  }
  if (!success) {
    for (auto *dumper : this->secondary_dumpers_)
      dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_));
  }
}

void RemoteReceiverBinarySensorBase::dump_config() { LOG_BINARY_SENSOR("", "Remote Receiver Binary Sensor", this); }

/* RemoteTransmitData */

void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count) {
  this->data_.clear();
  this->data_.reserve(count);

  while (len > 0) {
    // Parse varint (inline, no dependency on api component)
    uint32_t raw = 0;
    uint32_t shift = 0;
    uint32_t consumed = 0;
    for (; consumed < len && consumed < 5; consumed++) {
      uint8_t byte = data[consumed];
      raw |= (byte & 0x7F) << shift;
      if ((byte & 0x80) == 0) {
        consumed++;
        break;
      }
      shift += 7;
    }
    if (consumed == 0)
      break;  // Parse error

    // Zigzag decode: (n >> 1) ^ -(n & 1)
    int32_t decoded = static_cast<int32_t>((raw >> 1) ^ (~(raw & 1) + 1));
    this->data_.push_back(decoded);
    data += consumed;
    len -= consumed;
  }
}

/* RemoteTransmitterBase */

void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
  const auto &vec = this->temp_.get_data();
  char buffer[256];
  uint32_t buffer_offset = 0;
  buffer_offset += sprintf(buffer, "Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);

  for (size_t i = 0; i < vec.size(); i++) {
    const int32_t value = vec[i];
    const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
    int written;

    if (i + 1 < vec.size()) {
      written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
    } else {
      written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
    }

    if (written < 0 || written >= int(remaining_length)) {
      // write failed, flush...
      buffer[buffer_offset] = '\0';
      ESP_LOGVV(TAG, "%s", buffer);
      buffer_offset = 0;
      written = sprintf(buffer, "  ");
      if (i + 1 < vec.size()) {
        written += sprintf(buffer + written, "%" PRId32 ", ", value);
      } else {
        written += sprintf(buffer + written, "%" PRId32, value);
      }
    }

    buffer_offset += written;
  }
  if (buffer_offset != 0) {
    ESP_LOGVV(TAG, "%s", buffer);
  }
#endif
  this->send_internal(send_times, send_wait);
}
}  // namespace remote_base
}  // namespace esphome
