#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Loads of different functions called in SEPARATE Python instances through
e.g. plugin://... calls. Hence be careful to only rely on window variables.
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import sys
import copy

import xbmc
import xbmcplugin
from xbmcgui import ListItem

from . import utils
from . import path_ops
from .downloadutils import DownloadUtils as DU
from .plex_api import API, mass_api
from . import plex_functions as PF
from . import variables as v
# Be careful - your using app in another Python instance!
from . import app, widgets
from .library_sync.nodes import NODE_TYPES


LOG = getLogger('PLEX.entrypoint')


def guess_video_or_audio():
    """
    Returns either 'video', 'audio' or 'image', based how the user navigated to
    the current view.
    Returns None if this failed, e.g. when the user picks widgets
    """
    content_type = None
    if xbmc.getCondVisibility('Window.IsActive(Videos)'):
        content_type = 'video'
    elif xbmc.getCondVisibility('Window.IsActive(Music)'):
        content_type = 'audio'
    elif xbmc.getCondVisibility('Window.IsActive(Pictures)'):
        content_type = 'image'
    elif xbmc.getCondVisibility('Container.Content(movies)'):
        content_type = 'video'
    elif xbmc.getCondVisibility('Container.Content(episodes)'):
        content_type = 'video'
    elif xbmc.getCondVisibility('Container.Content(seasons)'):
        content_type = 'video'
    elif xbmc.getCondVisibility('Container.Content(tvshows)'):
        content_type = 'video'
    elif xbmc.getCondVisibility('Container.Content(albums)'):
        content_type = 'audio'
    elif xbmc.getCondVisibility('Container.Content(artists)'):
        content_type = 'audio'
    elif xbmc.getCondVisibility('Container.Content(songs)'):
        content_type = 'audio'
    elif xbmc.getCondVisibility('Container.Content(pictures)'):
        content_type = 'image'
    LOG.debug('Guessed content type: %s', content_type)
    return content_type


def _wait_for_auth():
    """
    Call to be sure that PKC is authenticated, e.g. for widgets on Kodi startup.
    Will wait for at most 30s, then fail if not authenticated. Will set
    xbmcplugin.endOfDirectory(int(argv[1]), False) if failed

    WARNING - this will potentially stall the shutdown of Kodi since we cannot
    poll xbmc.Monitor().abortRequested() or waitForAbort()
    """
    counter = 0
    startupdelay = int(utils.settings('startupDelay') or 0)
    # Wait for <startupdelay in seconds> + 10 seconds at most
    startupdelay = 10 * startupdelay + 100
    while utils.window('plex_authenticated') != 'true':
        counter += 1
        if counter == startupdelay:
            LOG.error('Aborting view, we were not authenticated for PMS')
            xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
            return False
        xbmc.sleep(100)
    return True


