import logging
import re
import threading

import downloadutils
from utils import window
import PlexFunctions as pf
from functions import *

###############################################################################

log = logging.getLogger("PLEX."+__name__)

###############################################################################


class SubscriptionManager:
    def __init__(self, jsonClass, RequestMgr, player, playlist):
        self.serverlist = []
        self.subscribers = {}
        self.info = {}
        self.lastkey = ""
        self.containerKey = ""
        self.ratingkey = ""
        self.lastplayers = {}
        self.lastinfo = {
            'video': {},
            'audio': {},
            'picture': {}
        }
        self.volume = 0
        self.mute = '0'
        self.server = ""
        self.protocol = "http"
        self.port = ""
        self.playerprops = {}
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.xbmcplayer = player
        self.playlist = playlist

        self.js = jsonClass
        self.RequestMgr = RequestMgr

    def getServerByHost(self, host):
        if len(self.serverlist) == 1:
            return self.serverlist[0]
        for server in self.serverlist:
            if (server.get('serverName') in host or
                    server.get('server') in host):
                return server
        return {}

    def getVolume(self):
        self.volume, self.mute = self.js.getVolume()

    def msg(self, players):
        msg = getXMLHeader()
        msg += '<MediaContainer commandID="INSERTCOMMANDID"'
        if players:
            self.getVolume()
            maintype = plex_audio()
            for p in players.values():
                if p.get('type') == xbmc_video():
                    maintype = plex_video()
                elif p.get('type') == xbmc_photo():
                    maintype = plex_photo()
            self.mainlocation = "fullScreen" + maintype[0:1].upper() + maintype[1:].lower()
        else:
            self.mainlocation = "navigation"
        msg += ' location="%s">' % self.mainlocation
        msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
        msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
        msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
        msg += "\r\n</MediaContainer>"
        return msg

    def getTimelineXML(self, playerid, ptype):
        if playerid is not None:
            info = self.getPlayerProperties(playerid)
            # save this info off so the server update can use it too
            self.playerprops[playerid] = info;
            state = info['state']
            time = info['time']
        else:
            state = "stopped"
            time = 0
        ret = "\r\n"+'  <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
        if playerid is None:
            ret += ' seekRange="0-0"'
            ret += ' />'
            return ret

        # pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
        # userId = str(WINDOW.getProperty('currUserId'))
        pbmc_server = window('pms_server')
        if pbmc_server:
            (self.protocol, self.server, self.port) = \
                pbmc_server.split(':')
            self.server = self.server.replace('/', '')
        keyid = None
        count = 0
        while not keyid:
            if count > 300:
                break
            keyid = window('plex_currently_playing_itemid')
            xbmc.sleep(100)
            count += 1
        if keyid:
            self.lastkey = "/library/metadata/%s" % keyid
            self.ratingkey = keyid
            ret += ' location="%s"' % self.mainlocation
            ret += ' key="%s"' % self.lastkey
            ret += ' ratingKey="%s"' % self.ratingkey
        serv = self.getServerByHost(self.server)
        if info.get('playQueueID'):
            self.containerKey = "/playQueues/%s" % info.get('playQueueID')
            ret += ' playQueueID="%s"' % info.get('playQueueID')
            ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
            ret += ' playQueueItemID="%s"' % info.get('playQueueItemID')
            ret += ' containerKey="%s"' % self.containerKey
            ret += ' guid="%s"' % info['guid']
        elif keyid:
            self.containerKey = self.lastkey
            ret += ' containerKey="%s"' % self.containerKey

        ret += ' duration="%s"' % info['duration']
        ret += ' seekRange="0-%s"' % info['duration']
        ret += ' controllable="%s"' % self.controllable()
        ret += ' machineIdentifier="%s"' % serv.get('uuid', "")
        ret += ' protocol="%s"' % serv.get('protocol', "http")
        ret += ' address="%s"' % serv.get('server', self.server)
        ret += ' port="%s"' % serv.get('port', self.port)
        ret += ' volume="%s"' % info['volume']
        ret += ' shuffle="%s"' % info['shuffle']
        ret += ' mute="%s"' % self.mute
        ret += ' repeat="%s"' % info['repeat']
        # Might need an update in the future
        ret += ' subtitleStreamID="-1"'
        ret += ' audioStreamID="-1"'

        ret += ' />'
        return ret

    def updateCommandID(self, uuid, commandID):
        if commandID and self.subscribers.get(uuid, False):
            self.subscribers[uuid].commandID = int(commandID)

    def notify(self, event=False):
        self.cleanup()
        # Don't tell anyone if we don't know a Plex ID and are still playing
        # (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
        if (not window('plex_currently_playing_itemid')
                and not self.lastplayers):
            return True
        players = self.js.getPlayers()
        # fetch the message, subscribers or not, since the server
        # will need the info anyway
        msg = self.msg(players)
        if self.subscribers:
            with threading.RLock():
                for sub in self.subscribers.values():
                    sub.send_update(msg, len(players) == 0)
        self.notifyServer(players)
        self.lastplayers = players
        return True

    def notifyServer(self, players):
        for typus, p in players.iteritems():
            info = self.playerprops[p.get('playerid')]
            self._sendNotification(info)
            self.lastinfo[typus] = info
            # Cross the one of the list
            try:
                del self.lastplayers[typus]
            except KeyError:
                pass
        # Process the players we have left (to signal a stop)
        for typus, p in self.lastplayers.iteritems():
            self.lastinfo[typus]['state'] = 'stopped'
            self._sendNotification(self.lastinfo[typus])

    def _sendNotification(self, info):
        params = {
            'containerKey': self.containerKey or "/library/metadata/900000",
            'key': self.lastkey or "/library/metadata/900000",
            'ratingKey': self.ratingkey or "900000",
            'state': info['state'],
            'time': info['time'],
            'duration': info['duration']
        }
        if info.get('playQueueID'):
            params['containerKey'] = '/playQueues/%s' % info['playQueueID']
            params['playQueueVersion'] = info['playQueueVersion']
            params['playQueueItemID'] = info['playQueueItemID']
        serv = self.getServerByHost(self.server)
        url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
                                         serv.get('server', 'localhost'),
                                         serv.get('port', '32400'))
        self.doUtils(url, parameters=params)
        log.debug("Sent server notification with parameters: %s to %s"
                  % (params, url))

    def controllable(self):
        return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"

    def addSubscriber(self, protocol, host, port, uuid, commandID):
        sub = Subscriber(protocol,
                         host,
                         port,
                         uuid,
                         commandID,
                         self,
                         self.RequestMgr)
        with threading.RLock():
            self.subscribers[sub.uuid] = sub
        return sub

    def removeSubscriber(self, uuid):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.uuid == uuid or sub.host == uuid:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]

    def cleanup(self):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.age > 30:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]

    def getPlayerProperties(self, playerid):
        try:
            # get info from the player
            props = self.js.jsonrpc(
                "Player.GetProperties",
                {"playerid": playerid,
                 "properties": ["time",
                                "totaltime",
                                "speed",
                                "shuffled",
                                "repeat"]})

            info = {
                'time': timeToMillis(props['time']),
                'duration': timeToMillis(props['totaltime']),
                'state': ("paused", "playing")[int(props['speed'])],
                'shuffle': ("0", "1")[props.get('shuffled', False)],
                'repeat': pf.getPlexRepeat(props.get('repeat')),
            }
            if self.playlist is not None:
                if self.playlist.QueueId() is not None:
                    info['playQueueID'] = self.playlist.QueueId()
                    info['playQueueVersion'] = self.playlist.PlayQueueVersion()
                    info['guid'] = self.playlist.Guid()
                    # Get the playlist position
                    pos = self.js.jsonrpc(
                        "Player.GetProperties",
                        {"playerid": playerid,
                         "properties": ["position"]})
                    info['playQueueItemID'] = \
                        self.playlist.getQueueIdFromPosition(pos['position'])
        except:
            import traceback
            log.error("Traceback:\n%s" % traceback.format_exc())
            info = {
                'time': 0,
                'duration': 0,
                'state': 'stopped',
                'shuffle': False,
                'repeat': 0
            }

        # get the volume from the application
        info['volume'] = self.volume
        info['mute'] = self.mute

        return info


