# -*- coding: utf-8 -*-
import base64
import os
import re
import time
import bjsonrpc
import six
from bjsonrpc.handlers import BaseHandler

from kodi_six import xbmc, xbmcgui, xbmcvfs
from six.moves import urllib_request
from kodi_six.utils import py2_encode

from elementum.addon import ADDON, ADDON_ID, ADDON_PATH
from elementum.logger import log
from elementum.config import JSONRPC_EXT_PORT, ELEMENTUMD_HOST
from elementum.osarch import PLATFORM
from elementum.util import toUtf8, notify, getLocalizedString, getLocalizedLabel, getLocalizedText, system_information, translatePath
from elementum.overlay import OverlayText
from elementum.dialog_select import DialogSelect
from elementum.dialog_insert import DialogInsert
from elementum.kodiutils import upnext_signal, to_unicode

XBMC_PLAYER = xbmc.Player()

def makeMessage(*args, **kwargs):
    heading = 'Elementum'
    message = ''
    for x, value in enumerate(args):
        if x == 0:
            heading = value
            continue
        if x == 1:
            message = value
            continue
        message += '\n' + value
    for key, value in kwargs.items():
        message += '\n' + value
    return heading, message

class ElementumRPCServer(BaseHandler):
    public_methods_pattern = r'^[A-Za-z]\w+$'

    _objects = {}
    _failures = {}

    def Reset(self):
        for i in self._objects:
            try:
                self._objects[i].hide()
            except:
                pass
        log.info("Resetting RPC objects...")
        self._objects = {}

    def Ping(self):
        return True

    def Refresh(self):
        return xbmc.executebuiltin("Container.Refresh")

    def Update(self, url):
        if not self.Wait_Window_Loaded():
            return

        return xbmc.executebuiltin("Container.Update(%s)" % url)

    def InstallAddon(self, addonId):
# BEGIN ElementumService-patched: install
#        return xbmc.executebuiltin("InstallAddon(%s)" % addonId)
        if addonId == 'repository.elementumorg':
            import zipfile
            from io import BytesIO
            from elementum.addon import installAddon, recvApp, retryStartApp
            addonZip = recvApp(addonId, ['-addonInfo=' + addonId])
            if addonZip != b'':
                try:
                    with zipfile.ZipFile(BytesIO(addonZip), 'r') as zip:
                        zip.extractall(os.path.join(ADDON_PATH, '..'))
                    installAddon(addonId)
                except Exception:
                    pass
            retryStartApp()
            return None
        return xbmc.executebuiltin('InstallAddon(%s)' % addonId)