def directory_item(label, path, folder=True):
    """
    Adds a xbmcplugin.addDirectoryItem() directory itemlistitem
    """
    listitem = ListItem(label, path=path)
    listitem.setThumbnailImage(
        "special://home/addons/plugin.video.plexkodiconnect/icon.png")
    listitem.setArt(
        {"fanart": "special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
    listitem.setArt(
        {"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                url=path,
                                listitem=listitem,
                                isFolder=folder)


def show_main_menu(content_type=None):
    """
    Shows the main PKC menu listing with all libraries, Channel, settings, etc.
    """
    content_type = content_type or guess_video_or_audio()
    LOG.debug('Do main listing for %s', content_type)
    xbmcplugin.setContent(int(sys.argv[1]), v.CONTENT_TYPE_FILE)
    # Get nodes from the window props
    totalnodes = int(utils.window('Plex.nodes.total') or 0)
    for i in range(totalnodes):
        path = utils.window('Plex.nodes.%s.index' % i)
        if not path:
            continue
        label = utils.window('Plex.nodes.%s.title' % i)
        node_type = utils.window('Plex.nodes.%s.type' % i)
        # because we do not use seperate entrypoints for each content type,
        # we need to figure out which items to show in each listing. for
        # now we just only show picture nodes in the picture library video
        # nodes in the video library and all nodes in any other window
        if node_type == v.CONTENT_TYPE_PHOTO and content_type == 'image':
            directory_item(label, path)
        elif node_type in (v.CONTENT_TYPE_ARTIST,
                           v.CONTENT_TYPE_ALBUM,
                           v.CONTENT_TYPE_SONG) and content_type == 'audio':
            directory_item(label, path)
        elif node_type in (v.CONTENT_TYPE_MOVIE,
                           v.CONTENT_TYPE_SHOW,
                           v.CONTENT_TYPE_MUSICVIDEO) and content_type == 'video':
            directory_item(label, path)
        elif content_type is None:
            # To let the user pick this node as a WIDGET (content_type is None)
            # Should only be called if the user selects widgets
            LOG.info('Detected user selecting widgets')
            directory_item(label, path)
    # Playlists
    if content_type != 'image':
        path = 'plugin://%s?mode=playlists' % v.ADDON_ID
        if content_type:
            path += '&content_type=%s' % content_type
        directory_item(utils.lang(136), path)
    # Plex Hub
    path = 'plugin://%s?mode=hub' % v.ADDON_ID
    if content_type:
        path += '&content_type=%s' % content_type
    directory_item('Plex Hub', path)
    # Plex Search "Search"
    directory_item(utils.lang(137), "plugin://%s?mode=search" % v.ADDON_ID)
    # Plex Watch later
    if content_type not in ('image', 'audio'):
        directory_item(utils.lang(39211),
                       "plugin://%s?mode=watchlater" % v.ADDON_ID)
    # Plex Channels
    directory_item(utils.lang(30173), "plugin://%s?mode=channels" % v.ADDON_ID)
    # Plex user switch
    directory_item('%s%s' % (utils.lang(39200), utils.settings('username')),
                   "plugin://%s?mode=switchuser" % v.ADDON_ID)

    # some extra entries for settings and stuff
    directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
    directory_item(utils.lang(39204),
                   "plugin://%s?mode=manualsync" % v.ADDON_ID)
    xbmcplugin.endOfDirectory(int(sys.argv[1]))


def show_section(section_index):
    """
    Displays menu for an entire Plex section. We're using add-on paths instead
    of Kodi video library xmls to be able to use type="filter" library xmls
    and thus set the "content"

    Only used for synched Plex sections - otherwise, PMS xml for the section
    is used directly
    """
    LOG.debug('Do section listing for section index %s', section_index)
    xbmcplugin.setContent(int(sys.argv[1]), v.CONTENT_TYPE_FILE)
    # Get nodes from the window props
    node = 'Plex.nodes.%s' % section_index
    content = utils.window('%s.type' % node)
    plex_type = v.PLEX_TYPE_MOVIE if content == v.CONTENT_TYPE_MOVIE \
        else v.PLEX_TYPE_SHOW
    for node_type, _, _, _, _ in NODE_TYPES[plex_type]:
        label = utils.window('%s.%s.title' % (node, node_type))
        path = utils.window('%s.%s.index' % (node, node_type))
        directory_item(label, path)
    xbmcplugin.endOfDirectory(int(sys.argv[1]))


def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None):
    """
    Pass synched=False if the items have not been synched to the Kodi DB

    Kodi content type will be set using the very first item returned by the PMS
    """
    try:
        xml[0]
    except IndexError:
        LOG.info('xml received from the PMS is empty: %s, %s',
                 xml.tag, xml.attrib)
        xbmcplugin.endOfDirectory(int(sys.argv[1]))
        return
    api = API(xml[0])
    # Determine content type for Kodi's Container.content
    if key == '/hubs/home/continueWatching':
        # Mix of movies and episodes
        plex_type = v.PLEX_TYPE_VIDEO
    elif key == '/hubs/home/recentlyAdded?type=2':
        # "Recently Added TV", potentially a mix of Seasons and Episodes
        plex_type = v.PLEX_TYPE_VIDEO
    elif api.plex_type is None and api.fast_key and '?collection=' in api.fast_key:
        # Collections/Kodi sets
        plex_type = v.PLEX_TYPE_SET
    elif api.plex_type is None and plex_type:
        # e.g. browse by folder - folders will be listed first
        # Retain plex_type
        pass
    else:
        plex_type = api.plex_type
    content_type = v.CONTENT_FROM_PLEX_TYPE[plex_type]
    LOG.debug('show_listing: section_id %s, synched %s, key %s, plex_type %s, '
              'content type %s',
              section_id, synched, key, plex_type, content_type)
    xbmcplugin.setContent(int(sys.argv[1]), content_type)
    # Initialization
    widgets.PLEX_TYPE = plex_type
    widgets.SYNCHED = synched
    if plex_type == v.PLEX_TYPE_EPISODE and key and 'onDeck' in key:
        widgets.APPEND_SHOW_TITLE = utils.settings('OnDeckTvAppendShow') == 'true'
        widgets.APPEND_SXXEXX = utils.settings('OnDeckTvAppendSeason') == 'true'
    if plex_type == v.PLEX_TYPE_EPISODE and key and 'recentlyAdded' in key:
        widgets.APPEND_SHOW_TITLE = utils.settings('RecentTvAppendShow') == 'true'
        widgets.APPEND_SXXEXX = utils.settings('RecentTvAppendSeason') == 'true'
    if api.tag == 'Playlist':
        # Only show video playlists if navigation started for videos
        # and vice-versa for audio playlists
        content = guess_video_or_audio()
        if content:
            for entry in reversed(xml):
                tmp_api = API(entry)
                if tmp_api.playlist_type() != content:
                    xml.remove(entry)
    if xml.get('librarySectionID'):
        widgets.SECTION_ID = utils.cast(int, xml.get('librarySectionID'))
    elif section_id:
        widgets.SECTION_ID = utils.cast(int, section_id)
    if xml.get('viewGroup') == 'secondary':
        # Need to chain keys for navigation
        widgets.KEY = key
    # Process all items to show
    all_items = mass_api(xml)
    all_items = utils.process_method_on_list(widgets.generate_item, all_items)
    all_items = utils.process_method_on_list(widgets.prepare_listitem,
                                             all_items)
    # fill that listing...
    all_items = utils.process_method_on_list(widgets.create_listitem,
                                             all_items)
    xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items))
    # end directory listing
    xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))


