# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals

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

import binascii
import json
import threading

import xbmc

from . import connect
from . import player
from .client import get_device_id
from .objects import PlaylistWorker, on_play, on_update, special_listener
from .helper import translate, settings, window, dialog, api, JSONRPC
from .helper.utils import JsonDebugPrinter
from .jellyfin import Jellyfin
from .helper import LazyLogger

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

LOG = LazyLogger(__name__)

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


def order_from_start_index(items, item_ids, start_index):
    """Order a remote Play command's items to match the requested ItemIds,
    beginning at StartIndex.

    ``get_items`` (``/Items?Ids=``) does not preserve the requested order, and a
    remote Play command carries a ``StartIndex`` selecting which item to begin
    with. Reorder the fetched items to the requested ``item_ids`` sequence and
    drop everything before ``start_index`` so the chosen item plays first,
    rather than always playing the first item that was fetched.
    """
    by_id = {item["Id"]: item for item in items}
    ordered = [by_id[item_id] for item_id in item_ids if item_id in by_id]

    return ordered[start_index:]


class Monitor(xbmc.Monitor):

    servers = []
    sleep = False

    def __init__(self):

        self.player = player.Player()
        self.device_id = get_device_id()
        self.listener = Listener(self)
        self.listener.start()
        xbmc.Monitor.__init__(self)

    def onScanStarted(self, library):
        LOG.info("-->[ kodi scan/%s ]", library)

    def onScanFinished(self, library):
        LOG.info("--<[ kodi scan/%s ]", library)

    def onNotification(self, sender, method, data):

        if sender.lower() not in (
            "plugin.video.jellyfin",
            "xbmc",
            "upnextprovider.signal",
        ):
            return

        if sender == "plugin.video.jellyfin":
            method = method.split(".")[1]

            if method not in (
                "ReportProgressRequested",
                "LoadServer",
                "AddUser",
                "PlayPlaylist",
                "Play",
                "Playstate",
                "GeneralCommand",
            ):
                return

            data = json.loads(data)[0]

        elif sender.startswith("upnextprovider"):
            LOG.info("Attempting to play the next episode via upnext")
            method = method.split(".", 1)[1]

            if method not in ("plugin.video.jellyfin_play_action",):
                LOG.info("Received invalid upnext method: %s", method)
                return

            data = json.loads(data)
            method = "Play"

            if data:
                data = json.loads(binascii.unhexlify(data[0]))
        else:
            if method not in (
                "Player.OnPlay",
                "VideoLibrary.OnUpdate",
                "Player.OnAVChange",
            ):

                """We have to clear the playlist if it was stopped before it has been played completely.
                Otherwise the next played item will be added the previous queue.
                """
                if method == "Player.OnStop":
                    xbmc.sleep(
                        3000
                    )  # let's wait for the player, so we don't clear the canceled playlist by mistake.

                    if xbmc.getCondVisibility(
                        "!Player.HasMedia + !Window.IsVisible(busydialog)"
                    ):

                        xbmc.executebuiltin("Playlist.Clear")
                        LOG.info("[ playlist ] cleared")

                return

            data = json.loads(data)

        LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data))

        if self.sleep:
            LOG.info("System.OnSleep detected, ignore monitor request.")

            return

        try:
            if not data.get("ServerId"):
                server = Jellyfin()
            else:
                if method != "LoadServer" and data["ServerId"] not in self.servers:

                    try:
                        connect.Connect().register(data["ServerId"])
                        self.server_instance(data["ServerId"])
                    except Exception as error:

                        LOG.exception(error)
                        dialog("ok", "{jellyfin}", translate(33142))

                        return

                server = Jellyfin(data["ServerId"])
        except Exception as error:
            LOG.exception(error)
            server = Jellyfin()

        server = server.get_client()

        if method == "Play":

            items = server.jellyfin.get_items(data["ItemIds"])
            # StartIndex is optional: music queues send it, but video casts omit
            # it (they send StartPositionTicks / stream indices instead). Absent
            # means start at the first item.
            items["Items"] = order_from_start_index(
                items["Items"], data["ItemIds"], data.get("StartIndex", 0)
            )

            PlaylistWorker(
                data.get("ServerId"),
                items,
                data["PlayCommand"] == "PlayNow",
                data.get("StartPositionTicks", 0),
                data.get("AudioStreamIndex"),
                data.get("SubtitleStreamIndex"),
            ).start()

        # TODO no clue if this is called by anything
        elif method == "PlayPlaylist":

            server.jellyfin.post_session(
                server.config.data["app.session"],
                "Playing",
                {
                    "PlayCommand": "PlayNow",
                    "ItemIds": data["Id"],
                    "StartPositionTicks": 0,
                },
            )

        elif method in ("ReportProgressRequested", "Player.OnAVChange"):
            self.player.report_playback(data.get("Report", True))

        elif method == "Playstate":
            self.playstate(data)

        elif method == "GeneralCommand":
            self.general_commands(data)

        elif method == "LoadServer":
            self.server_instance(data["ServerId"])

        elif method == "AddUser":
            server.jellyfin.session_add_user(
                server.config.data["app.session"], data["Id"], data["Add"]
            )
            self.additional_users(server)

        elif method == "Player.OnPlay":
            on_play(data, server)

        elif method == "VideoLibrary.OnUpdate":
            on_update(data, server)

    def server_instance(self, server_id=None):

        server = Jellyfin(server_id).get_client()
        session = server.jellyfin.get_device(self.device_id)
        if not session:
            LOG.info(
                "No session yet for device %s; skipping server_instance setup",
                self.device_id,
            )
            return
        server.config.data["app.session"] = session[0]["Id"]

        if server_id is not None:
            self.servers.append(server_id)
        elif settings("additionalUsers"):

            users = settings("additionalUsers").split(",")
            all_users = server.jellyfin.get_users()

            for additional in users:
                for user in all_users:

                    if user["Name"].lower() in additional.lower():
                        server.jellyfin.session_add_user(
                            server.config.data["app.session"], user["Id"], True
                        )

            self.additional_users(server)

    def additional_users(self, server):
        """Setup additional users images."""
        for i in range(10):
            window("JellyfinAdditionalUserImage.%s" % i, clear=True)

        try:
            session = server.jellyfin.get_device(self.device_id)
        except Exception as error:
            LOG.exception(error)

            return

        for index, user in enumerate(session[0]["AdditionalUsers"]):

            info = server.jellyfin.get_user(user["UserId"])
            image = api.API(info, server.config.data["auth.server"]).get_user_artwork(
                user["UserId"]
            )
            window("JellyfinAdditionalUserImage.%s" % index, image)
            window("JellyfinAdditionalUserPosition.%s" % user["UserId"], str(index))

    def playstate(self, data):
        """Jellyfin playstate updates."""
        command = data["Command"]
        actions = {
            "Stop": self.player.stop,
            "Unpause": self.player.pause,
            "Pause": self.player.pause,
            "PlayPause": self.player.pause,
            "NextTrack": self.player.playnext,
            "PreviousTrack": self.player.playprevious,
        }
        if command == "Seek":

            if self.player.isPlaying():

                seektime = data["SeekPositionTicks"] / 10000000.0
                self.player.seekTime(seektime)
                LOG.info("[ seek/%s ]", seektime)

        elif command in actions:

            actions[command]()
            LOG.info("[ command/%s ]", command)

    def general_commands(self, data):
        """General commands from Jellyfin to control the Kodi interface."""
        command = data["Name"]
        args = data["Arguments"]

        if command in (
            "Mute",
            "Unmute",
            "SetVolume",
            "SetSubtitleStreamIndex",
            "SetAudioStreamIndex",
            "SetRepeatMode",
        ):

            if command in ["Mute", "Unmute"]:
                xbmc.executebuiltin("Mute")
            elif command == "SetAudioStreamIndex":
                self.player.set_audio_subs(args["Index"])
            elif command == "SetRepeatMode":
                xbmc.executebuiltin("xbmc.PlayerControl(%s)" % args["RepeatMode"])
            elif command == "SetSubtitleStreamIndex":
                self.player.set_audio_subs(None, args["Index"])

            elif command == "SetVolume":
                xbmc.executebuiltin("SetVolume(%s[,showvolumebar])" % args["Volume"])
            # Kodi needs a bit of time to update its current status
            xbmc.sleep(500)
            self.player.report_playback()

        elif command == "DisplayMessage":
            dialog(
                "notification",
                heading=args["Header"],
                message=args["Text"],
                icon="{jellyfin}",
                time=int(settings("displayMessage")) * 1000,
            )

        elif command == "SendString":
            JSONRPC("Input.SendText").execute({"text": args["String"], "done": False})

        elif command == "GoHome":
            JSONRPC("GUI.ActivateWindow").execute({"window": "home"})

        elif command == "Guide":
            JSONRPC("GUI.ActivateWindow").execute({"window": "tvguide"})

        elif command in ("MoveUp", "MoveDown", "MoveRight", "MoveLeft"):
            actions = {
                "MoveUp": "Input.Up",
                "MoveDown": "Input.Down",
                "MoveRight": "Input.Right",
                "MoveLeft": "Input.Left",
            }
            JSONRPC(actions[command]).execute()

        else:
            builtin = {
                "ToggleFullscreen": "Action(FullScreen)",
                "ToggleOsdMenu": "Action(OSD)",
                "ToggleContextMenu": "Action(ContextMenu)",
                "Select": "Action(Select)",
                "Back": "Action(back)",
                "PageUp": "Action(PageUp)",
                "NextLetter": "Action(NextLetter)",
                "GoToSearch": "VideoLibrary.Search",
                "GoToSettings": "ActivateWindow(Settings)",
                "PageDown": "Action(PageDown)",
                "PreviousLetter": "Action(PrevLetter)",
                "TakeScreenshot": "TakeScreenshot",
                "ToggleMute": "Mute",
                "VolumeUp": "Action(VolumeUp)",
                "VolumeDown": "Action(VolumeDown)",
            }
            if command in builtin:
                xbmc.executebuiltin(builtin[command])


class Listener(threading.Thread):

    stop_thread = False

    def __init__(self, monitor):
        self.monitor = monitor

        threading.Thread.__init__(self)

    def run(self):
        """Detect the resume dialog for widgets.
        Detect external players.
        """
        LOG.info("--->[ listener ]")

        while not self.stop_thread:
            special_listener()

            if self.monitor.waitForAbort(0.5):
                # Abort was requested while waiting. We should exit
                break

        LOG.info("---<[ listener ]")

    def stop(self):
        self.stop_thread = True
