"""This component provides support for RainMachine programs and zones."""
from datetime import datetime
import logging

from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ATTR_ID
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
    async_dispatcher_connect, async_dispatcher_send)

from . import (
    DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC,
    ZONE_UPDATE_TOPIC, RainMachineEntity)

_LOGGER = logging.getLogger(__name__)

ATTR_NEXT_RUN = 'next_run'
ATTR_AREA = 'area'
ATTR_CS_ON = 'cs_on'
ATTR_CURRENT_CYCLE = 'current_cycle'
ATTR_CYCLES = 'cycles'
ATTR_DELAY = 'delay'
ATTR_DELAY_ON = 'delay_on'
ATTR_FIELD_CAPACITY = 'field_capacity'
ATTR_NO_CYCLES = 'number_of_cycles'
ATTR_PRECIP_RATE = 'sprinkler_head_precipitation_rate'
ATTR_RESTRICTIONS = 'restrictions'
ATTR_SLOPE = 'slope'
ATTR_SOAK = 'soak'
ATTR_SOIL_TYPE = 'soil_type'
ATTR_SPRINKLER_TYPE = 'sprinkler_head_type'
ATTR_STATUS = 'status'
ATTR_SUN_EXPOSURE = 'sun_exposure'
ATTR_VEGETATION_TYPE = 'vegetation_type'
ATTR_ZONES = 'zones'

DAYS = [
    'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
    'Sunday'
]

PROGRAM_STATUS_MAP = {0: 'Not Running', 1: 'Running', 2: 'Queued'}

SOIL_TYPE_MAP = {
    0: 'Not Set',
    1: 'Clay Loam',
    2: 'Silty Clay',
    3: 'Clay',
    4: 'Loam',
    5: 'Sandy Loam',
    6: 'Loamy Sand',
    7: 'Sand',
    8: 'Sandy Clay',
    9: 'Silt Loam',
    10: 'Silt',
    99: 'Other'
}

SLOPE_TYPE_MAP = {
    0: 'Not Set',
    1: 'Flat',
    2: 'Moderate',
    3: 'High',
    4: 'Very High',
    99: 'Other'
}

SPRINKLER_TYPE_MAP = {
    0: 'Not Set',
    1: 'Popup Spray',
    2: 'Rotors',
    3: 'Surface Drip',
    4: 'Bubblers Drip',
    99: 'Other'
}

SUN_EXPOSURE_MAP = {
    0: 'Not Set',
    1: 'Full Sun',
    2: 'Partial Shade',
    3: 'Full Shade'
}

VEGETATION_MAP = {
    0: 'Not Set',
    2: 'Cool Season Grass',
    3: 'Fruit Trees',
    4: 'Flowers',
    5: 'Vegetables',
    6: 'Citrus',
    7: 'Trees and Bushes',
    9: 'Drought Tolerant Plants',
    10: 'Warm Season Grass',
    99: 'Other'
}


async def async_setup_platform(
        hass, config, async_add_entities, discovery_info=None):
    """Set up RainMachine switches sensor based on the old way."""
    pass


async def async_setup_entry(hass, entry, async_add_entities):
    """Set up RainMachine switches based on a config entry."""
    rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]

    entities = []

    programs = await rainmachine.client.programs.all(include_inactive=True)
    for program in programs:
        entities.append(RainMachineProgram(rainmachine, program))

    zones = await rainmachine.client.zones.all(include_inactive=True)
    for zone in zones:
        entities.append(
            RainMachineZone(
                rainmachine, zone, rainmachine.default_zone_runtime))

    async_add_entities(entities, True)


class RainMachineSwitch(RainMachineEntity, SwitchDevice):
    """A class to represent a generic RainMachine switch."""

    def __init__(self, rainmachine, switch_type, obj):
        """Initialize a generic RainMachine switch."""
        super().__init__(rainmachine)

        self._name = obj['name']
        self._obj = obj
        self._rainmachine_entity_id = obj['uid']
        self._switch_type = switch_type

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return bool(self._obj.get('active'))

    @property
    def icon(self) -> str:
        """Return the icon."""
        return 'mdi:water'

    @property
    def unique_id(self) -> str:
        """Return a unique, HASS-friendly identifier for this entity."""
        return '{0}_{1}_{2}'.format(
            self.rainmachine.device_mac.replace(':', ''), self._switch_type,
            self._rainmachine_entity_id)

    @callback
    def _program_updated(self):
        """Update state, trigger updates."""
        self.async_schedule_update_ha_state(True)


