"""Support for Fronius devices."""
import copy
import logging
import voluptuous as vol

from pyfronius import Fronius

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_RESOURCE, CONF_SENSOR_TYPE, CONF_DEVICE,
                                 CONF_MONITORED_CONDITIONS)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(__name__)

CONF_SCOPE = 'scope'

TYPE_INVERTER = 'inverter'
TYPE_STORAGE = 'storage'
TYPE_METER = 'meter'
TYPE_POWER_FLOW = 'power_flow'
SCOPE_DEVICE = 'device'
SCOPE_SYSTEM = 'system'

DEFAULT_SCOPE = SCOPE_DEVICE
DEFAULT_DEVICE = 0
DEFAULT_INVERTER = 1

SENSOR_TYPES = [TYPE_INVERTER, TYPE_STORAGE, TYPE_METER, TYPE_POWER_FLOW]
SCOPE_TYPES = [SCOPE_DEVICE, SCOPE_SYSTEM]


def _device_id_validator(config):
    """Ensure that inverters have default id 1 and other devices 0."""
    config = copy.deepcopy(config)
    for cond in config[CONF_MONITORED_CONDITIONS]:
        if CONF_DEVICE not in cond:
            if cond[CONF_SENSOR_TYPE] == TYPE_INVERTER:
                cond[CONF_DEVICE] = DEFAULT_INVERTER
            else:
                cond[CONF_DEVICE] = DEFAULT_DEVICE
    return config


PLATFORM_SCHEMA = vol.Schema(vol.All(PLATFORM_SCHEMA.extend({
    vol.Required(CONF_RESOURCE):
        cv.url,
    vol.Required(CONF_MONITORED_CONDITIONS):
        vol.All(
            cv.ensure_list,
            [{
                vol.Required(CONF_SENSOR_TYPE): vol.In(SENSOR_TYPES),
                vol.Optional(CONF_SCOPE, default=DEFAULT_SCOPE):
                    vol.In(SCOPE_TYPES),
                vol.Optional(CONF_DEVICE):
                    vol.All(vol.Coerce(int), vol.Range(min=0))
            }]
        )
}), _device_id_validator))


async def async_setup_platform(hass,
                               config,
                               async_add_entities,
                               discovery_info=None):
    """Set up of Fronius platform."""
    session = async_get_clientsession(hass)
    fronius = Fronius(session, config[CONF_RESOURCE])

    sensors = []
    for condition in config[CONF_MONITORED_CONDITIONS]:

        device = condition[CONF_DEVICE]
        name = "Fronius {} {} {}".format(
            condition[CONF_SENSOR_TYPE].replace('_', ' ').capitalize(),
            device,
            config[CONF_RESOURCE],
        )
        sensor_type = condition[CONF_SENSOR_TYPE]
        scope = condition[CONF_SCOPE]
        if sensor_type == TYPE_INVERTER:
            if scope == SCOPE_SYSTEM:
                sensor_cls = FroniusInverterSystem
            else:
                sensor_cls = FroniusInverterDevice
        elif sensor_type == TYPE_METER:
            if scope == SCOPE_SYSTEM:
                sensor_cls = FroniusMeterSystem
            else:
                sensor_cls = FroniusMeterDevice
        elif sensor_type == TYPE_POWER_FLOW:
            sensor_cls = FroniusPowerFlow
        else:
            sensor_cls = FroniusStorage

        sensors.append(sensor_cls(fronius, name, device))

    async_add_entities(sensors, True)


class FroniusSensor(Entity):
    """The Fronius sensor implementation."""

    def __init__(self, data, name, device):
        """Initialize the sensor."""
        self.data = data
        self._name = name
        self._device = device
        self._state = None
        self._attributes = {}

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the current state."""
        return self._state

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        return self._attributes

    async def async_update(self):
        """Retrieve and update latest state."""
        values = {}
        try:
            values = await self._update()
        except ConnectionError:
            _LOGGER.error("Failed to update: connection error")
        except ValueError:
            _LOGGER.error("Failed to update: invalid response returned."
                          "Maybe the configured device is not supported")

        if values:
            self._state = values['status']['Code']
            attributes = {}
            for key in values:
                if 'value' in values[key]:
                    attributes[key] = values[key].get('value', 0)
            self._attributes = attributes

    async def _update(self):
        """Return values of interest."""
        pass


class FroniusInverterSystem(FroniusSensor):
    """Sensor for the fronius inverter with system scope."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_system_inverter_data()


class FroniusInverterDevice(FroniusSensor):
    """Sensor for the fronius inverter with device scope."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_inverter_data(self._device)


class FroniusStorage(FroniusSensor):
    """Sensor for the fronius battery storage."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_storage_data(self._device)


class FroniusMeterSystem(FroniusSensor):
    """Sensor for the fronius meter with system scope."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_system_meter_data()


class FroniusMeterDevice(FroniusSensor):
    """Sensor for the fronius meter with device scope."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_meter_data(self._device)


class FroniusPowerFlow(FroniusSensor):
    """Sensor for the fronius power flow."""

    async def _update(self):
        """Get the values for the current state."""
        return await self.data.current_power_flow()
