import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components.ledc.output import LEDCOutput
from esphome.components.gpio.output import GPIOBinaryOutput
from esphome.components.i2c import I2CComponent, I2CDevice
from esphome.core import coroutine
from esphome.core import CORE
from esphome.const import (
    CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_TRIGGER_PIN,
    CONF_SDA, CONF_SCL, CONF_ADDRESS, CONF_PLATFORM
)

CODEOWNERS = ["@mmakaay"]

CONF_RED_ID = "red_id"
CONF_GREEN_ID = "green_id"
CONF_BLUE_ID = "blue_id"
CONF_WHITE_ID = "white_id"
CONF_MASTER1 = "master1"
CONF_MASTER1_ID = "master1_id"
CONF_MASTER2 = "master2"
CONF_MASTER2_ID = "master2_id"
CONF_FP_I2C_ID = "front_panel_i2c_id"
CONF_LIGHT_HAL_ID = "light_hal_id"
CONF_FRONT_PANEL_HAL_ID = "front_panel_hal_id"
CONF_ON_BRIGHTNESS = "on_brightness"
CONF_LEDS = "leds"

AUTO_LOAD = ["ledc", "output", "i2c"]

xiaomi_ns = cg.esphome_ns.namespace("xiaomi")
bslamp2_ns = xiaomi_ns.namespace("bslamp2")
LightHAL = bslamp2_ns.class_("LightHAL", cg.Component)
FrontPanelHAL = bslamp2_ns.class_("FrontPanelHAL", cg.Component, I2CDevice)

FrontPanelLEDs = bslamp2_ns.enum("FrontPanelLEDs")
FRONT_PANEL_LED_OPTIONS = {
    "NONE": FrontPanelLEDs.LED_NONE,
    "ALL": FrontPanelLEDs.LED_ALL,
    "POWER": FrontPanelLEDs.LED_POWER,
    "COLOR": FrontPanelLEDs.LED_COLOR,
    "1": FrontPanelLEDs.LED_1,
    "2": FrontPanelLEDs.LED_2,
    "3": FrontPanelLEDs.LED_3,
    "4": FrontPanelLEDs.LED_4,
    "5": FrontPanelLEDs.LED_5,
    "6": FrontPanelLEDs.LED_6,
    "7": FrontPanelLEDs.LED_7,
    "8": FrontPanelLEDs.LED_8,
    "9": FrontPanelLEDs.LED_9,
    "10": FrontPanelLEDs.LED_10,
}

CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
    # RGBWW Light
    cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL),
    cv.GenerateID(CONF_RED_ID): cv.declare_id(LEDCOutput),
    cv.Optional(CONF_RED, default="GPIO13"): pins.validate_gpio_pin,
    cv.GenerateID(CONF_GREEN_ID): cv.declare_id(LEDCOutput),
    cv.Optional(CONF_GREEN, default="GPIO14"): pins.validate_gpio_pin,
    cv.GenerateID(CONF_BLUE_ID): cv.declare_id(LEDCOutput),
    cv.Optional(CONF_BLUE, default="GPIO5"): pins.validate_gpio_pin,
    cv.GenerateID(CONF_WHITE_ID): cv.declare_id(LEDCOutput),
    cv.Optional(CONF_WHITE, default="GPIO12"): pins.validate_gpio_pin,
    cv.GenerateID(CONF_MASTER1_ID): cv.declare_id(GPIOBinaryOutput),
    cv.Optional(CONF_MASTER1, default="GPIO33"): pins.validate_gpio_pin,
    cv.GenerateID(CONF_MASTER2_ID): cv.declare_id(GPIOBinaryOutput),
    cv.Optional(CONF_MASTER2, default="GPIO4"): pins.validate_gpio_pin,

    # Front panel I2C
    cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.declare_id(FrontPanelHAL),
    cv.GenerateID(CONF_FP_I2C_ID): cv.use_id(I2CComponent),
    cv.Optional(CONF_SDA, default="GPIO21"): pins.validate_gpio_pin,
    cv.Optional(CONF_SCL, default="GPIO19"): pins.validate_gpio_pin,
    cv.Optional(CONF_ADDRESS, default="0x2C"): cv.i2c_address,
    cv.Optional(CONF_TRIGGER_PIN, default="GPIO16"): cv.All(
        pins.validate_gpio_pin,
        pins.validate_has_interrupt
    ),
})

async def make_gpio(number, mode="OUTPUT"):
    return await cg.gpio_pin_expression({ "number": number, "mode": mode });

async def make_gpio_binary_output(id_, number):
    gpio_var = await make_gpio(number)
    output_var = cg.new_Pvariable(id_)
    cg.add(output_var.set_pin(gpio_var))   
    return await cg.register_component(output_var, {})

async def make_ledc_output(id_, number, frequency, channel):
    gpio_var = await make_gpio(number)
    ledc_var = cg.new_Pvariable(id_, gpio_var)
    cg.add(ledc_var.set_frequency(frequency));
    cg.add(ledc_var.set_channel(channel));
    return await cg.register_component(ledc_var, {})

async def make_light_hal(config):
    r_var = await make_ledc_output(config[CONF_RED_ID], config[CONF_RED], 3000, 0)
    g_var = await make_ledc_output(config[CONF_GREEN_ID], config[CONF_GREEN], 3000, 1)
    b_var = await make_ledc_output(config[CONF_BLUE_ID], config[CONF_BLUE], 3000, 2)
    w_var = await make_ledc_output(config[CONF_WHITE_ID], config[CONF_WHITE], 10000, 4)
    m1_var = await make_gpio_binary_output(config[CONF_MASTER1_ID], config[CONF_MASTER1])
    m2_var = await make_gpio_binary_output(config[CONF_MASTER2_ID], config[CONF_MASTER2])
    light_hal = cg.new_Pvariable(config[CONF_LIGHT_HAL_ID])
    await cg.register_component(light_hal, config)
    cg.add(light_hal.set_red_pin(r_var))
    cg.add(light_hal.set_green_pin(g_var))
    cg.add(light_hal.set_blue_pin(b_var))
    cg.add(light_hal.set_white_pin(w_var))
    cg.add(light_hal.set_master1_pin(m1_var))
    cg.add(light_hal.set_master2_pin(m2_var))

async def make_front_panel_hal(config):
    trigger_pin = await make_gpio(config[CONF_TRIGGER_PIN], "INPUT")
    fp_hal = cg.new_Pvariable(config[CONF_FRONT_PANEL_HAL_ID])
    await cg.register_component(fp_hal, config)
    cg.add(fp_hal.set_trigger_pin(trigger_pin))   

    # The i2c component automatically sets up one I2C bus.
    # Take that bus and update is to make it work for the
    # front panel I2C communication.
    fp_i2c_var = await cg.get_variable(config[CONF_FP_I2C_ID])
    cg.add(fp_i2c_var.set_sda_pin(config[CONF_SDA]))
    cg.add(fp_i2c_var.set_scl_pin(config[CONF_SCL]))
    cg.add(fp_i2c_var.set_scan(True))
    cg.add(fp_hal.set_i2c_parent(fp_i2c_var))
    cg.add(fp_hal.set_i2c_address(config[CONF_ADDRESS]))

async def to_code(config):
    # Dirty little hack to make the ESPHome component loader include
    # the code for the "gpio" platform for the "output" domain.
    # Loading specific platform components is not possible using
    # the AUTO_LOAD feature unfortunately.
    CORE.config["output"].append({ CONF_PLATFORM: "gpio" })

    await make_light_hal(config)
    await make_front_panel_hal(config)
