from datetime import datetime

import exceptions
import util
import plexapp
import json

# Search Types - Plex uses these to filter specific media types when searching.
SEARCHTYPES = {
    'movie': 1,
    'show': 2,
    'season': 3,
    'episode': 4,
    'artist': 8,
    'album': 9,
    'track': 10
}

LIBRARY_TYPES = {}


def registerLibType(cls):
    LIBRARY_TYPES[cls.TYPE] = cls
    return cls


def registerLibFactory(ftype):
    def wrap(func):
        LIBRARY_TYPES[ftype] = func
        return func
    return wrap


class PlexValue(unicode):
    def __new__(cls, value, parent=None):
        self = super(PlexValue, cls).__new__(cls, value)
        self.parent = parent
        self.NA = False
        return self

    def __call__(self, default):
        return not self.NA and self or PlexValue(default, self.parent)

    def asBool(self):
        return self == '1'

    def asInt(self, default=0):
        return int(self or default)

    def asFloat(self, default=0):
        return float(self or default)

    def asDatetime(self, format_=None):
        if not self:
            return None

        if self.isdigit():
            dt = datetime.fromtimestamp(int(self))
        else:
            dt = datetime.strptime(self, '%Y-%m-%d')

        if not format_:
            return dt

        return dt.strftime(format_)

    def asURL(self):
        return self.parent.server.url(self)

    def asTranscodedImageURL(self, w, h, **extras):
        return self.parent.server.getImageTranscodeURL(self, w, h, **extras)


class JEncoder(json.JSONEncoder):
    def default(self, o):
        try:
            return json.JSONEncoder.default(self, o)
        except:
            return None


def asFullObject(func):
    def wrap(self, *args, **kwargs):
        if not self.isFullObject():
            self.reload()
        return func(self, *args, **kwargs)

    return wrap


class Checks:
    def isLibraryItem(self):
        return "/library/metadata" in self.get('key', '') or ("/playlists/" in self.get('key', '') and self.get("type", "") == "playlist")

    def isVideoItem(self):
        return False

    def isMusicItem(self):
        return False

    def isOnlineItem(self):
        return self.isChannelItem() or self.isMyPlexItem() or self.isVevoItem() or self.isIvaItem()

    def isMyPlexItem(self):
        return self.container.server.TYPE == 'MYPLEXSERVER' or self.container.identifier == 'com.plexapp.plugins.myplex'

    def isChannelItem(self):
        identifier = self.getIdentifier() or "com.plexapp.plugins.library"
        return not self.isLibraryItem() and not self.isMyPlexItem() and identifier != "com.plexapp.plugins.library"

    def isVevoItem(self):
        return 'vevo://' in self.get('guid')

    def isIvaItem(self):
        return 'iva://' in self.get('guid')

    def isGracenoteCollection(self):
        return False

    def isIPhoto(self):
        return (self.title == "iPhoto" or self.container.title == "iPhoto" or (self.mediaType == "Image" or self.mediaType == "Movie"))

    def isDirectory(self):
        return self.name == "Directory" or self.name == "Playlist"

    def isPhotoOrDirectoryItem(self):
        return self.type == "photoalbum"  # or self.isPhotoItem()

    def isMusicOrDirectoryItem(self):
        return self.type in ('artist', 'album', 'track')

    def isVideoOrDirectoryItem(self):
        return self.type in ('movie', 'show', 'episode')

    def isSettings(self):
        return False