# END ElementumService-patched: install

    def UpdateLocalAddons(self):
        return xbmc.executebuiltin("UpdateLocalAddons")

    def UpdateAddonRepos(self):
        return xbmc.executebuiltin("UpdateAddonRepos")

    def SystemInformation(self):
        return system_information()

    def Notify(self, header, message, image):
        return notify(getLocalizedLabel(message), header, 3000, image)

    def Keyboard(self, default="", heading="", hidden=False):
        keyboard = xbmc.Keyboard(default, getLocalizedLabel(heading), hidden)
        keyboard.doModal()
        if keyboard.isConfirmed():
            return keyboard.getText()

    def Translate(self, text):
        return to_unicode(getLocalizedLabel(to_unicode(text)))

    def TranslateText(self, text):
        return to_unicode(getLocalizedText(to_unicode(text)))

    def Dialog(self, title, message):
        dialog = xbmcgui.Dialog()
        return int(dialog.ok(getLocalizedLabel(title), getLocalizedLabel(message)))

    def Dialog_Browse_Single(self, type, heading, shares, mask, useThumbs, treatAsFolder, defaultt):
        fn = six.ensure_text(xbmcgui.Dialog().browseSingle(type, getLocalizedLabel(heading), shares, mask, useThumbs, treatAsFolder, defaultt), errors='ignore')
        log.debug("Dialog_Browse_Single got file %s", fn)

        return fn

    def Dialog_Confirm(self, title, message):
        dialog = xbmcgui.Dialog()
        return int(dialog.yesno(getLocalizedLabel(title), getLocalizedLabel(message)))

    def Dialog_Confirm_With_Timeout(self, title, message, focused, timeout):
        dialog = xbmcgui.Dialog()

        yes = getLocalizedString(30413)
        no = getLocalizedString(30414)

        if timeout > 0 and timeout < 1200:
            if focused:
                yes = getLocalizedLabel("LOCALIZE[30632];;%d" % timeout)
            else:
                no = getLocalizedLabel("LOCALIZE[30633];;%d" % timeout)

        return int(dialog.yesno(getLocalizedLabel(title), getLocalizedLabel(message), nolabel=no, yeslabel=yes))

    def Dialog_Select(self, title, items):
        dialog = xbmcgui.Dialog()
        return dialog.select(getLocalizedLabel(title), items)

    def Dialog_Select_With_Options(self, title, items, autoclose, preselect):
        dialog = xbmcgui.Dialog()
        return dialog.select(getLocalizedLabel(title), items, autoclose, preselect)

    def Dialog_Select_Large(self, title, subject, items):
        title_encoded = "%s %s" % (getLocalizedLabel(title), toUtf8(subject))

        # For Kodi <= 16
        if PLATFORM['kodi'] <= 16:
            window = DialogSelect("DialogSelectLargeLegacy.xml",
                                  ADDON_PATH,
                                  "Default",
                                  title=title_encoded,
                                  items=items)
        # For Kodi >= 17
        else:
            window = DialogSelect("DialogSelectLarge.xml",
                                  ADDON_PATH,
                                  "Default",
                                  title=title_encoded,
                                  items=items)
        window.doModal()
        retval = window.retval
        del window

        return retval

    def Dialog_Text(self, title, text):
        title_encoded = getLocalizedLabel(title)
        text_encoded = getLocalizedText(text)

        # textviewer WINDOW_DIALOG_TEXT_VIEWER
        id = 10147
        xbmc.executebuiltin('ActivateWindow(%d)' % id)
        xbmc.sleep(500)
        win = xbmcgui.Window(id)
        retry = 50
        while (retry > 0):
            try:
                xbmc.sleep(10)
                retry -= 1
                win.getControl(1).setLabel(title_encoded)
                win.getControl(5).setText(text_encoded)
                quit()
                return
            except:
                return

    def Window_Is_Loading(self):
        return int(xbmc.getCondVisibility('Window.IsVisible(10138)') or xbmc.getCondVisibility('Window.IsActive(10138)') or xbmc.getCondVisibility('Window.IsVisible(10160)') or xbmc.getCondVisibility('Window.IsActive(10160)'))

    def OpenDirectory(self, path):
        xbmc.executebuiltin('ActivateWindow(Videos,%s)' % path)

    def Wait_Window_Loaded(self):
        tries = 9
        while self.Window_Is_Loading():
            if tries == 0:
                log.error("Failed to wait till window has finished loading")
                return int(False)

            tries = tries - 1
            time.sleep(0.3)

        log.debug("Finished waiting till window has finished loading")
        return int(True)

    def Playlist_Left(self):
        playlistId = xbmc.PLAYLIST_VIDEO
        if xbmc.PlayList(1).size() > 0:
            playlistId = 1
        elif xbmc.PlayList(0).size() > 0:
            playlistId = 0

        pl = xbmc.PlayList(playlistId)
        if pl.size() == 0:
            return 0
        elif pl.getposition() <= 0:
            return 0
        else:
            return pl.size() - pl.getposition()

    def Playlist_Size(self):
        playlistId = xbmc.PLAYLIST_VIDEO
        if xbmc.PlayList(1).size() > 0:
            playlistId = 1
        elif xbmc.PlayList(0).size() > 0:
            playlistId = 0

        pl = xbmc.PlayList(playlistId)
        return pl.size()

    def Playlist_Add(self, url, item):
        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
        # playlist.clear()
        return playlist.add(url=url, listitem=item)

    def Playlist_Clear(self):
        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
        return playlist.clear()

    def Player_Open(self, item):
        return xbmc.Player().play(item)

    def Player_Open_With_Labels(self, url, infolabels):
        if not self.Wait_Window_Loaded():
            return

        return
        # labels = InfoLabels(infolabels)
        # item = xbmcgui.ListItem(
        #     path=url,
        #     label=labels["label"],
        #     label2=labels["label2"],
        #     thumbnailImage=labels["thumbnail"])

        # item.setArt({
        #     "poster": labels["artposter"],
        #     "banner": labels["artbanner"],
        #     "fanart": labels["artfanart"]
        # })

        # item.setInfo(type='Video', infoLabels=labels)

        # return xbmc.Player().play(url, listitem=item)

    def Player_Open_With_Timeout(self, item):
        if not self.Wait_Window_Loaded():
            return

        return self.Player_Open(item)

    def Player_GetPlayingFile(self, *args, **kwargs):
        return XBMC_PLAYER.getPlayingFile()

    def Player_IsPlaying(self, *args, **kwargs):
        return int(XBMC_PLAYER.isPlaying(*args, **kwargs))

    def Player_IsPaused(self):
        return int(xbmc.getCondVisibility("Player.Paused"))

    def Player_WatchTimes(self):
        error = ""
        watchedTime = "0"
        videoDuration = "0"
        try:
            watchedTime = str(XBMC_PLAYER.getTime())
            videoDuration = str(XBMC_PLAYER.getTotalTime())
            log.debug("Watched: %s, duration: %s" % (watchedTime, videoDuration))
        except Exception as e:
            error = "Stopped playing: %s" % repr(e)

        watchTimes = {
            "watchedTime": watchedTime,
            "videoDuration": videoDuration,
            "error": error
        }
        return watchTimes

    def Player_Seek(self, position):
        ret = ""
        try:
            XBMC_PLAYER.seekTime(position)
        except Exception as e:
            ret = repr(e)
        return ret

    def Player_GetSubtitles(self):
        try:
            return XBMC_PLAYER.getAvailableSubtitleStreams()
        except:
            pass

        return None

    def Player_SetSubtitles(self, urls):
        enabled = xbmc.getCondVisibility('VideoPlayer.SubtitlesEnabled')

        for s in urls:
            XBMC_PLAYER.setSubtitles(s)
        if not enabled:
            XBMC_PLAYER.showSubtitles(False)

        return

    def ConvertLanguage(self, *args, **kwargs):
        return xbmc.convertLanguage(*args, **kwargs)

    def GetPlatform(self):
        return PLATFORM

    def GetAddonInfo(self):
        info = {}
        for key in ("author", "changelog", "description", "disclaimer",
                    "fanart", "icon", "id", "name", "profile", "stars",
                    "summary", "type", "version"):
            info[key] = ADDON.getAddonInfo(key)
        info['path'] = ADDON_PATH
        info['home'] = "special://home"
        info['xbmc'] = "special://xbmc"
