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

#include <cinttypes>

namespace esphome {
namespace remote_base {

static const char *const TAG = "remote_base";

#ifdef USE_ESP32
RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) {
  static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0;
  this->channel_ = next_rmt_channel;
  next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + mem_block_num);
}

RemoteRMTChannel::RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num)
    : channel_(channel), mem_block_num_(mem_block_num) {}

void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) {
  if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) {
    this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_);
    ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_);
  }
  rmt.channel = this->channel_;
  rmt.clk_div = this->clock_divider_;
  rmt.mem_block_num = this->mem_block_num_;
}
#endif

/* 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_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::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_));
}

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

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

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
