"""
Support for IP Webcam, an Android app that acts as a full-featured webcam.

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

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.const import (
    CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
    CONF_SENSORS, CONF_SWITCHES, CONF_TIMEOUT, CONF_SCAN_INTERVAL,
    CONF_PLATFORM)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
    async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.components.camera.mjpeg import (
    CONF_MJPEG_URL, CONF_STILL_IMAGE_URL)

DOMAIN = 'android_ip_webcam'
REQUIREMENTS = ["pydroid-ipcam==0.4"]

_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)

DATA_IP_WEBCAM = 'android_ip_webcam'

ATTR_HOST = 'host'
ATTR_VID_CONNS = 'Video Connections'
ATTR_AUD_CONNS = 'Audio Connections'

KEY_MAP = {
    'audio_connections': 'Audio Connections',
    'adet_limit': 'Audio Trigger Limit',
    'antibanding': 'Anti-banding',
    'audio_only': 'Audio Only',
    'battery_level': 'Battery Level',
    'battery_temp': 'Battery Temperature',
    'battery_voltage': 'Battery Voltage',
    'coloreffect': 'Color Effect',
    'exposure': 'Exposure Level',
    'exposure_lock': 'Exposure Lock',
    'ffc': 'Front-facing Camera',
    'flashmode': 'Flash Mode',
    'focus': 'Focus',
    'focus_homing': 'Focus Homing',
    'focus_region': 'Focus Region',
    'focusmode': 'Focus Mode',
    'gps_active': 'GPS Active',
    'idle': 'Idle',
    'ip_address': 'IPv4 Address',
    'ipv6_address': 'IPv6 Address',
    'ivideon_streaming': 'Ivideon Streaming',
    'light': 'Light Level',
    'mirror_flip': 'Mirror Flip',
    'motion': 'Motion',
    'motion_active': 'Motion Active',
    'motion_detect': 'Motion Detection',
    'motion_event': 'Motion Event',
    'motion_limit': 'Motion Limit',
    'night_vision': 'Night Vision',
    'night_vision_average': 'Night Vision Average',
    'night_vision_gain': 'Night Vision Gain',
    'orientation': 'Orientation',
    'overlay': 'Overlay',
    'photo_size': 'Photo Size',
    'pressure': 'Pressure',
    'proximity': 'Proximity',
    'quality': 'Quality',
    'scenemode': 'Scene Mode',
    'sound': 'Sound',
    'sound_event': 'Sound Event',
    'sound_timeout': 'Sound Timeout',
    'torch': 'Torch',
    'video_connections': 'Video Connections',
    'video_chunk_len': 'Video Chunk Length',
    'video_recording': 'Video Recording',
    'video_size': 'Video Size',
    'whitebalance': 'White Balance',
    'whitebalance_lock': 'White Balance Lock',
    'zoom': 'Zoom'
}

ICON_MAP = {
    'audio_connections': 'mdi:speaker',
    'battery_level': 'mdi:battery',
    'battery_temp': 'mdi:thermometer',
    'battery_voltage': 'mdi:battery-charging-100',
    'exposure_lock': 'mdi:camera',
    'ffc': 'mdi:camera-front-variant',
    'focus': 'mdi:image-filter-center-focus',
    'gps_active': 'mdi:crosshairs-gps',
    'light': 'mdi:flashlight',
    'motion': 'mdi:run',
    'night_vision': 'mdi:weather-night',
    'overlay': 'mdi:monitor',
    'pressure': 'mdi:gauge',
    'proximity': 'mdi:map-marker-radius',
    'quality': 'mdi:quality-high',
    'sound': 'mdi:speaker',
    'sound_event': 'mdi:speaker',
    'sound_timeout': 'mdi:speaker',
    'torch': 'mdi:white-balance-sunny',
    'video_chunk_len': 'mdi:video',
    'video_connections': 'mdi:eye',
    'video_recording': 'mdi:record-rec',
    'whitebalance_lock': 'mdi:white-balance-auto'
}

SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
            'overlay', 'torch', 'whitebalance_lock', 'video_recording']

SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
           'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
           'sound', 'video_connections']

SIGNAL_UPDATE_DATA = 'android_ip_webcam_update'

CONF_AUTO_DISCOVERY = 'auto_discovery'
CONF_MOTION_SENSOR = 'motion_sensor'

DEFAULT_AUTO_DISCOVERY = True
DEFAULT_MOTION_SENSOR = False
DEFAULT_NAME = 'IP Webcam'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10


CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
        vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
            cv.time_period,
        vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
        vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
        vol.Optional(CONF_AUTO_DISCOVERY, default=DEFAULT_AUTO_DISCOVERY):
            cv.boolean,
        vol.Optional(CONF_SWITCHES, default=[]):
            vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
        vol.Optional(CONF_SENSORS, default=[]):
            vol.All(cv.ensure_list, [vol.In(SENSORS)]),
        vol.Optional(CONF_MOTION_SENSOR, default=DEFAULT_MOTION_SENSOR):
            cv.boolean,
    })])
}, extra=vol.ALLOW_EXTRA)


@asyncio.coroutine
def async_setup(hass, config):
    """Setup the IP Webcam component."""
    from pydroid_ipcam import PyDroidIPCam

    webcams = hass.data[DATA_IP_WEBCAM] = {}
    websession = async_get_clientsession(hass)

    @asyncio.coroutine
    def async_setup_ipcamera(cam_config):
        """Setup a ip camera."""
        host = cam_config[CONF_HOST]
        username = cam_config.get(CONF_USERNAME)
        password = cam_config.get(CONF_PASSWORD)
        name = cam_config[CONF_NAME]
        interval = cam_config[CONF_SCAN_INTERVAL]
        switches = cam_config[CONF_SWITCHES]
        sensors = cam_config[CONF_SENSORS]
        motion = cam_config[CONF_MOTION_SENSOR]

        # init ip webcam
        cam = PyDroidIPCam(
            hass.loop, websession, host, cam_config[CONF_PORT],
            username=username, password=password,
            timeout=cam_config[CONF_TIMEOUT]
        )

        @asyncio.coroutine
        def async_update_data(now):
            """Update data from ipcam in SCAN_INTERVAL."""
            yield from cam.update()
            async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)

            async_track_point_in_utc_time(
                hass, async_update_data, utcnow() + interval)

        yield from async_update_data(None)

        # use autodiscovery to detect sensors/configs
        if cam_config[CONF_AUTO_DISCOVERY]:
            if not cam.available:
                _LOGGER.error(
                    "Android webcam %s not found for discovery!", cam.base_url)
                return

            sensors = [sensor for sensor in cam.enabled_sensors
                       if sensor in SENSORS]
            switches = [setting for setting in cam.enabled_settings
                        if setting in SWITCHES]
            motion = True if 'motion_active' in cam.enabled_sensors else False
            sensors.extend(['audio_connections', 'video_connections'])

        # load platforms
        webcams[host] = cam

        mjpeg_camera = {
            CONF_PLATFORM: 'mjpeg',
            CONF_MJPEG_URL: cam.mjpeg_url,
            CONF_STILL_IMAGE_URL: cam.image_url,
            CONF_NAME: name,
        }
        if username and password:
            mjpeg_camera.update({
                CONF_USERNAME: username,
                CONF_PASSWORD: password
            })

        hass.async_add_job(discovery.async_load_platform(
            hass, 'camera', 'mjpeg', mjpeg_camera, config))

        if sensors:
            hass.async_add_job(discovery.async_load_platform(
                hass, 'sensor', DOMAIN, {
                    CONF_NAME: name,
                    CONF_HOST: host,
                    CONF_SENSORS: sensors,
                }, config))

        if switches:
            hass.async_add_job(discovery.async_load_platform(
                hass, 'switch', DOMAIN, {
                    CONF_NAME: name,
                    CONF_HOST: host,
                    CONF_SWITCHES: switches,
                }, config))

        if motion:
            hass.async_add_job(discovery.async_load_platform(
                hass, 'binary_sensor', DOMAIN, {
                    CONF_HOST: host,
                    CONF_NAME: name,
                }, config))

    tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
    if tasks:
        yield from asyncio.wait(tasks, loop=hass.loop)

    return True


class AndroidIPCamEntity(Entity):
    """The Android device running IP Webcam."""

    def __init__(self, host, ipcam):
        """Initialize the data oject."""
        self._host = host
        self._ipcam = ipcam

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register update dispatcher."""
        @callback
        def async_ipcam_update(host):
            """Update callback."""
            if self._host != host:
                return
            self.hass.async_add_job(self.async_update_ha_state(True))

        async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update)

    @property
    def should_poll(self):
        """Return True if entity has to be polled for state."""
        return False

    @property
    def available(self):
        """Return True if entity is available."""
        return self._ipcam.available

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        state_attr = {ATTR_HOST: self._host}
        if self._ipcam.status_data is None:
            return state_attr

        state_attr[ATTR_VID_CONNS] = \
            self._ipcam.status_data.get('video_connections')
        state_attr[ATTR_AUD_CONNS] = \
            self._ipcam.status_data.get('audio_connections')

        return state_attr
