import esphome.codegen as cg
from esphome.components import binary_sensor, sensor
import esphome.config_validation as cv
from esphome.const import (
    CONF_BINARY_SENSOR,
    CONF_CHANNELS,
    CONF_GROUP,
    CONF_SUM,
    CONF_TYPE,
    CONF_VALUE,
    ICON_CHECK_CIRCLE_OUTLINE,
)

DEPENDENCIES = ["binary_sensor"]

binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map")
BinarySensorMap = binary_sensor_map_ns.class_(
    "BinarySensorMap", cg.Component, sensor.Sensor
)
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")

CONF_BAYESIAN = "bayesian"
CONF_PRIOR = "prior"
CONF_PROB_GIVEN_TRUE = "prob_given_true"
CONF_PROB_GIVEN_FALSE = "prob_given_false"
CONF_OBSERVATIONS = "observations"

SENSOR_MAP_TYPES = {
    CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
    CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
    CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN,
}

entry_one_parameter = {
    cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
    cv.Required(CONF_VALUE): cv.float_,
}

entry_bayesian_parameters = {
    cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
    cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1),
    cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1),
}

CONFIG_SCHEMA = cv.typed_schema(
    {
        CONF_GROUP: sensor.sensor_schema(
            BinarySensorMap,
            icon=ICON_CHECK_CIRCLE_OUTLINE,
            accuracy_decimals=0,
        ).extend(
            {
                cv.Required(CONF_CHANNELS): cv.All(
                    cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
                ),
            }
        ),
        CONF_SUM: sensor.sensor_schema(
            BinarySensorMap,
            icon=ICON_CHECK_CIRCLE_OUTLINE,
            accuracy_decimals=0,
        ).extend(
            {
                cv.Required(CONF_CHANNELS): cv.All(
                    cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
                ),
            }
        ),
        CONF_BAYESIAN: sensor.sensor_schema(
            BinarySensorMap,
            accuracy_decimals=2,
        ).extend(
            {
                cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1),
                cv.Required(CONF_OBSERVATIONS): cv.All(
                    cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64)
                ),
            }
        ),
    },
    lower=True,
)


async def to_code(config):
    var = await sensor.new_sensor(config)
    await cg.register_component(var, config)

    constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
    cg.add(var.set_sensor_type(constant))

    if config[CONF_TYPE] == CONF_BAYESIAN:
        cg.add(var.set_bayesian_prior(config[CONF_PRIOR]))

        for obs in config[CONF_OBSERVATIONS]:
            input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR])
            cg.add(
                var.add_channel(
                    input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE]
                )
            )
    else:
        for ch in config[CONF_CHANNELS]:
            input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
            cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