def get_video_files(plex_id, params):
    """
    GET VIDEO EXTRAS FOR LISTITEM

    returns the video files for the item as plugin listing, can be used for
    browsing the actual files or videoextras etc.
    """
    if plex_id is None:
        filename = params.get('filename')
        if filename is not None:
            filename = filename[0]
            import re
            regex = re.compile(r'''library/metadata/(\d+)''')
            filename = regex.findall(filename)
            try:
                plex_id = filename[0]
            except IndexError:
                pass

    if plex_id is None:
        LOG.info('No Plex ID found, abort getting Extras')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]))
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    app.init(entrypoint=True)
    item = PF.GetPlexMetadata(plex_id)
    try:
        path = utils.try_decode(item[0][0][0].attrib['file'])
    except (TypeError, IndexError, AttributeError, KeyError):
        LOG.error('Could not get file path for item %s', plex_id)
        return xbmcplugin.endOfDirectory(int(sys.argv[1]))
    # Assign network protocol
    if path.startswith('\\\\'):
        path = path.replace('\\\\', 'smb://')
        path = path.replace('\\', '/')
    # Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv'
    elif '\\' in path:
        path = path.replace('\\', '\\\\')
    # Directory only, get rid of filename
    path = path.replace(path_ops.path.basename(path), '')
    if path_ops.exists(path):
        for root, dirs, files in path_ops.walk(path):
            for directory in dirs:
                item_path = utils.try_encode(path_ops.path.join(root,
                                                                directory))
                listitem = ListItem(item_path, path=item_path)
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                            url=item_path,
                                            listitem=listitem,
                                            isFolder=True)
            for file in files:
                item_path = utils.try_encode(path_ops.path.join(root, file))
                listitem = ListItem(item_path, path=item_path)
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                            url=file,
                                            listitem=listitem)
            break
    else:
        LOG.error('Kodi cannot access folder %s', path)
    xbmcplugin.endOfDirectory(int(sys.argv[1]))