class PlexObject(object, Checks):
    def __init__(self, data, initpath=None, server=None, container=None):
        self.initpath = initpath
        self.key = None
        self.server = server
        self.container = container
        self.mediaChoice = None
        self.titleSort = PlexValue('')
        self.deleted = False
        self._reloaded = False

        if data is None:
            return

        self._setData(data)

        self.init(data)

    def _setData(self, data):
        if data is False:
            return

        self.name = data.tag
        for k, v in data.attrib.items():
            setattr(self, k, PlexValue(v, self))

    def __getattr__(self, attr):
        a = PlexValue('', self)
        a.NA = True

        try:
            setattr(self, attr, a)
        except AttributeError:
            util.LOG('Failed to set attribute: {0} ({1})'.format(attr, self.__class__))

        return a

    def exists(self):
        # Used for media items - for others we just return True
        return True

    def get(self, attr, default=''):
        ret = self.__dict__.get(attr)
        return ret is not None and ret or PlexValue(default, self)

    def set(self, attr, value):
        setattr(self, attr, PlexValue(unicode(value), self))

    def init(self, data):
        pass

    def isFullObject(self):
        return self.initpath is None or self.key is None or self.initpath == self.key

    def getAddress(self):
        return self.server.activeConnection.address

    @property
    def defaultTitle(self):
        return self.get('title')

    @property
    def defaultThumb(self):
        return self.__dict__.get('thumb') and self.thumb or PlexValue('', self)

    @property
    def defaultArt(self):
        return self.__dict__.get('art') and self.art or PlexValue('', self)

    def refresh(self):
        import requests
        self.server.query('%s/refresh' % self.key, method=requests.put)

    def reload(self, _soft=False, **kwargs):
        """ Reload the data for this object from PlexServer XML. """
        if _soft and self._reloaded:
            return self

        try:
            if self.get('ratingKey'):
                data = self.server.query('/library/metadata/{0}'.format(self.ratingKey), params=kwargs)
            else:
                data = self.server.query(self.key, params=kwargs)
            self._reloaded = True
        except Exception, e:
            import traceback
            traceback.print_exc()
            util.ERROR(err=e)
            self.initpath = self.key
            return self

        self.initpath = self.key

        try:
            self._setData(data[0])
        except IndexError:
            util.DEBUG_LOG('No data on reload: {0}'.format(self))
            return self

        return self

    def softReload(self, **kwargs):
        return self.reload(_soft=True, **kwargs)

    def getLibrarySectionId(self):
        ID = self.get('librarySectionID')

        if not ID:
            ID = self.container.get("librarySectionID", '')

        return ID

    def getLibrarySectionTitle(self):
        title = self.get('librarySectionTitle')

        if not title:
            title = self.container.get("librarySectionTitle", '')

        if not title:
            lsid = self.getLibrarySectionId()
            if lsid:
                data = self.server.query('/library/sections/{0}'.format(lsid))
                title = data.attrib.get('title1')
                if title:
                    self.librarySectionTitle = title
        return title

    def getLibrarySectionType(self):
        type_ = self.get('librarySectionType')

        if not type_:
            type_ = self.container.get("librarySectionType", '')

        if not type_:
            lsid = self.getLibrarySectionId()
            if lsid:
                data = self.server.query('/library/sections/{0}'.format(lsid))
                type_ = data.attrib.get('type')
                if type_:
                    self.librarySectionTitle = type_
        return type_

    def getLibrarySectionUuid(self):
        uuid = self.get("uuid") or self.get("librarySectionUUID")

        if not uuid:
            uuid = self.container.get("librarySectionUUID", "")

        return uuid

    def _findLocation(self, data):
        elem = data.find('Location')
        if elem is not None:
            return elem.attrib.get('path')
        return None

    def _findPlayer(self, data):
        elem = data.find('Player')
        if elem is not None:
            from plexapi.client import Client
            return Client(self.server, elem)
        return None

    def _findTranscodeSession(self, data):
        elem = data.find('TranscodeSession')
        if elem is not None:
            from plexapi import media
            return media.TranscodeSession(self.server, elem)
        return None

    def _findUser(self, data):
        elem = data.find('User')
        if elem is not None:
            from plexapi.myplex import MyPlexUser
            return MyPlexUser(elem, self.initpath)
        return None

    def getAbsolutePath(self, attr):
        path = getattr(self, attr, None)
        if path is None:
            return None
        else:
            return self.container._getAbsolutePath(path)

    def _getAbsolutePath(self, path):
        if path.startswith('/'):
            return path
        elif "://" in path:
            return path
        else:
            return self.getAddress() + "/" + path

    def getParentPath(self, key):
        # Some containers have /children on its key while others (such as playlists) use /items
        path = self.getAbsolutePath(key)
        if path is None:
            return ""

        for suffix in ("/children", "/items"):
            path = path.replace(suffix, "")

        return path

    def getServer(self):
        return self.server

    def getTranscodeServer(self, localServerRequired=False, transcodeType=None):
        server = self.server

        # If the server is myPlex, try to use a different PMS for transcoding
        import myplexserver
        if server == myplexserver.MyPlexServer:
            fallbackServer = plexapp.SERVERMANAGER.getChannelServer()

            if fallbackServer:
                server = fallbackServer
            elif localServerRequired:
                return None

        return server

    @classmethod
    def deSerialize(cls, jstring):
        import plexserver
        obj = json.loads(jstring)
        server = plexserver.PlexServer.deSerialize(obj['server'])
        server.identifier = None
        ad = util.AttributeDict()
        ad.attrib = obj['obj']
        ad.find = lambda x: None
        po = buildItem(server, ad, ad.initpath, container=server)

        return po

    def serialize(self, full=False):
        import json
        odict = {}
        if full:
            for k, v in self.__dict__.items():
                if k not in ('server', 'container', 'media', 'initpath', '_data') and v:
                    odict[k] = v
        else:
            odict['key'] = self.key
            odict['type'] = self.type

        odict['initpath'] = '/none'
        obj = {'obj': odict, 'server': self.server.serialize(full=full)}

        return json.dumps(obj, cls=JEncoder)


