"""
Support for Synology NAS Sensors.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.synologydsm/
"""

import logging
from datetime import timedelta

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
    CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
    CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, EVENT_HOMEASSISTANT_START)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv

import voluptuous as vol

REQUIREMENTS = ['python-synology==0.1.0']

_LOGGER = logging.getLogger(__name__)

CONF_DISKS = 'disks'
CONF_VOLUMES = 'volumes'
DEFAULT_NAME = 'Synology DSM'
DEFAULT_PORT = 5000

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)

_UTILISATION_MON_COND = {
    'cpu_other_load': ['CPU Load (Other)', '%', 'mdi:chip'],
    'cpu_user_load': ['CPU Load (User)', '%', 'mdi:chip'],
    'cpu_system_load': ['CPU Load (System)', '%', 'mdi:chip'],
    'cpu_total_load': ['CPU Load (Total)', '%', 'mdi:chip'],
    'cpu_1min_load': ['CPU Load (1 min)', '%', 'mdi:chip'],
    'cpu_5min_load': ['CPU Load (5 min)', '%', 'mdi:chip'],
    'cpu_15min_load': ['CPU Load (15 min)', '%', 'mdi:chip'],
    'memory_real_usage': ['Memory Usage (Real)', '%', 'mdi:memory'],
    'memory_size': ['Memory Size', 'Mb', 'mdi:memory'],
    'memory_cached': ['Memory Cached', 'Mb', 'mdi:memory'],
    'memory_available_swap': ['Memory Available (Swap)', 'Mb', 'mdi:memory'],
    'memory_available_real': ['Memory Available (Real)', 'Mb', 'mdi:memory'],
    'memory_total_swap': ['Memory Total (Swap)', 'Mb', 'mdi:memory'],
    'memory_total_real': ['Memory Total (Real)', 'Mb', 'mdi:memory'],
    'network_up': ['Network Up', 'Kbps', 'mdi:upload'],
    'network_down': ['Network Down', 'Kbps', 'mdi:download'],
}
_STORAGE_VOL_MON_COND = {
    'volume_status': ['Status', None, 'mdi:checkbox-marked-circle-outline'],
    'volume_device_type': ['Type', None, 'mdi:harddisk'],
    'volume_size_total': ['Total Size', None, 'mdi:chart-pie'],
    'volume_size_used': ['Used Space', None, 'mdi:chart-pie'],
    'volume_percentage_used': ['Volume Used', '%', 'mdi:chart-pie'],
    'volume_disk_temp_avg': ['Average Disk Temp', None, 'mdi:thermometer'],
    'volume_disk_temp_max': ['Maximum Disk Temp', None, 'mdi:thermometer'],
}
_STORAGE_DSK_MON_COND = {
    'disk_name': ['Name', None, 'mdi:harddisk'],
    'disk_device': ['Device', None, 'mdi:dots-horizontal'],
    'disk_smart_status': ['Status (Smart)', None,
                          'mdi:checkbox-marked-circle-outline'],
    'disk_status': ['Status', None, 'mdi:checkbox-marked-circle-outline'],
    'disk_exceed_bad_sector_thr': ['Exceeded Max Bad Sectors', None,
                                   'mdi:test-tube'],
    'disk_below_remain_life_thr': ['Below Min Remaining Life', None,
                                   'mdi:test-tube'],
    'disk_temp': ['Temperature', None, 'mdi:thermometer'],
}

_MONITORED_CONDITIONS = list(_UTILISATION_MON_COND.keys()) + \
    list(_STORAGE_VOL_MON_COND.keys()) + \
    list(_STORAGE_DSK_MON_COND.keys())

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
    vol.Required(CONF_USERNAME): cv.string,
    vol.Required(CONF_PASSWORD): cv.string,
    vol.Optional(CONF_MONITORED_CONDITIONS):
        vol.All(cv.ensure_list, [vol.In(_MONITORED_CONDITIONS)]),
    vol.Optional(CONF_DISKS, default=None): cv.ensure_list,
    vol.Optional(CONF_VOLUMES, default=None): cv.ensure_list,
})


