#include "midea_ir.h"
#include "midea_data.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/components/coolix/coolix.h"

namespace esphome {
namespace midea_ir {

static const char *const TAG = "midea_ir.climate";

void ControlData::set_temp(float temp) {
  uint8_t min;
  if (this->get_fahrenheit()) {
    min = MIDEA_TEMPF_MIN;
    temp = esphome::clamp<float>(celsius_to_fahrenheit(temp), MIDEA_TEMPF_MIN, MIDEA_TEMPF_MAX);
  } else {
    min = MIDEA_TEMPC_MIN;
    temp = esphome::clamp<float>(temp, MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX);
  }
  this->set_value_(2, lroundf(temp) - min, 31);
}

float ControlData::get_temp() const {
  const uint8_t temp = this->get_value_(2, 31);
  if (this->get_fahrenheit())
    return fahrenheit_to_celsius(static_cast<float>(temp + MIDEA_TEMPF_MIN));
  return static_cast<float>(temp + MIDEA_TEMPC_MIN);
}

void ControlData::fix() {
  // In FAN_AUTO, modes COOL, HEAT and FAN_ONLY bit #5 in byte #1 must be set
  const uint8_t value = this->get_value_(1, 31);
  if (value == 0 || value == 3 || value == 4)
    this->set_mask_(1, true, 32);
  // In FAN_ONLY mode we need to set all temperature bits
  if (this->get_mode_() == MODE_FAN_ONLY)
    this->set_mask_(2, true, 31);
}

void ControlData::set_mode(ClimateMode mode) {
  switch (mode) {
    case ClimateMode::CLIMATE_MODE_OFF:
      this->set_power_(false);
      return;
    case ClimateMode::CLIMATE_MODE_COOL:
      this->set_mode_(MODE_COOL);
      break;
    case ClimateMode::CLIMATE_MODE_DRY:
      this->set_mode_(MODE_DRY);
      break;
    case ClimateMode::CLIMATE_MODE_FAN_ONLY:
      this->set_mode_(MODE_FAN_ONLY);
      break;
    case ClimateMode::CLIMATE_MODE_HEAT:
      this->set_mode_(MODE_HEAT);
      break;
    default:
      this->set_mode_(MODE_AUTO);
      break;
  }
  this->set_power_(true);
}

ClimateMode ControlData::get_mode() const {
  if (!this->get_power_())
    return ClimateMode::CLIMATE_MODE_OFF;
  switch (this->get_mode_()) {
    case MODE_COOL:
      return ClimateMode::CLIMATE_MODE_COOL;
    case MODE_DRY:
      return ClimateMode::CLIMATE_MODE_DRY;
    case MODE_FAN_ONLY:
      return ClimateMode::CLIMATE_MODE_FAN_ONLY;
    case MODE_HEAT:
      return ClimateMode::CLIMATE_MODE_HEAT;
    default:
      return ClimateMode::CLIMATE_MODE_HEAT_COOL;
  }
}

void ControlData::set_fan_mode(ClimateFanMode mode) {
  switch (mode) {
    case ClimateFanMode::CLIMATE_FAN_LOW:
      this->set_fan_mode_(FAN_LOW);
      break;
    case ClimateFanMode::CLIMATE_FAN_MEDIUM:
      this->set_fan_mode_(FAN_MEDIUM);
      break;
    case ClimateFanMode::CLIMATE_FAN_HIGH:
      this->set_fan_mode_(FAN_HIGH);
      break;
    default:
      this->set_fan_mode_(FAN_AUTO);
      break;
  }
}

ClimateFanMode ControlData::get_fan_mode() const {
  switch (this->get_fan_mode_()) {
    case FAN_LOW:
      return ClimateFanMode::CLIMATE_FAN_LOW;
    case FAN_MEDIUM:
      return ClimateFanMode::CLIMATE_FAN_MEDIUM;
    case FAN_HIGH:
      return ClimateFanMode::CLIMATE_FAN_HIGH;
    default:
      return ClimateFanMode::CLIMATE_FAN_AUTO;
  }
}

void MideaIR::control(const climate::ClimateCall &call) {
  // swing and preset resets after unit powered off
  if (call.get_mode() == climate::CLIMATE_MODE_OFF) {
    this->swing_mode = climate::CLIMATE_SWING_OFF;
    this->preset = climate::CLIMATE_PRESET_NONE;
  } else {
    auto swing = call.get_swing_mode();
    if (swing.has_value() &&
        ((*swing == climate::CLIMATE_SWING_OFF && this->swing_mode == climate::CLIMATE_SWING_VERTICAL) ||
         (*swing == climate::CLIMATE_SWING_VERTICAL && this->swing_mode == climate::CLIMATE_SWING_OFF))) {
      this->swing_ = true;
    } else {
      auto preset = call.get_preset();
      if (preset.has_value() &&
          ((*preset == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) ||
           (*preset == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) {
        this->boost_ = true;
      }
    }
  }
  climate_ir::ClimateIR::control(call);
}

void MideaIR::transmit_(MideaData &data) {
  data.finalize();
  auto transmit = this->transmitter_->transmit();
  remote_base::MideaProtocol().encode(transmit.get_data(), data);
  transmit.perform();
}

void MideaIR::transmit_state() {
  if (this->swing_) {
    SpecialData data(SpecialData::VSWING_TOGGLE);
    this->transmit_(data);
    this->swing_ = false;
    return;
  }
  if (this->boost_) {
    SpecialData data(SpecialData::TURBO_TOGGLE);
    this->transmit_(data);
    this->boost_ = false;
    return;
  }
  ControlData data;
  data.set_fahrenheit(this->fahrenheit_);
  data.set_temp(this->target_temperature);
  data.set_mode(this->mode);
  data.set_fan_mode(this->fan_mode.value_or(ClimateFanMode::CLIMATE_FAN_AUTO));
  data.set_sleep_preset(this->preset == climate::CLIMATE_PRESET_SLEEP);
  data.fix();
  this->transmit_(data);
}

bool MideaIR::on_receive(remote_base::RemoteReceiveData data) {
  auto midea = remote_base::MideaProtocol().decode(data);
  if (midea.has_value())
    return this->on_midea_(*midea);
  return coolix::CoolixClimate::on_coolix(this, data);
}

bool MideaIR::on_midea_(const MideaData &data) {
  char buf[MideaData::TO_STR_BUFFER_SIZE];
  ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_str(buf));
  if (data.type() == MideaData::MIDEA_TYPE_CONTROL) {
    const ControlData status = data;
    if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY)
      this->target_temperature = status.get_temp();
    this->mode = status.get_mode();
    this->fan_mode = status.get_fan_mode();
    if (status.get_sleep_preset()) {
      this->preset = climate::CLIMATE_PRESET_SLEEP;
    } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) {
      this->preset = climate::CLIMATE_PRESET_NONE;
    }
    this->publish_state();
    return true;
  }
  if (data.type() == MideaData::MIDEA_TYPE_SPECIAL) {
    switch (data[1]) {
      case SpecialData::VSWING_TOGGLE:
        this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? climate::CLIMATE_SWING_OFF
                                                                               : climate::CLIMATE_SWING_VERTICAL;
        break;
      case SpecialData::TURBO_TOGGLE:
        this->preset = this->preset == climate::CLIMATE_PRESET_BOOST ? climate::CLIMATE_PRESET_NONE
                                                                     : climate::CLIMATE_PRESET_BOOST;
        break;
    }
    this->publish_state();
    return true;
  }

  return false;
}

}  // namespace midea_ir
}  // namespace esphome