class PlexContainer(PlexObject):
    def __init__(self, data, initpath=None, server=None, address=None):
        PlexObject.__init__(self, data, initpath, server)
        self.setAddress(address)

    def setAddress(self, address):
        if address != "/" and address.endswith("/"):
            self.address = address[:-1]
        else:
            self.address = address

        # TODO(schuyler): Do we need to make sure that we only hang onto the path here and not a full URL?
        if not self.address.startswith("/") and "node.plexapp.com" not in self.address:
            util.FATAL("Container address is not an expected path: {0}".format(address))

    def getAbsolutePath(self, path):
        if path.startswith('/'):
            return path
        elif "://" in path:
            return path
        else:
            return self.address + "/" + path


class PlexServerContainer(PlexContainer):
    def __init__(self, data, initpath=None, server=None, address=None):
        PlexContainer.__init__(self, data, initpath, server, address)
        import plexserver
        self.resources = [plexserver.PlexServer(elem) for elem in data]

    def __getitem__(self, idx):
        return self.resources[idx]

    def __iter__(self):
        for i in self.resources:
            yield i

    def __len__(self):
        return len(self.resources)


class PlexItemList(object):
    def __init__(self, data, item_cls, tag, server=None, container=None):
        self._data = data
        self._itemClass = item_cls
        self._itemTag = tag
        self._server = server
        self._container = container
        self._items = None

    def __iter__(self):
        for i in self.items:
            yield i

    def __getitem__(self, idx):
        return self.items[idx]

    @property
    def items(self):
        if self._items is None:
            if self._data is not None:
                if self._server:
                    self._items = [self._itemClass(elem, server=self._server, container=self._container) for elem in self._data if elem.tag == self._itemTag]
                else:
                    self._items = [self._itemClass(elem) for elem in self._data if elem.tag == self._itemTag]
            else:
                self._items = []

        return self._items

    def __call__(self, *args):
        return self.items

    def __len__(self):
        return len(self.items)

    def append(self, item):
        self.items.append(item)


class PlexMediaItemList(PlexItemList):
    def __init__(self, data, item_cls, tag, initpath=None, server=None, media=None):
        PlexItemList.__init__(self, data, item_cls, tag, server)
        self._initpath = initpath
        self._media = media
        self._items = None

    @property
    def items(self):
        if self._items is None:
            if self._data is not None:
                self._items = [self._itemClass(elem, self._initpath, self._server, self._media) for elem in self._data if elem.tag == self._itemTag]
            else:
                self._items = []

        return self._items


def findItem(server, path, title):
    for elem in server.query(path):
        if elem.attrib.get('title').lower() == title.lower():
            return buildItem(server, elem, path)
    raise exceptions.NotFound('Unable to find item: {0}'.format(title))


def buildItem(server, elem, initpath, bytag=False, container=None, tag_fallback=False):
    libtype = elem.tag if bytag else elem.attrib.get('type')
    if not libtype and tag_fallback:
        libtype = elem.tag

    if libtype in LIBRARY_TYPES:
        cls = LIBRARY_TYPES[libtype]
        return cls(elem, initpath=initpath, server=server, container=container)
    raise exceptions.UnknownType('Unknown library type: {0}'.format(libtype))


def listItems(server, path, libtype=None, watched=None, bytag=False, data=None, container=None):
    items = []
    data = data if data is not None else server.query(path)
    container = container or PlexContainer(data, path, server, path)
    for elem in data:
        if libtype and elem.attrib.get('type') != libtype:
            continue
        if watched is True and elem.attrib.get('viewCount', 0) == 0:
            continue
        if watched is False and elem.attrib.get('viewCount', 0) >= 1:
            continue
        try:
            items.append(buildItem(server, elem, path, bytag, container))
        except exceptions.UnknownType:
            pass

    return items


def searchType(libtype):
    searchtypesstrs = [str(k) for k in SEARCHTYPES.keys()]
    if libtype in SEARCHTYPES + searchtypesstrs:
        return libtype
    stype = SEARCHTYPES.get(libtype.lower())
    if not stype:
        raise exceptions.NotFound('Unknown libtype: %s' % libtype)
    return stype