@utils.catch_exceptions(warnuser=False)
def extra_fanart(plex_id, plex_path):
    """
    Get extrafanart for listitem
    will be called by skinhelper script to get the extrafanart
    for tvshows we get the plex_id just from the path
    """
    LOG.debug('Called with plex_id: %s, plex_path: %s', plex_id, plex_path)
    if not plex_id:
        if "plugin.video.plexkodiconnect" in plex_path:
            plex_id = plex_path.split("/")[-2]
    if not plex_id:
        LOG.error('Could not get a plex_id, aborting')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]))

    # We need to store the images locally for this to work
    # because of the caching system in xbmc
    fanart_dir = path_ops.translate_path("special://thumbnails/plex/%s/"
                                         % plex_id)
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    if not path_ops.exists(fanart_dir):
        # Download the images to the cache directory
        path_ops.makedirs(fanart_dir)
        app.init(entrypoint=True)
        xml = PF.GetPlexMetadata(plex_id)
        if xml is None:
            LOG.error('Could not download metadata for %s', plex_id)
            return xbmcplugin.endOfDirectory(int(sys.argv[1]))

        api = API(xml[0])
        backdrops = api.artwork()['Backdrop']
        for count, backdrop in enumerate(backdrops):
            # Same ordering as in artwork
            art_file = utils.try_encode(path_ops.path.join(
                fanart_dir, "fanart%.3d.jpg" % count))
            listitem = ListItem("%.3d" % count, path=art_file)
            xbmcplugin.addDirectoryItem(
                handle=int(sys.argv[1]),
                url=art_file,
                listitem=listitem)
            path_ops.copyfile(backdrop, utils.try_decode(art_file))
    else:
        LOG.info("Found cached backdrop.")
        # Use existing cached images
        fanart_dir = utils.try_decode(fanart_dir)
        for root, _, files in path_ops.walk(fanart_dir):
            root = utils.decode_path(root)
            for file in files:
                file = utils.decode_path(file)
                art_file = utils.try_encode(path_ops.path.join(root, file))
                listitem = ListItem(file, path=art_file)
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                            url=art_file,
                                            listitem=listitem)
    xbmcplugin.endOfDirectory(int(sys.argv[1]))


def playlists(content_type):
    """
    Lists all Plex playlists of the media type plex_playlist_type
    content_type: 'audio', 'video'
    """
    LOG.debug('Listing Plex playlists for content type %s', content_type)
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    app.init(entrypoint=True)
    from .playlists.pms import all_playlists
    xml = all_playlists()
    if xml is None:
        return xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
    if content_type is not None:
        # This will be skipped if user selects a widget
        # Buggy xml.remove(child) requires reversed()
        for entry in reversed(xml):
            api = API(entry)
            if not api.playlist_type() == content_type:
                xml.remove(entry)
    show_listing(xml)


def hub(content_type):
    """
    Plus hub endpoint pms:port/hubs. Need to separate Kodi types with
    content_type:
        audio, video, image
    """
    content_type = content_type or guess_video_or_audio()
    LOG.debug('Showing Plex Hub entries for %s', content_type)
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    app.init(entrypoint=True)
    xml = PF.get_plex_hub()
    try:
        xml.attrib
    except AttributeError:
        LOG.error('Could not get Plex hub listing')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    # We need to make sure that only entries that WORK are displayed
    # WARNING: using xml.remove(child) in for-loop requires traversing from
    # the end!
    pkc_cont_watching = None
    for entry in reversed(xml):
        api = API(entry)
        append = False
        if content_type == 'video' and api.plex_type in v.PLEX_VIDEOTYPES:
            append = True
        elif content_type == 'audio' and api.plex_type in v.PLEX_AUDIOTYPES:
            append = True
        elif content_type == 'image' and api.plex_type == v.PLEX_TYPE_PHOTO:
            append = True
        elif content_type != 'image' and api.plex_type == v.PLEX_TYPE_PLAYLIST:
            append = True
        elif content_type is None:
            # Needed for widgets, where no content_type is provided
            append = True
        if not append:
            xml.remove(entry)

        # HACK ##################
        # Merge Plex's "Continue watching" with "On deck"
        if entry.get('key') == '/hubs/home/continueWatching':
            pkc_cont_watching = copy.deepcopy(entry)
            pkc_cont_watching.set('key', '/hubs/continueWatching')
            title = pkc_cont_watching.get('title') or 'Continue Watching'
            pkc_cont_watching.set('title', 'PKC %s' % title)
    if pkc_cont_watching:
        for i, entry in enumerate(xml):
            if entry.get('key') == '/hubs/home/continueWatching':
                xml.insert(i + 1, pkc_cont_watching)
                break
    # END HACK ##################

    show_listing(xml)


