"""Support for Repetier-Server sensors."""
import logging
from datetime import timedelta

import voluptuous as vol

from homeassistant.const import (
    CONF_API_KEY, CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PORT,
    CONF_SENSORS, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
from homeassistant.util import slugify as util_slugify

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = 'RepetierServer'
DOMAIN = 'repetier'
REPETIER_API = 'repetier_api'
SCAN_INTERVAL = timedelta(seconds=10)
UPDATE_SIGNAL = 'repetier_update_signal'

TEMP_DATA = {
    'tempset': 'temp_set',
    'tempread': 'state',
    'output': 'output',
}


API_PRINTER_METHODS = {
    'bed_temperature': {
        'offline': {'heatedbeds': None, 'state': 'off'},
        'state': {'heatedbeds': 'temp_data'},
        'temp_data': TEMP_DATA,
        'attribute': 'heatedbeds',
    },
    'extruder_temperature': {
        'offline': {'extruder': None, 'state': 'off'},
        'state': {'extruder': 'temp_data'},
        'temp_data': TEMP_DATA,
        'attribute': 'extruder',
    },
    'chamber_temperature': {
        'offline': {'heatedchambers': None, 'state': 'off'},
        'state': {'heatedchambers': 'temp_data'},
        'temp_data': TEMP_DATA,
        'attribute': 'heatedchambers',
    },
    'current_state': {
        'offline': {'state': None},
        'state': {
            'state': 'state',
            'activeextruder': 'active_extruder',
            'hasxhome': 'x_homed',
            'hasyhome': 'y_homed',
            'haszhome': 'z_homed',
            'firmware': 'firmware',
            'firmwareurl': 'firmware_url',
        },
    },
    'current_job': {
        'offline': {'job': None, 'state': 'off'},
        'state': {
            'done': 'state',
            'job': 'job_name',
            'jobid': 'job_id',
            'totallines': 'total_lines',
            'linessent': 'lines_sent',
            'oflayer': 'total_layers',
            'layer': 'current_layer',
            'speedmultiply': 'feed_rate',
            'flowmultiply': 'flow',
            'x': 'x',
            'y': 'y',
            'z': 'z',
        },
    },
    'job_end': {
        'offline': {
            'job': None, 'state': 'off', 'start': None, 'printtime': None},
        'state': {
            'job': 'job_name',
            'start': 'start',
            'printtime': 'print_time',
            'printedtimecomp': 'from_start',
        },
    },
    'job_start': {
        'offline': {
            'job': None,
            'state': 'off',
            'start': None,
            'printedtimecomp': None
        },
        'state': {
            'job': 'job_name',
            'start': 'start',
            'printedtimecomp': 'from_start',
        },
    },
}


def has_all_unique_names(value):
    """Validate that printers have an unique name."""
    names = [util_slugify(printer[CONF_NAME]) for printer in value]
    vol.Schema(vol.Unique())(names)
    return value


SENSOR_TYPES = {
    # Type, Unit, Icon
    'bed_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
                        '_bed_'],
    'extruder_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
                             '_extruder_'],
    'chamber_temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer',
                            '_chamber_'],
    'current_state': ['state', None, 'mdi:printer-3d', ''],
    'current_job': ['progress', '%', 'mdi:file-percent', '_current_job'],
    'job_end': ['progress', None, 'mdi:clock-end', '_job_end'],
    'job_start': ['progress', None, 'mdi:clock-start', '_job_start'],
}

SENSOR_SCHEMA = vol.Schema({
    vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
        vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
        vol.Required(CONF_API_KEY): cv.string,
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_PORT, default=3344): cv.port,
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
    })], has_all_unique_names),
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
    """Set up the Repetier Server component."""
    import pyrepetier

    hass.data[REPETIER_API] = {}

    for repetier in config[DOMAIN]:
        _LOGGER.debug("Repetier server config %s", repetier[CONF_HOST])

        url = "http://{}".format(repetier[CONF_HOST])
        port = repetier[CONF_PORT]
        api_key = repetier[CONF_API_KEY]

        client = pyrepetier.Repetier(
            url=url,
            port=port,
            apikey=api_key)

        printers = client.getprinters()

        if not printers:
            return False

        sensors = repetier[CONF_SENSORS][CONF_MONITORED_CONDITIONS]
        api = PrinterAPI(hass, client, printers, sensors,
                         repetier[CONF_NAME], config)
        api.update()
        track_time_interval(hass, api.update, SCAN_INTERVAL)

        hass.data[REPETIER_API][repetier[CONF_NAME]] = api

    return True


class PrinterAPI:
    """Handle the printer API."""

    def __init__(self, hass, client, printers, sensors, conf_name, config):
        """Set up instance."""
        self._hass = hass
        self._client = client
        self.printers = printers
        self.sensors = sensors
        self.conf_name = conf_name
        self.config = config
        self._known_entities = set()

    def get_data(self, printer_id, sensor_type, temp_id):
        """Get data from the state cache."""
        printer = self.printers[printer_id]
        methods = API_PRINTER_METHODS[sensor_type]
        for prop, offline in methods['offline'].items():
            state = getattr(printer, prop)
            if state == offline:
                # if state matches offline, sensor is offline
                return None

        data = {}
        for prop, attr in methods['state'].items():
            prop_data = getattr(printer, prop)
            if attr == 'temp_data':
                temp_methods = methods['temp_data']
                for temp_prop, temp_attr in temp_methods.items():
                    data[temp_attr] = getattr(prop_data[temp_id], temp_prop)
            else:
                data[attr] = prop_data
        return data

    def update(self, now=None):
        """Update the state cache from the printer API."""
        for printer in self.printers:
            printer.get_data()
        self._load_entities()
        dispatcher_send(self._hass, UPDATE_SIGNAL)

    def _load_entities(self):
        sensor_info = []
        for pidx, printer in enumerate(self.printers):
            for sensor_type in self.sensors:
                info = {}
                info['sensor_type'] = sensor_type
                info['printer_id'] = pidx
                info['name'] = printer.slug
                info['printer_name'] = self.conf_name

                known = '{}-{}'.format(printer.slug, sensor_type)
                if known in self._known_entities:
                    continue

                methods = API_PRINTER_METHODS[sensor_type]
                if 'temp_data' in methods['state'].values():
                    prop_data = getattr(printer, methods['attribute'])
                    if prop_data is None:
                        continue
                    for idx, _ in enumerate(prop_data):
                        info['temp_id'] = idx
                        sensor_info.append(info)
                else:
                    info['temp_id'] = None
                    sensor_info.append(info)

                self._known_entities.add(known)

        if not sensor_info:
            return
        load_platform(self._hass, 'sensor', DOMAIN, sensor_info, self.config)
