"""
Integration with the Rachio Iro sprinkler system controller.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.rachio/
"""
from datetime import timedelta
import logging

import voluptuous as vol

from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
from homeassistant.const import CONF_ACCESS_TOKEN
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util

REQUIREMENTS = ['rachiopy==0.1.2']

_LOGGER = logging.getLogger(__name__)

CONF_MANUAL_RUN_MINS = 'manual_run_mins'

DATA_RACHIO = 'rachio'

DEFAULT_MANUAL_RUN_MINS = 10

MIN_UPDATE_INTERVAL = timedelta(seconds=30)
MIN_FORCED_UPDATE_INTERVAL = timedelta(seconds=1)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_ACCESS_TOKEN): cv.string,
    vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS):
        cv.positive_int,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Rachio switches."""
    from rachiopy import Rachio

    # Get options
    manual_run_mins = config.get(CONF_MANUAL_RUN_MINS)
    _LOGGER.debug("Rachio run time is %d min", manual_run_mins)

    access_token = config.get(CONF_ACCESS_TOKEN)

    # Configure API
    _LOGGER.debug("Configuring Rachio API")
    rachio = Rachio(access_token)

    person = None
    try:
        person = _get_person(rachio)
    except KeyError:
        _LOGGER.error(
            "Could not reach the Rachio API. Is your access token valid?")
        return

    # Get and persist devices
    devices = _list_devices(rachio, manual_run_mins)
    if not devices:
        _LOGGER.error(
            "No Rachio devices found in account %s", person['username'])
        return

    hass.data[DATA_RACHIO] = devices[0]

    if len(devices) > 1:
        _LOGGER.warning("Multiple Rachio devices found in account, "
                        "using %s", hass.data[DATA_RACHIO].device_id)
    else:
        _LOGGER.debug("Found Rachio device")

    hass.data[DATA_RACHIO].update()
    add_devices(hass.data[DATA_RACHIO].list_zones())


def _get_person(rachio):
    """Pull the account info of the person whose access token was provided."""
    person_id = rachio.person.getInfo()[1]['id']
    return rachio.person.get(person_id)[1]


def _list_devices(rachio, manual_run_mins):
    """Pull a list of devices on the account."""
    return [RachioIro(rachio, d['id'], manual_run_mins)
            for d in _get_person(rachio)['devices']]


class RachioIro(object):
    """Representation of a Rachio Iro."""

    def __init__(self, rachio, device_id, manual_run_mins):
        """Initialize a Rachio device."""
        self.rachio = rachio
        self._device_id = device_id
        self.manual_run_mins = manual_run_mins
        self._device = None
        self._running = None
        self._zones = None

    def __str__(self):
        """Display the device as a string."""
        return "Rachio Iro {}".format(self.serial_number)

    @property
    def device_id(self):
        """Return the Rachio API device ID."""
        return self._device['id']

    @property
    def status(self):
        """Return the current status of the device."""
        return self._device['status']

    @property
    def serial_number(self):
        """Return the serial number of the device."""
        return self._device['serialNumber']

    @property
    def is_paused(self):
        """Return whether the device is temporarily disabled."""
        return self._device['paused']

    @property
    def is_on(self):
        """Return whether the device is powered on and connected."""
        return self._device['on']

    @property
    def current_schedule(self):
        """Return the schedule that the device is running right now."""
        return self._running

    def list_zones(self, include_disabled=False):
        """Return a list of the zones connected to the device, incl. data."""
        if not self._zones:
            self._zones = [RachioZone(self.rachio, self, zone['id'],
                                      self.manual_run_mins)
                           for zone in self._device['zones']]

        if include_disabled:
            return self._zones

        self.update(no_throttle=True)
        return [z for z in self._zones if z.is_enabled]

    @util.Throttle(MIN_UPDATE_INTERVAL, MIN_FORCED_UPDATE_INTERVAL)
    def update(self, **kwargs):
        """Pull updated device info from the Rachio API."""
        self._device = self.rachio.device.get(self._device_id)[1]
        self._running = self.rachio.device\
                            .getCurrentSchedule(self._device_id)[1]

        # Possibly update all zones
        for zone in self.list_zones(include_disabled=True):
            zone.update()

        _LOGGER.debug("Updated %s", str(self))


class RachioZone(SwitchDevice):
    """Representation of one zone of sprinklers connected to the Rachio Iro."""

    def __init__(self, rachio, device, zone_id, manual_run_mins):
        """Initialize a new Rachio Zone."""
        self.rachio = rachio
        self._device = device
        self._zone_id = zone_id
        self._zone = None
        self._manual_run_secs = manual_run_mins * 60

    def __str__(self):
        """Display the zone as a string."""
        return "Rachio Zone {}".format(self.name)

    @property
    def zone_id(self):
        """How the Rachio API refers to the zone."""
        return self._zone['id']

    @property
    def unique_id(self):
        """Return the unique string ID for the zone."""
        return '{iro}-{zone}'.format(
            iro=self._device.device_id, zone=self.zone_id)

    @property
    def number(self):
        """Return the physical connection of the zone pump."""
        return self._zone['zoneNumber']

    @property
    def name(self):
        """Return the friendly name of the zone."""
        return self._zone['name']

    @property
    def is_enabled(self):
        """Return whether the zone is allowed to run."""
        return self._zone['enabled']

    @property
    def is_on(self):
        """Return whether the zone is currently running."""
        schedule = self._device.current_schedule
        return self.zone_id == schedule.get('zoneId')

    def update(self):
        """Pull updated zone info from the Rachio API."""
        self._zone = self.rachio.zone.get(self._zone_id)[1]

        # Possibly update device
        self._device.update()

        _LOGGER.debug("Updated %s", str(self))

    def turn_on(self):
        """Start the zone."""
        # Stop other zones first
        self.turn_off()

        _LOGGER.info("Watering %s for %d s", self.name, self._manual_run_secs)
        self.rachio.zone.start(self.zone_id, self._manual_run_secs)

    def turn_off(self):
        """Stop all zones."""
        _LOGGER.info("Stopping watering of all zones")
        self.rachio.device.stopWater(self._device.device_id)
