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

namespace esphome {
namespace panasonic_heatpump {
static const char* const TAG = "panasonic_heatpump.climate";

void PanasonicHeatpumpClimate::dump_config() {
  LOG_CLIMATE("", "Panasonic Heatpump Climate", this);
}

climate::ClimateTraits PanasonicHeatpumpClimate::traits() {
  auto traits = climate::ClimateTraits();

  // traits.set_supports_action(true);
  traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
  traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT});
  if (this->cool_mode_ &&
      (this->id_ == ClimateIds::CONF_CLIMATE_ZONE1 || this->id_ == ClimateIds::CONF_CLIMATE_ZONE2)) {
    traits.add_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);
    traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
    traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
  }

  traits.set_visual_min_temperature(this->min_temperature_);
  traits.set_visual_max_temperature(this->max_temperature_);
  traits.set_visual_temperature_step(this->temperature_step_);

  return traits;
}

void PanasonicHeatpumpClimate::control(const climate::ClimateCall& call) {
  if (call.get_mode().has_value()) {
    int byte6 = this->parent_->get_response_byte(6);
    if (byte6 >= 0) {
      climate::ClimateMode new_mode = *call.get_mode();
      uint8_t newByte6 = this->setClimateMode(new_mode, (uint8_t)byte6);
      this->parent_->set_command_byte(newByte6, 6);
    }
  }

  if (call.get_target_temperature().has_value()) {
    float new_temp = *call.get_target_temperature();
    int new_temp_int = static_cast<int>(round(new_temp));
    switch (this->id_) {
    case ClimateIds::CONF_CLIMATE_TANK:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 42);  // set11
      break;
    case ClimateIds::CONF_CLIMATE_ZONE1:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 38);  // set5
      break;
    case ClimateIds::CONF_CLIMATE_ZONE2:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 40);  // set7
      break;
    };
  }

  if (call.get_target_temperature_high().has_value()) {
    float new_temp = *call.get_target_temperature_high();
    int new_temp_int = static_cast<int>(round(new_temp));
    switch (this->id_) {
    case ClimateIds::CONF_CLIMATE_ZONE1:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 38);  // set5
      break;
    case ClimateIds::CONF_CLIMATE_ZONE2:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 40);  // set7
      break;
    };
  }

  if (call.get_target_temperature_low().has_value()) {
    float new_temp = *call.get_target_temperature_low();
    int new_temp_int = static_cast<int>(round(new_temp));
    switch (this->id_) {
    case ClimateIds::CONF_CLIMATE_ZONE1:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 39);  // set6
      break;
    case ClimateIds::CONF_CLIMATE_ZONE2:
      this->parent_->set_command_byte(PanasonicCommand::setPlus128(new_temp_int), 41);  // set8
      break;
    };
  }

  this->publish_state();
  this->keep_state_ = KEEP_STATE;
}

void PanasonicHeatpumpClimate::publish_new_state(const std::vector<uint8_t>& data) {
  if (this->keep_state_ > 0) {
    this->keep_state_--;
    return;
  }
  if (data.empty())
    return;

  uint8_t new_mode;
  float new_target_temp_heat;
  float new_target_temp_cool;
  float new_current_temp;

  new_mode = this->getClimateMode(data[6]);  // set9
  switch (this->id_) {
  case ClimateIds::CONF_CLIMATE_TANK:
    new_target_temp_heat = PanasonicDecode::getByteMinus128(data[42]);  // set11
    new_current_temp = PanasonicDecode::getByteMinus128(data[141]);     // top10
    break;
  case ClimateIds::CONF_CLIMATE_ZONE1:
    new_target_temp_heat = PanasonicDecode::getByteMinus128(data[38]);  // set5
    new_target_temp_cool = PanasonicDecode::getByteMinus128(data[39]);  // set6
    new_current_temp = PanasonicDecode::getByteMinus128(data[139]);     // top56
    break;
  case ClimateIds::CONF_CLIMATE_ZONE2:
    new_target_temp_heat = PanasonicDecode::getByteMinus128(data[40]);  // set7
    new_target_temp_cool = PanasonicDecode::getByteMinus128(data[41]);  // set8
    new_current_temp = PanasonicDecode::getByteMinus128(data[140]);     // top57
    break;
  default:
    return;
  };

  if (!this->get_traits().has_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE) &&
      this->mode == new_mode && this->target_temperature == new_target_temp_heat &&
      this->current_temperature == new_current_temp)
    return;
  if (this->get_traits().has_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE) &&
      this->mode == new_mode && this->target_temperature_high == new_target_temp_heat &&
      this->target_temperature_low == new_target_temp_cool && this->current_temperature == new_current_temp)
    return;

  if (new_mode != 0xFF)
    this->mode = (climate::ClimateMode)new_mode;
  if (this->get_traits().has_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
    this->target_temperature_high = new_target_temp_heat;
    this->target_temperature_low = new_target_temp_cool;
  } else {
    this->target_temperature = new_target_temp_heat;
  }
  this->current_temperature = new_current_temp;
  this->publish_state();
}

uint8_t PanasonicHeatpumpClimate::getClimateMode(const uint8_t input) {
  switch (this->id_) {
  case ClimateIds::CONF_CLIMATE_TANK:
    switch ((uint8_t)(input & 0b110000)) {
    case 0b010000:
      return climate::CLIMATE_MODE_OFF;
    case 0b100000:
      return climate::CLIMATE_MODE_HEAT;
    default:
      return 255;
    };
  case ClimateIds::CONF_CLIMATE_ZONE1:
  case ClimateIds::CONF_CLIMATE_ZONE2:
    switch ((uint8_t)(input & 0b1111)) {
    case 0b0001:
      return climate::CLIMATE_MODE_OFF;
    case 0b0010:
      return climate::CLIMATE_MODE_HEAT;
    case 0b0011:
      return climate::CLIMATE_MODE_COOL;
    case 0b1000:
      return climate::CLIMATE_MODE_AUTO;
    case 0b1001:
      return climate::CLIMATE_MODE_AUTO;  // 0x19 = auto-heat
    case 0b1010:
      return climate::CLIMATE_MODE_AUTO;  // 0x1A = auto-cool
    default:
      return 0xFF;
    };
  default:
    return 0xFF;
  };
}

uint8_t PanasonicHeatpumpClimate::setClimateMode(const climate::ClimateMode mode, uint8_t byte) {
  uint8_t newByte = byte;
  switch (this->id_) {
  case ClimateIds::CONF_CLIMATE_TANK:
    newByte = newByte & 0b11001111;
    switch (mode) {
    case climate::CLIMATE_MODE_OFF:
      return newByte + 0b010000;
    case climate::CLIMATE_MODE_HEAT:
      return newByte + 0b100000;
    default:
      return 0;
    };
  case ClimateIds::CONF_CLIMATE_ZONE1:
  case ClimateIds::CONF_CLIMATE_ZONE2:
    newByte = newByte & 0b11110000;
    switch (mode) {
    case climate::CLIMATE_MODE_OFF:
      return newByte + 0b0001;
    case climate::CLIMATE_MODE_HEAT:
      return newByte + 0b0010;
    case climate::CLIMATE_MODE_COOL:
      return newByte + 0b0011;
    case climate::CLIMATE_MODE_AUTO:
      return newByte + 0b1000;
    default:
      return 0;
    };
  default:
    return 0;
  };
}
}  // namespace panasonic_heatpump
}  // namespace esphome
