"""
Support for PlayStation 4 consoles.

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

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
from homeassistant.components.media_player import (
    MediaPlayerDevice, ENTITY_IMAGE_URL)
from homeassistant.components.media_player.const import (
    MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOURCE,
    SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
)
from homeassistant.components.ps4.const import DOMAIN as PS4_DOMAIN
from homeassistant.const import (
    ATTR_ENTITY_ID, ATTR_COMMAND, CONF_HOST, CONF_NAME, CONF_REGION,
    CONF_TOKEN, STATE_IDLE, STATE_OFF, STATE_PLAYING,
)
from homeassistant.util.json import load_json, save_json


DEPENDENCIES = ['ps4']

_LOGGER = logging.getLogger(__name__)

SUPPORT_PS4 = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \
    SUPPORT_STOP | SUPPORT_SELECT_SOURCE

PS4_DATA = 'ps4_data'
ICON = 'mdi:playstation'
GAMES_FILE = '.ps4-games.json'
MEDIA_IMAGE_DEFAULT = None

MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=10)

COMMANDS = (
    'up',
    'down',
    'right',
    'left',
    'enter',
    'back',
    'option',
    'ps',
)

SERVICE_COMMAND = 'send_command'

PS4_COMMAND_SCHEMA = vol.Schema({
    vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Required(ATTR_COMMAND): vol.In(list(COMMANDS))
})


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up PS4 from a config entry."""
    config = config_entry

    def add_entities(entities, update_before_add=False):
        """Sync version of async add devices."""
        hass.add_job(async_add_entities, entities, update_before_add)

    await hass.async_add_executor_job(
        setup_platform, hass, config,
        add_entities, None)

    async def async_service_handle(hass):
        """Handle for services."""
        def service_command(call):
            entity_ids = call.data[ATTR_ENTITY_ID]
            command = call.data[ATTR_COMMAND]
            for device in hass.data[PS4_DATA].devices:
                if device.entity_id in entity_ids:
                    device.send_command(command)

        hass.services.async_register(
            PS4_DOMAIN, SERVICE_COMMAND, service_command,
            schema=PS4_COMMAND_SCHEMA)

    await async_service_handle(hass)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up PS4 Platform."""
    import pyps4_homeassistant as pyps4
    hass.data[PS4_DATA] = PS4Data()
    games_file = hass.config.path(GAMES_FILE)
    creds = config.data[CONF_TOKEN]
    device_list = []
    for device in config.data['devices']:
        host = device[CONF_HOST]
        region = device[CONF_REGION]
        name = device[CONF_NAME]
        ps4 = pyps4.Ps4(host, creds)
        device_list.append(PS4Device(
            name, host, region, ps4, games_file))
    add_entities(device_list, True)


class PS4Data():
    """Init Data Class."""

    def __init__(self):
        """Init Class."""
        self.devices = []


class PS4Device(MediaPlayerDevice):
    """Representation of a PS4."""

    def __init__(self, name, host, region, ps4, games_file):
        """Initialize the ps4 device."""
        self._ps4 = ps4
        self._host = host
        self._name = name
        self._region = region
        self._state = None
        self._games_filename = games_file
        self._media_content_id = None
        self._media_title = None
        self._media_image = None
        self._source = None
        self._games = {}
        self._source_list = []
        self._retry = 0
        self._info = None
        self._unique_id = None
        self._power_on = False

    async def async_added_to_hass(self):
        """Subscribe PS4 events."""
        self.hass.data[PS4_DATA].devices.append(self)

    @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
    def update(self):
        """Retrieve the latest data."""
        try:
            status = self._ps4.get_status()
            if self._info is None:
                # Add entity to registry
                self.get_device_info(status)
                self._games = self.load_games()
                if self._games is not None:
                    self._source_list = list(sorted(self._games.values()))
        except socket.timeout:
            status = None
        if status is not None:
            self._retry = 0
            if status.get('status') == 'Ok':
                # Check if only 1 device in Hass.
                if len(self.hass.data[PS4_DATA].devices) == 1:
                    # Enable keep alive feature for PS4 Connection.
                    # Only 1 device is supported, Since have to use port 997.
                    self._ps4.keep_alive = True
                else:
                    self._ps4.keep_alive = False
                if self._power_on:
                    # Auto Login after Turn On.
                    self._ps4.open()
                    self._power_on = False
                title_id = status.get('running-app-titleid')
                name = status.get('running-app-name')
                if title_id and name is not None:
                    self._state = STATE_PLAYING
                    if self._media_content_id != title_id:
                        self._media_content_id = title_id
                        self.get_title_data(title_id, name)
                else:
                    self.idle()
            else:
                self.state_off()
        elif self._retry > 5:
            self.state_unknown()
        else:
            self._retry += 1

    def idle(self):
        """Set states for state idle."""
        self.reset_title()
        self._state = STATE_IDLE

    def state_off(self):
        """Set states for state off."""
        self.reset_title()
        self._state = STATE_OFF

    def state_unknown(self):
        """Set states for state unknown."""
        self.reset_title()
        self._state = None
        _LOGGER.warning("PS4 could not be reached")
        self._retry = 0

    def reset_title(self):
        """Update if there is no title."""
        self._media_title = None
        self._media_content_id = None
        self._source = None

    def get_title_data(self, title_id, name):
        """Get PS Store Data."""
        app_name = None
        art = None
        try:
            app_name, art = self._ps4.get_ps_store_data(
                name, title_id, self._region)
        except TypeError:
            _LOGGER.error(
                "Could not find data in region: %s for PS ID: %s",
                self._region, title_id)
        finally:
            self._media_title = app_name or name
            self._source = self._media_title
            self._media_image = art
            self.update_list()

    def update_list(self):
        """Update Game List, Correct data if different."""
        if self._media_content_id in self._games:
            store = self._games[self._media_content_id]
            if store != self._media_title:
                self._games.pop(self._media_content_id)
        if self._media_content_id not in self._games:
            self.add_games(self._media_content_id, self._media_title)
            self._games = self.load_games()
        self._source_list = list(sorted(self._games.values()))

    def load_games(self):
        """Load games for sources."""
        g_file = self._games_filename
        try:
            games = load_json(g_file)

        # If file does not exist, create empty file.
        except FileNotFoundError:
            games = {}
            self.save_games(games)
        return games

    def save_games(self, games):
        """Save games to file."""
        g_file = self._games_filename
        try:
            save_json(g_file, games)
        except OSError as error:
            _LOGGER.error("Could not save game list, %s", error)

        # Retry loading file
        if games is None:
            self.load_games()

    def add_games(self, title_id, app_name):
        """Add games to list."""
        games = self._games
        if title_id is not None and title_id not in games:
            game = {title_id: app_name}
            games.update(game)
            self.save_games(games)

    def get_device_info(self, status):
        """Return device info for registry."""
        _sw_version = status['system-version']
        _sw_version = _sw_version[1:4]
        sw_version = "{}.{}".format(_sw_version[0], _sw_version[1:])
        self._info = {
            'name': status['host-name'],
            'model': 'PlayStation 4',
            'identifiers': {
                (PS4_DOMAIN, status['host-id'])
            },
            'manufacturer': 'Sony Interactive Entertainment Inc.',
            'sw_version': sw_version
        }
        self._unique_id = status['host-id']

    async def async_will_remove_from_hass(self):
        """Remove Entity from Hass."""
        self.hass.data[PS4_DATA].devices.remove(self)

    @property
    def device_info(self):
        """Return information about the device."""
        return self._info

    @property
    def unique_id(self):
        """Return Unique ID for entity."""
        return self._unique_id

    @property
    def entity_picture(self):
        """Return picture."""
        if self._state == STATE_PLAYING and self._media_content_id is not None:
            image_hash = self.media_image_hash
            if image_hash is not None:
                return ENTITY_IMAGE_URL.format(
                    self.entity_id, self.access_token, image_hash)
        return MEDIA_IMAGE_DEFAULT

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

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def icon(self):
        """Icon."""
        return ICON

    @property
    def media_content_id(self):
        """Content ID of current playing media."""
        return self._media_content_id

    @property
    def media_content_type(self):
        """Content type of current playing media."""
        return MEDIA_TYPE_MUSIC

    @property
    def media_image_url(self):
        """Image url of current playing media."""
        if self._media_content_id is None:
            return MEDIA_IMAGE_DEFAULT
        return self._media_image

    @property
    def media_title(self):
        """Title of current playing media."""
        return self._media_title

    @property
    def supported_features(self):
        """Media player features that are supported."""
        return SUPPORT_PS4

    @property
    def source(self):
        """Return the current input source."""
        return self._source

    @property
    def source_list(self):
        """List of available input sources."""
        return self._source_list

    def turn_off(self):
        """Turn off media player."""
        self._ps4.standby()

    def turn_on(self):
        """Turn on the media player."""
        self._power_on = True
        self._ps4.wakeup()

    def media_pause(self):
        """Send keypress ps to return to menu."""
        self.send_remote_control('ps')

    def media_stop(self):
        """Send keypress ps to return to menu."""
        self.send_remote_control('ps')

    def select_source(self, source):
        """Select input source."""
        for title_id, game in self._games.items():
            if source == game:
                _LOGGER.debug(
                    "Starting PS4 game %s (%s) using source %s",
                    game, title_id, source)
                self._ps4.start_title(
                    title_id, running_id=self._media_content_id)
                return

    def send_command(self, command):
        """Send Button Command."""
        self.send_remote_control(command)

    def send_remote_control(self, command):
        """Send RC command."""
        self._ps4.remote_control(command)
