"""Support for retrieving meteorological data from Dark Sky."""
from datetime import datetime, timedelta
import logging

from requests.exceptions import (
    ConnectionError as ConnectError, HTTPError, Timeout)
import voluptuous as vol

from homeassistant.components.weather import (
    ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP,
    ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING,
    ATTR_FORECAST_WIND_SPEED, PLATFORM_SCHEMA, WeatherEntity)
from homeassistant.const import (
    CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, CONF_NAME,
    PRESSURE_HPA, PRESSURE_INHG, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.util.pressure import convert as convert_pressure
_LOGGER = logging.getLogger(__name__)

ATTRIBUTION = "Powered by Dark Sky"

FORECAST_MODE = ['hourly', 'daily']

MAP_CONDITION = {
    'clear-day': 'sunny',
    'clear-night': 'clear-night',
    'rain': 'rainy',
    'snow': 'snowy',
    'sleet': 'snowy-rainy',
    'wind': 'windy',
    'fog': 'fog',
    'cloudy': 'cloudy',
    'partly-cloudy-day': 'partlycloudy',
    'partly-cloudy-night': 'partlycloudy',
    'hail': 'hail',
    'thunderstorm': 'lightning',
    'tornado': None,
}

CONF_UNITS = 'units'

DEFAULT_NAME = 'Dark Sky'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_API_KEY): cv.string,
    vol.Optional(CONF_LATITUDE): cv.latitude,
    vol.Optional(CONF_LONGITUDE): cv.longitude,
    vol.Optional(CONF_MODE, default='hourly'): vol.In(FORECAST_MODE),
    vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']),
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=3)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Dark Sky weather."""
    latitude = config.get(CONF_LATITUDE, hass.config.latitude)
    longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
    name = config.get(CONF_NAME)
    mode = config.get(CONF_MODE)

    units = config.get(CONF_UNITS)
    if not units:
        units = 'ca' if hass.config.units.is_metric else 'us'

    dark_sky = DarkSkyData(
        config.get(CONF_API_KEY), latitude, longitude, units)

    add_entities([DarkSkyWeather(name, dark_sky, mode)], True)


class DarkSkyWeather(WeatherEntity):
    """Representation of a weather condition."""

    def __init__(self, name, dark_sky, mode):
        """Initialize Dark Sky weather."""
        self._name = name
        self._dark_sky = dark_sky
        self._mode = mode

        self._ds_data = None
        self._ds_currently = None
        self._ds_hourly = None
        self._ds_daily = None

    @property
    def attribution(self):
        """Return the attribution."""
        return ATTRIBUTION

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

    @property
    def temperature(self):
        """Return the temperature."""
        return self._ds_currently.get('temperature')

    @property
    def temperature_unit(self):
        """Return the unit of measurement."""
        if self._dark_sky.units is None:
            return None
        return TEMP_FAHRENHEIT if 'us' in self._dark_sky.units \
            else TEMP_CELSIUS

    @property
    def humidity(self):
        """Return the humidity."""
        return round(self._ds_currently.get('humidity') * 100.0, 2)

    @property
    def wind_speed(self):
        """Return the wind speed."""
        return self._ds_currently.get('windSpeed')

    @property
    def wind_bearing(self):
        """Return the wind bearing."""
        return self._ds_currently.get('windBearing')

    @property
    def ozone(self):
        """Return the ozone level."""
        return self._ds_currently.get('ozone')

    @property
    def pressure(self):
        """Return the pressure."""
        pressure = self._ds_currently.get('pressure')
        if 'us' in self._dark_sky.units:
            return round(
                convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2)
        return pressure

    @property
    def visibility(self):
        """Return the visibility."""
        return self._ds_currently.get('visibility')

    @property
    def condition(self):
        """Return the weather condition."""
        return MAP_CONDITION.get(self._ds_currently.get('icon'))

    @property
    def forecast(self):
        """Return the forecast array."""
        # Per conversation with Joshua Reyes of Dark Sky, to get the total
        # forecasted precipitation, you have to multiple the intensity by
        # the hours for the forecast interval
        def calc_precipitation(intensity, hours):
            amount = None
            if intensity is not None:
                amount = round((intensity * hours), 1)
            return amount if amount > 0 else None

        data = None

        if self._mode == 'daily':
            data = [{
                ATTR_FORECAST_TIME:
                    datetime.fromtimestamp(entry.d.get('time')).isoformat(),
                ATTR_FORECAST_TEMP:
                    entry.d.get('temperatureHigh'),
                ATTR_FORECAST_TEMP_LOW:
                    entry.d.get('temperatureLow'),
                ATTR_FORECAST_PRECIPITATION:
                    calc_precipitation(entry.d.get('precipIntensity'), 24),
                ATTR_FORECAST_WIND_SPEED:
                    entry.d.get('windSpeed'),
                ATTR_FORECAST_WIND_BEARING:
                    entry.d.get('windBearing'),
                ATTR_FORECAST_CONDITION:
                    MAP_CONDITION.get(entry.d.get('icon'))
            } for entry in self._ds_daily.data]
        else:
            data = [{
                ATTR_FORECAST_TIME:
                    datetime.fromtimestamp(entry.d.get('time')).isoformat(),
                ATTR_FORECAST_TEMP:
                    entry.d.get('temperature'),
                ATTR_FORECAST_PRECIPITATION:
                    calc_precipitation(entry.d.get('precipIntensity'), 1),
                ATTR_FORECAST_CONDITION:
                    MAP_CONDITION.get(entry.d.get('icon'))
            } for entry in self._ds_hourly.data]

        return data

    def update(self):
        """Get the latest data from Dark Sky."""
        self._dark_sky.update()

        self._ds_data = self._dark_sky.data
        self._ds_currently = self._dark_sky.currently.d
        self._ds_hourly = self._dark_sky.hourly
        self._ds_daily = self._dark_sky.daily


class DarkSkyData:
    """Get the latest data from Dark Sky."""

    def __init__(self, api_key, latitude, longitude, units):
        """Initialize the data object."""
        self._api_key = api_key
        self.latitude = latitude
        self.longitude = longitude
        self.requested_units = units

        self.data = None
        self.currently = None
        self.hourly = None
        self.daily = None

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Get the latest data from Dark Sky."""
        import forecastio

        try:
            self.data = forecastio.load_forecast(
                self._api_key, self.latitude, self.longitude,
                units=self.requested_units)
            self.currently = self.data.currently()
            self.hourly = self.data.hourly()
            self.daily = self.data.daily()
        except (ConnectError, HTTPError, Timeout, ValueError) as error:
            _LOGGER.error("Unable to connect to Dark Sky. %s", error)
            self.data = None

    @property
    def units(self):
        """Get the unit system of returned data."""
        if self.data is None:
            return None
        return self.data.json.get('flags').get('units')