class RainMachineProgram(RainMachineSwitch):
    """A RainMachine program."""

    def __init__(self, rainmachine, obj):
        """Initialize a generic RainMachine switch."""
        super().__init__(rainmachine, 'program', obj)

    @property
    def is_on(self) -> bool:
        """Return whether the program is running."""
        return bool(self._obj.get('status'))

    @property
    def zones(self) -> list:
        """Return a list of active zones associated with this program."""
        return [z for z in self._obj['wateringTimes'] if z['active']]

    async def async_added_to_hass(self):
        """Register callbacks."""
        self._dispatcher_handlers.append(async_dispatcher_connect(
            self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated))

    async def async_turn_off(self, **kwargs) -> None:
        """Turn the program off."""
        from regenmaschine.errors import RequestError

        try:
            await self.rainmachine.client.programs.stop(
                self._rainmachine_entity_id)
            async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
        except RequestError as err:
            _LOGGER.error(
                'Unable to turn off program "%s": %s', self.unique_id,
                str(err))

    async def async_turn_on(self, **kwargs) -> None:
        """Turn the program on."""
        from regenmaschine.errors import RequestError

        try:
            await self.rainmachine.client.programs.start(
                self._rainmachine_entity_id)
            async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
        except RequestError as err:
            _LOGGER.error(
                'Unable to turn on program "%s": %s', self.unique_id, str(err))

    async def async_update(self) -> None:
        """Update info for the program."""
        from regenmaschine.errors import RequestError

        try:
            self._obj = await self.rainmachine.client.programs.get(
                self._rainmachine_entity_id)

            try:
                next_run = datetime.strptime(
                    '{0} {1}'.format(
                        self._obj['nextRun'], self._obj['startTime']),
                    '%Y-%m-%d %H:%M').isoformat()
            except ValueError:
                next_run = None

            self._attrs.update({
                ATTR_ID: self._obj['uid'],
                ATTR_NEXT_RUN: next_run,
                ATTR_SOAK: self._obj.get('soak'),
                ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')],
                ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
            })
        except RequestError as err:
            _LOGGER.error(
                'Unable to update info for program "%s": %s', self.unique_id,
                str(err))


class RainMachineZone(RainMachineSwitch):
    """A RainMachine zone."""

    def __init__(self, rainmachine, obj, zone_run_time):
        """Initialize a RainMachine zone."""
        super().__init__(rainmachine, 'zone', obj)

        self._properties_json = {}
        self._run_time = zone_run_time

    @property
    def is_on(self) -> bool:
        """Return whether the zone is running."""
        return bool(self._obj.get('state'))

    async def async_added_to_hass(self):
        """Register callbacks."""
        self._dispatcher_handlers.append(async_dispatcher_connect(
            self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated))
        self._dispatcher_handlers.append(async_dispatcher_connect(
            self.hass, ZONE_UPDATE_TOPIC, self._program_updated))

    async def async_turn_off(self, **kwargs) -> None:
        """Turn the zone off."""
        from regenmaschine.errors import RequestError

        try:
            await self.rainmachine.client.zones.stop(
                self._rainmachine_entity_id)
        except RequestError as err:
            _LOGGER.error(
                'Unable to turn off zone "%s": %s', self.unique_id, str(err))

    async def async_turn_on(self, **kwargs) -> None:
        """Turn the zone on."""
        from regenmaschine.errors import RequestError

        try:
            await self.rainmachine.client.zones.start(
                self._rainmachine_entity_id, self._run_time)
        except RequestError as err:
            _LOGGER.error(
                'Unable to turn on zone "%s": %s', self.unique_id, str(err))

    async def async_update(self) -> None:
        """Update info for the zone."""
        from regenmaschine.errors import RequestError

        try:
            self._obj = await self.rainmachine.client.zones.get(
                self._rainmachine_entity_id)

            self._properties_json = await self.rainmachine.client.zones.get(
                self._rainmachine_entity_id, details=True)

            self._attrs.update({
                ATTR_ID:
                    self._obj['uid'],
                ATTR_AREA:
                    self._properties_json.get('waterSense').get('area'),
                ATTR_CURRENT_CYCLE:
                    self._obj.get('cycle'),
                ATTR_FIELD_CAPACITY:
                    self._properties_json.get('waterSense').get(
                        'fieldCapacity'),
                ATTR_NO_CYCLES:
                    self._obj.get('noOfCycles'),
                ATTR_PRECIP_RATE:
                    self._properties_json.get('waterSense').get(
                        'precipitationRate'),
                ATTR_RESTRICTIONS:
                    self._obj.get('restriction'),
                ATTR_SLOPE:
                    SLOPE_TYPE_MAP.get(self._properties_json.get('slope')),
                ATTR_SOIL_TYPE:
                    SOIL_TYPE_MAP.get(self._properties_json.get('sun')),
                ATTR_SPRINKLER_TYPE:
                    SPRINKLER_TYPE_MAP.get(
                        self._properties_json.get('group_id')),
                ATTR_SUN_EXPOSURE:
                    SUN_EXPOSURE_MAP.get(self._properties_json.get('sun')),
                ATTR_VEGETATION_TYPE:
                    VEGETATION_MAP.get(self._obj.get('type')),
            })
        except RequestError as err:
            _LOGGER.error(
                'Unable to update info for zone "%s": %s', self.unique_id,
                str(err))