class Subscriber:
    def __init__(self, protocol, host, port, uuid, commandID,
                 subMgr, RequestMgr):
        self.protocol = protocol or "http"
        self.host = host
        self.port = port or 32400
        self.uuid = uuid or host
        self.commandID = int(commandID) or 0
        self.navlocationsent = False
        self.age = 0
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.subMgr = subMgr
        self.RequestMgr = RequestMgr

    def __eq__(self, other):
        return self.uuid == other.uuid

    def tostr(self):
        return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)

    def cleanup(self):
        self.RequestMgr.closeConnection(self.protocol, self.host, self.port)

    def send_update(self, msg, is_nav):
        self.age += 1
        if not is_nav:
            self.navlocationsent = False
        elif self.navlocationsent:
            return True
        else:
            self.navlocationsent = True
        msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
        log.debug("sending xml to subscriber %s: %s" % (self.tostr(), msg))
        url = self.protocol + '://' + self.host + ':' + self.port \
            + "/:/timeline"
        t = threading.Thread(target=self.threadedSend, args=(url, msg))
        t.start()

    def threadedSend(self, url, msg):
        """
        Threaded POST request, because they stall due to PMS response missing
        the Content-Length header :-(
        """
        response = self.doUtils(url,
                                postBody=msg,
                                action_type="POST")
        if response in [False, None, 401]:
            self.subMgr.removeSubscriber(self.uuid)