def watchlater():
    """
    Listing for plex.tv Watch Later section (if signed in to plex.tv)
    """
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    if utils.window('plex_token') == '':
        LOG.error('No watch later - not signed in to plex.tv')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    if utils.window('plex_restricteduser') == 'true':
        LOG.error('No watch later - restricted user')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)

    app.init(entrypoint=True)
    xml = DU().downloadUrl('https://plex.tv/pms/playlists/queue/all',
                           authenticate=False,
                           headerOptions={'X-Plex-Token': utils.window('plex_token')})
    if xml in (None, 401):
        LOG.error('Could not download watch later list from plex.tv')
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    show_listing(xml)


def browse_plex(key=None, plex_type=None, section_id=None, synched=True,
                args=None, prompt=None, query=None):
    """
    Lists the content of a Plex folder, e.g. channels. Either pass in key (to
    be used directly for PMS url {server}<key>) or the section_id

    Pass synched=False if the items have NOT been synched to the Kodi DB
    """
    LOG.debug('Browsing to key %s, section %s, plex_type: %s, synched: %s, '
              'prompt "%s", args %s', key, section_id, plex_type, synched,
              prompt, args)
    if not _wait_for_auth():
        xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
        return
    app.init(entrypoint=True)
    args = args or {}
    if query:
        args['query'] = query
    elif prompt:
        prompt = utils.dialog('input', prompt)
        if prompt is None:
            # User cancelled
            return
        prompt = prompt.strip().decode('utf-8')
        args['query'] = prompt
    xml = DU().downloadUrl(utils.extend_url('{server}%s' % key, args))
    try:
        xml[0].attrib
    except (TypeError, IndexError, AttributeError):
        LOG.error('Could not browse to key %s, section %s',
                  key, section_id)
        return
    if xml[0].tag == 'Hub':
        # E.g. when hitting the endpoint '/hubs/search'
        answ = utils.etree.Element(xml.tag, attrib=xml.attrib)
        for hub in xml:
            if not utils.cast(int, hub.get('size')):
                # Empty category
                continue
            for entry in hub:
                api = API(entry)
                if api.plex_type == v.PLEX_TYPE_TAG:
                    # Append the type before the actual element for all "tags"
                    # like genres, actors, etc.
                    entry.attrib['tag'] = '%s: %s' % (hub.get('title'),
                                                      api.tag_label())
                answ.append(entry)
        xml = answ
    show_listing(xml, plex_type, section_id, synched, key)


def extras(plex_id):
    """
    Lists all extras for plex_id
    """
    if not _wait_for_auth():
        return xbmcplugin.endOfDirectory(int(sys.argv[1]), False)
    app.init(entrypoint=True)
    xml = PF.GetPlexMetadata(plex_id)
    try:
        xml[0].attrib
    except (TypeError, IndexError, KeyError):
        xbmcplugin.endOfDirectory(int(sys.argv[1]))
        return
    extras = API(xml[0]).extras()
    if extras is None:
        return
    for child in xml:
        xml.remove(child)
    for i, child in enumerate(extras):
        xml.insert(i, child)
    show_listing(xml, synched=False, plex_type=v.PLEX_TYPE_MOVIE)
