#include "light_json_schema.h"
#include "color_mode.h"
#include "light_output.h"
#include "esphome/core/progmem.h"

#ifdef USE_JSON

namespace esphome::light {

// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema

// Color mode JSON strings - packed into flash with compile-time generated offsets.
// Indexed by ColorModeBitPolicy bit index (1-9), so index 0 maps to bit 1 ("onoff").
PROGMEM_STRING_TABLE(ColorModeStrings, "onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct",
                     "rgbww");

// Get JSON string for color mode. Returns nullptr for UNKNOWN (bit 0).
// Returns ProgmemStr so ArduinoJson knows to handle PROGMEM strings on ESP8266.
static ProgmemStr get_color_mode_json_str(ColorMode mode) {
  unsigned bit = ColorModeBitPolicy::to_bit(mode);
  if (bit == 0)
    return nullptr;
  // bit is 1-9 for valid modes, so bit-1 is always valid (0-8). LAST_INDEX fallback never used.
  return ColorModeStrings::get_progmem_str(bit - 1, ColorModeStrings::LAST_INDEX);
}

void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
  if (state.supports_effects()) {
    root[ESPHOME_F("effect")] = state.get_effect_name().c_str();
    root[ESPHOME_F("effect_index")] = state.get_current_effect_index();
    root[ESPHOME_F("effect_count")] = state.get_effect_count();
  }

  auto values = state.remote_values;

  const auto color_mode = values.get_color_mode();
  const auto *mode_str = get_color_mode_json_str(color_mode);
  if (mode_str != nullptr) {
    root[ESPHOME_F("color_mode")] = mode_str;
  }

  if (color_mode & ColorCapability::ON_OFF)
    root[ESPHOME_F("state")] = (values.get_state() != 0.0f) ? "ON" : "OFF";
  if (color_mode & ColorCapability::BRIGHTNESS)
    root[ESPHOME_F("brightness")] = to_uint8_scale(values.get_brightness());

  JsonObject color = root[ESPHOME_F("color")].to<JsonObject>();
  if (color_mode & ColorCapability::RGB) {
    float color_brightness = values.get_color_brightness();
    color[ESPHOME_F("r")] = to_uint8_scale(color_brightness * values.get_red());
    color[ESPHOME_F("g")] = to_uint8_scale(color_brightness * values.get_green());
    color[ESPHOME_F("b")] = to_uint8_scale(color_brightness * values.get_blue());
  }
  if (color_mode & ColorCapability::WHITE) {
    uint8_t white_val = to_uint8_scale(values.get_white());
    color[ESPHOME_F("w")] = white_val;
    root[ESPHOME_F("white_value")] = white_val;  // legacy API
  }
  if (color_mode & ColorCapability::COLOR_TEMPERATURE) {
    // this one isn't under the color subkey for some reason
    root[ESPHOME_F("color_temp")] = uint32_t(values.get_color_temperature());
  }
  if (color_mode & ColorCapability::COLD_WARM_WHITE) {
    color[ESPHOME_F("c")] = to_uint8_scale(values.get_cold_white());
    color[ESPHOME_F("w")] = to_uint8_scale(values.get_warm_white());
  }
}

void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) {
  if (root[ESPHOME_F("state")].is<const char *>()) {
    auto val = parse_on_off(root[ESPHOME_F("state")]);
    switch (val) {
      case PARSE_ON:
        call.set_state(true);
        break;
      case PARSE_OFF:
        call.set_state(false);
        break;
      case PARSE_TOGGLE:
        call.set_state(!state.remote_values.is_on());
        break;
      case PARSE_NONE:
        break;
    }
  }

  if (root[ESPHOME_F("brightness")].is<uint8_t>()) {
    call.set_brightness(float(root[ESPHOME_F("brightness")]) / 255.0f);
  }

  if (root[ESPHOME_F("color")].is<JsonObject>()) {
    JsonObject color = root[ESPHOME_F("color")];
    // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
    float max_rgb = 0.0f;
    if (color[ESPHOME_F("r")].is<uint8_t>()) {
      float r = float(color[ESPHOME_F("r")]) / 255.0f;
      max_rgb = fmaxf(max_rgb, r);
      call.set_red(r);
    }
    if (color[ESPHOME_F("g")].is<uint8_t>()) {
      float g = float(color[ESPHOME_F("g")]) / 255.0f;
      max_rgb = fmaxf(max_rgb, g);
      call.set_green(g);
    }
    if (color[ESPHOME_F("b")].is<uint8_t>()) {
      float b = float(color[ESPHOME_F("b")]) / 255.0f;
      max_rgb = fmaxf(max_rgb, b);
      call.set_blue(b);
    }
    if (color[ESPHOME_F("r")].is<uint8_t>() || color[ESPHOME_F("g")].is<uint8_t>() ||
        color[ESPHOME_F("b")].is<uint8_t>()) {
      call.set_color_brightness(max_rgb);
    }

    if (color[ESPHOME_F("c")].is<uint8_t>()) {
      call.set_cold_white(float(color[ESPHOME_F("c")]) / 255.0f);
    }
    if (color[ESPHOME_F("w")].is<uint8_t>()) {
      // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm
      // white channel in RGBWW.
      if (color[ESPHOME_F("c")].is<uint8_t>()) {
        call.set_warm_white(float(color[ESPHOME_F("w")]) / 255.0f);
      } else {
        call.set_white(float(color[ESPHOME_F("w")]) / 255.0f);
      }
    }
  }

  if (root[ESPHOME_F("white_value")].is<uint8_t>()) {  // legacy API
    call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f);
  }

  if (root[ESPHOME_F("color_temp")].is<uint16_t>()) {
    call.set_color_temperature(float(root[ESPHOME_F("color_temp")]));
  }
}

void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) {
  LightJSONSchema::parse_color_json(state, call, root);

  if (root[ESPHOME_F("flash")].is<uint32_t>()) {
    auto length = uint32_t(float(root[ESPHOME_F("flash")]) * 1000);
    call.set_flash_length(length);
  }

  if (root[ESPHOME_F("transition")].is<uint16_t>()) {
    auto length = uint32_t(float(root[ESPHOME_F("transition")]) * 1000);
    call.set_transition_length(length);
  }

  if (root[ESPHOME_F("effect")].is<const char *>()) {
    const char *effect = root[ESPHOME_F("effect")];
    call.set_effect(effect, strlen(effect));
  }

  if (root[ESPHOME_F("effect_index")].is<uint32_t>()) {
    uint32_t effect_index = root[ESPHOME_F("effect_index")];
    call.set_effect(effect_index);
  }
}

}  // namespace esphome::light

#endif