def setup_platform(hass, config, add_devices_callback, discovery_info=None):
    """Setup the Synology NAS Sensor."""
    # pylint: disable=too-many-locals
    def run_setup(event):
        """Wait until HASS is fully initialized before creating.

        Delay the setup until Home Assistant is fully initialized.
        This allows any entities to be created already
        """
        # Setup API
        api = SynoApi(config.get(CONF_HOST), config.get(CONF_PORT),
                      config.get(CONF_USERNAME), config.get(CONF_PASSWORD),
                      hass.config.units.temperature_unit)

        sensors = [SynoNasUtilSensor(api, variable,
                                     _UTILISATION_MON_COND[variable])
                   for variable in config[CONF_MONITORED_CONDITIONS]
                   if variable in _UTILISATION_MON_COND]

        # Handle all Volumes
        volumes = config['volumes']
        if volumes is None:
            volumes = api.storage.volumes

        for volume in volumes:
            sensors += [SynoNasStorageSensor(api, variable,
                                             _STORAGE_VOL_MON_COND[variable],
                                             volume)
                        for variable in config[CONF_MONITORED_CONDITIONS]
                        if variable in _STORAGE_VOL_MON_COND]

        # Handle all Disks
        disks = config['disks']
        if disks is None:
            disks = api.storage.disks

        for disk in disks:
            sensors += [SynoNasStorageSensor(api, variable,
                                             _STORAGE_DSK_MON_COND[variable],
                                             disk)
                        for variable in config[CONF_MONITORED_CONDITIONS]
                        if variable in _STORAGE_DSK_MON_COND]

        add_devices_callback(sensors)

    # Wait until start event is sent to load this component.
    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)


class SynoApi():
    """Class to interface with API."""

    # pylint: disable=too-many-arguments, bare-except
    def __init__(self, host, port, username, password, temp_unit):
        """Constructor of the API wrapper class."""
        from SynologyDSM import SynologyDSM
        self.temp_unit = temp_unit

        try:
            self._api = SynologyDSM(host,
                                    port,
                                    username,
                                    password)
        except:
            _LOGGER.error("Error setting up Synology DSM")

        # Will be updated when `update` gets called.
        self.utilisation = self._api.utilisation
        self.storage = self._api.storage

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Update function for updating api information."""
        self._api.update()


class SynoNasSensor(Entity):
    """Representation of a Synology Nas Sensor."""

    def __init__(self, api, variable, variableInfo, monitor_device=None):
        """Initialize the sensor."""
        self.var_id = variable
        self.var_name = variableInfo[0]
        self.var_units = variableInfo[1]
        self.var_icon = variableInfo[2]
        self.monitor_device = monitor_device
        self._api = api

    @property
    def name(self):
        """Return the name of the sensor, if any."""
        if self.monitor_device is not None:
            return "{} ({})".format(self.var_name, self.monitor_device)
        else:
            return self.var_name

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return self.var_icon

    @property
    def unit_of_measurement(self):
        """Return the unit the value is expressed in."""
        if self.var_id in ['volume_disk_temp_avg', 'volume_disk_temp_max',
                           'disk_temp']:
            return self._api.temp_unit
        else:
            return self.var_units

    def update(self):
        """Get the latest data for the states."""
        if self._api is not None:
            self._api.update()


class SynoNasUtilSensor(SynoNasSensor):
    """Representation a Synology Utilisation Sensor."""

    @property
    def state(self):
        """Return the state of the sensor."""
        network_sensors = ['network_up', 'network_down']
        memory_sensors = ['memory_size', 'memory_cached',
                          'memory_available_swap', 'memory_available_real',
                          'memory_total_swap', 'memory_total_real']

        if self.var_id in network_sensors or self.var_id in memory_sensors:
            attr = getattr(self._api.utilisation, self.var_id)(False)

            if self.var_id in network_sensors:
                return round(attr / 1024.0, 1)
            elif self.var_id in memory_sensors:
                return round(attr / 1024.0 / 1024.0, 1)
        else:
            return getattr(self._api.utilisation, self.var_id)


class SynoNasStorageSensor(SynoNasSensor):
    """Representation a Synology Utilisation Sensor."""

    @property
    def state(self):
        """Return the state of the sensor."""
        temp_sensors = ['volume_disk_temp_avg', 'volume_disk_temp_max',
                        'disk_temp']

        if self.monitor_device is not None:
            if self.var_id in temp_sensors:
                attr = getattr(self._api.storage,
                               self.var_id)(self.monitor_device)

                if self._api.temp_unit == TEMP_CELSIUS:
                    return attr
                else:
                    return round(attr * 1.8 + 32.0, 1)
            else:
                return getattr(self._api.storage,
                               self.var_id)(self.monitor_device)