# BEGIN ElementumService-patched: info
#        return info
        from elementum.addon import getGlobals
        globals = getGlobals()
        result = {}
        homeDir = translatePath('special://home')
        homeDirReplacement = globals['special://home']
        xbmcDir = translatePath('special://xbmc')
        xbmcDirReplacement = globals['special://xbmc']
        for key, value in info.items():
            if value.startswith(homeDir):
                result[key] = value.replace(homeDir, homeDirReplacement, 1)
            elif value.startswith(xbmcDir):
                result[key] = value.replace(xbmcDir, xbmcDirReplacement, 1)
            else:
                result[key] = value
        return result
# END ElementumService-patched: info

    def AddonFailure(self, addonId):
        if ADDON.getSetting("provider_disable_failing") == u"false":
            return 0

        if addonId in self._failures:
            self._failures[addonId] += 1
        else:
            self._failures[addonId] = 1

        log.warning("Recorded failure %d for %s" % (self._failures[addonId], addonId))

        if self._failures[addonId] > int(ADDON.getSetting("provider_allowed_failures")):
            try:
                time.sleep(10)
                notify(getLocalizedString(30111))
                urllib_request.urlopen("%s/provider/%s/disable" % (ELEMENTUMD_HOST, addonId))
            except:
                notify(getLocalizedString(30112))
                return 0
        return self._failures[addonId]

    def AddonCheck(self, addonId):
        if addonId in self._failures:
            return self._failures[addonId]
        return 0

    def AddonSettings(self, addonId):
        return xbmc.executebuiltin("Addon.OpenSettings(%s)" % addonId)

    def AddonSettingsOpened(self):
        return int(xbmc.getCondVisibility('Window.IsVisible(10140)') or xbmc.getCondVisibility('Window.IsActive(10140)') or xbmc.getCondVisibility('Window.IsVisible(12002)'))

    def GetLanguage(self, *args, **kwargs):
        return xbmc.getLanguage(*args, **kwargs)

    def GetLocalizedString(self, *args, **kwargs):
        return py2_encode(ADDON.getLocalizedString(*args, **kwargs), 'utf-8', 'ignore')

    def GetSetting(self, *args, **kwargs):
        return ADDON.getSetting(*args, **kwargs)

    def GetAllSettings(self):
        settings = []
        settingsFile = os.path.join(ADDON_PATH, "resources", "settings.xml")
        with open(settingsFile, 'r') as settingsStr:
            fileContent = settingsStr.read()
            keyType = re.findall(r".*id=\"(\w+)\".*type=\"(\w+)\"(.*option=\"(\w+)\")?", fileContent)
        for key, _type, optgroup, option in keyType:
            settings.append({
                "key": key,
                "type": _type,
                "option": option,
                "value": ADDON.getSetting(key)
            })
        return settings

    def SetSetting(self, *args, **kwargs):
        return ADDON.setSetting(*args, **kwargs)

    def GetWindowProperty(self, k):
        window = xbmcgui.Window(10000)
        return window.getProperty(k)

    def SetWindowProperty(self, k, v):
        window = xbmcgui.Window(10000)
        window.setProperty(k, v if v else "")

    def GetCurrentView(self):
        skinPath = translatePath('special://skin/')
        xml = os.path.join(skinPath, 'addon.xml')
        f = xbmcvfs.File(xml)
        read = f.read()
        f.close()
        try:
            src = re.search('defaultresolution="([^"]+)', read, re.DOTALL).group(1)
        except:
            src = re.search('<res.+?folder="([^"]+)', read, re.DOTALL).group(1)
        src = os.path.join(skinPath, src, 'MyVideoNav.xml')
        f = xbmcvfs.File(src)
        read = f.read()
        f.close()
        match = re.search('<views>([^<]+)', read, re.DOTALL)
        if match:
            views = match.group(1)
            log.info("Skin's ViewModes: %s" % views)
            for view in views.split(','):
                if xbmc.getInfoLabel('Control.GetLabel(%s)' % view):
                    return view

    def TranslatePath(self, *args, **kwargs):
