# -*- coding: utf-8 -*-
from collections import defaultdict

from plexapi import log, utils
from plexapi.base import PlexObject
from plexapi.compat import quote, string_type
from plexapi.exceptions import BadRequest, NotFound


class Settings(PlexObject):
    """ Container class for all settings. Allows getting and setting PlexServer settings.

        Attributes:
            key (str): '/:/prefs'
    """
    key = '/:/prefs'

    def __init__(self, server, data, initpath=None):
        self._settings = {}
        super(Settings, self).__init__(server, data, initpath)

    def __getattr__(self, attr):
        if attr.startswith('_'):
            return self.__dict__[attr]
        return self.get(attr).value

    def __setattr__(self, attr, value):
        if not attr.startswith('_'):
            return self.get(attr).set(value)
        self.__dict__[attr] = value

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        self._data = data
        for elem in data:
            id = utils.lowerFirst(elem.attrib['id'])
            if id in self._settings:
                self._settings[id]._loadData(elem)
                continue
            self._settings[id] = Setting(self._server, elem, self._initpath)

    def all(self):
        """ Returns a list of all :class:`~plexapi.settings.Setting` objects available. """
        return list(v for id, v in sorted(self._settings.items()))

    def get(self, id):
        """ Return the :class:`~plexapi.settings.Setting` object with the specified id. """
        id = utils.lowerFirst(id)
        if id in self._settings:
            return self._settings[id]
        raise NotFound('Invalid setting id: %s' % id)

    def groups(self):
        """ Returns a dict of lists for all :class:`~plexapi.settings.Setting`
            objects grouped by setting group.
        """
        groups = defaultdict(list)
        for setting in self.all():
            groups[setting.group].append(setting)
        return dict(groups)

    def group(self, group):
        """ Return a list of all :class:`~plexapi.settings.Setting` objects in the specified group.

            Parameters:
                group (str): Group to return all settings.
        """
        return self.groups().get(group, [])

    def save(self):
        """ Save any outstanding settnig changes to the :class:`~plexapi.server.PlexServer`. This
            performs a full reload() of Settings after complete.
        """
        params = {}
        for setting in self.all():
            if setting._setValue:
                log.info('Saving PlexServer setting %s = %s' % (setting.id, setting._setValue))
                params[setting.id] = quote(setting._setValue)
        if not params:
            raise BadRequest('No setting have been modified.')
        querystr = '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
        url = '%s?%s' % (self.key, querystr)
        self._server.query(url, self._server._session.put)
        self.reload()


class Setting(PlexObject):
    """ Represents a single Plex setting.

        Attributes:
            id (str): Setting id (or name).
            label (str): Short description of what this setting is.
            summary (str): Long description of what this setting is.
            type (str): Setting type (text, int, double, bool).
            default (str): Default value for this setting.
            value (str,bool,int,float): Current value for this setting.
            hidden (bool): True if this is a hidden setting.
            advanced (bool): True if this is an advanced setting.
            group (str): Group name this setting is categorized as.
            enumValues (list,dict): List or dictionary of valis values for this setting.
    """
    _bool_cast = lambda x: True if x == 'true' else False
    _bool_str = lambda x: str(x).lower()
    TYPES = {
        'bool': {'type': bool, 'cast': _bool_cast, 'tostr': _bool_str},
        'double': {'type': float, 'cast': float, 'tostr': string_type},
        'int': {'type': int, 'cast': int, 'tostr': string_type},
        'text': {'type': string_type, 'cast': string_type, 'tostr': string_type},
    }

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        self._setValue = None
        self.id = data.attrib.get('id')
        self.label = data.attrib.get('label')
        self.summary = data.attrib.get('summary')
        self.type = data.attrib.get('type')
        self.default = self._cast(data.attrib.get('default'))
        self.value = self._cast(data.attrib.get('value'))
        self.hidden = utils.cast(bool, data.attrib.get('hidden'))
        self.advanced = utils.cast(bool, data.attrib.get('advanced'))
        self.group = data.attrib.get('group')
        self.enumValues = self._getEnumValues(data)

    def _cast(self, value):
        """ Cast the specifief value to the type of this setting. """
        if self.type != 'text':
            value = utils.cast(self.TYPES.get(self.type)['cast'], value)
        return value

    def _getEnumValues(self, data):
        """ Returns a list of dictionary of valis value for this setting. """
        enumstr = data.attrib.get('enumValues')
        if not enumstr:
            return None
        if ':' in enumstr:
            return {self._cast(k): v for k, v in [kv.split(':') for kv in enumstr.split('|')]}
        return enumstr.split('|')

    def set(self, value):
        """ Set a new value for this setitng. NOTE: You must call plex.settings.save() for before
            any changes to setting values are persisted to the :class:`~plexapi.server.PlexServer`.
        """
        # check a few things up front
        if not isinstance(value, self.TYPES[self.type]['type']):
            badtype = type(value).__name__
            raise BadRequest('Invalid value for %s: a %s is required, not %s' % (self.id, self.type, badtype))
        if self.enumValues and value not in self.enumValues:
            raise BadRequest('Invalid value for %s: %s not in %s' % (self.id, value, list(self.enumValues)))
        # store value off to the side until we call settings.save()
        tostr = self.TYPES[self.type]['tostr']
        self._setValue = tostr(value)

    def toUrl(self):
        """Helper for urls"""
        return '%s=%s' % (self.id, self._value or self.value)