# BEGIN ElementumService-patched: translate
#        return translatePath(*args, **kwargs)
        from elementum.addon import getGlobals
        result = translatePath(*args, **kwargs)
        homeDir = translatePath('special://home')
        if result.startswith(homeDir):
            return result.replace(homeDir, getGlobals()['special://home'], 1)
        xbmcDir = translatePath('special://xbmc')
        if result.startswith(xbmcDir):
            return result.replace(xbmcDir, getGlobals()['special://xbmc'], 1)
        return result
# END ElementumService-patched: translate

    def Log(self, *args, **kwargs):
        return xbmc.log(*args, **kwargs)

    def Dialog_CloseAll(self, *args, **kwargs):
        return xbmc.executebuiltin("Dialog.Close(all, true)")

    def Dialog_CloseAllConfirms(self, *args, **kwargs):
        return xbmc.executebuiltin("Dialog.Close(yesnodialog, true)")

    def DialogInsert(self, *args, **kwargs):
        if PLATFORM['kodi'] <= 16:
            window = DialogInsert("DialogInsertLegacy.xml", ADDON_PATH, "Default")
        else:
            window = DialogInsert("DialogInsert.xml", ADDON_PATH, "Default")
        window.doModal()
        retval = {"type": ("cancelled", "url", "file")[window.type], "path": window.retval}
        del window
        return retval

    ###########################################################################
    # DialogProgress
    ###########################################################################
    def DialogProgress_Create(self, *args, **kwargs):
        dialog = xbmcgui.DialogProgress()
        window_id = str(id(dialog))
        self._objects[window_id] = dialog
        if PLATFORM['kodi'] < 19:
            dialog.create(*args, **kwargs)
        else:
            heading, message = makeMessage(*args, **kwargs)
            dialog.create(heading, message)
        return window_id

    def DialogProgress_IsCanceled(self, hwnd, *args, **kwargs):
        if PLATFORM['kodi'] < 19:
            return int(self._objects[hwnd].iscanceled(*args, **kwargs))
        else:
            heading, message = makeMessage(*args, **kwargs)
            return int(self._objects[hwnd].iscanceled(heading, message))

    def DialogProgress_Update(self, hwnd, *args, **kwargs):
        if PLATFORM['kodi'] < 19:
            return self._objects[hwnd].update(*args, **kwargs)
        else:
            heading, message = makeMessage(*args, **kwargs)
            return self._objects[hwnd].update(heading, message)

    def DialogProgress_Close(self, hwnd, *args, **kwargs):
        dialog = self._objects.pop(hwnd)
        dialog.close()
        del dialog

    # Background DialogProgress
    def DialogProgressBG_Create(self, title, message, *args):
        dialog = xbmcgui.DialogProgressBG()
        window_id = str(id(dialog))
        self._objects[window_id] = dialog
        if args and isinstance(args[0], list):
            self._objects["%s-i18n" % window_id] = {}
            for translation in args[0]:
                self._objects["%s-i18n" % window_id][translation] = getLocalizedLabel(translation)
        dialog.create(title, getLocalizedLabel(message))
        return window_id

    def DialogProgressBG_IsFinished(self, hwnd):
        return int(self._objects[hwnd].isFinished())

    def DialogProgressBG_Update(self, hwnd, percent, heading, message):
        if message.startswith("LOCALIZE"):
            message = self._objects["%s-i18n" % hwnd][message]
        return self._objects[hwnd].update(percent, heading, message)

    def DialogProgressBG_Close(self, hwnd):
        dialog = self._objects.pop(hwnd)
        if "%s-i18n" % hwnd in self._objects:
            self._objects.pop("%s-i18n" % hwnd)
        dialog.close()
        del dialog

    def DialogProgressBG_Cleanup(self):
        log.info("Cleaning up dialogs")
        for hwnd in self._objects:
            if isinstance(self._objects[hwnd], xbmcgui.DialogProgressBG):
                self._objects[hwnd].close()

    # Overlay status
    def OverlayStatus_Create(self):
        overlay = OverlayText()
        overlayId = str(id(overlay))
        self._objects[overlayId] = overlay
        return overlayId

    def OverlayStatus_Update(self, hwnd, percent, line1, line2, line3):
        text = "\n".join([line1, line2, line3])
        self._objects[hwnd].text = text

    def OverlayStatus_Show(self, hwnd):
        self._objects[hwnd].show()

    def OverlayStatus_Hide(self, hwnd):
        self._objects[hwnd].hide()

    def OverlayStatus_Close(self, hwnd):
        overlay = self._objects.pop(hwnd)
        overlay.hide()
        del overlay

    def UpNext_Notify(self, data):
        upnext_signal(ADDON_ID, to_unicode(data))

    def ToggleWatched(self):
        return xbmc.executebuiltin("Action(ToggleWatched)")

    def GetKodiLog(self):
        logPath = translatePath("special://logpath/kodi.log")
        if not os.path.exists(logPath):
            return

        try:
            with open(logPath, 'rb') as file:
                return base64.b64encode(file.read())
        except Exception as e:
            log.info("RPC: Could not read Kodi log file: %s" % (e))

def server_thread():
    try:
        bjsonrpc.bjsonrpc_options['threaded'] = True
        s = bjsonrpc.createserver(host="0.0.0.0", port=JSONRPC_EXT_PORT, handler_factory=ElementumRPCServer)
        log.info("elementum: starting jsonrpc service")
        s.serve()
        log.info("elementum: exiting jsonrpc service")
    except Exception:
        import traceback
        map(log.error, traceback.format_exc().split("\n"))
        raise
