import sys
import xbmc
import xbmcgui
import xbmcplugin
import datetime
import random
import resources.lib.utils as utils
import resources.lib.constants as constants
from resources.lib.traktapi import TraktAPI
from resources.lib.listitem import ListItem
from resources.lib.kodilibrary import KodiLibrary
from resources.lib.plugin import Plugin


class Container(Plugin):
    def __init__(self):
        super(Container, self).__init__()
        self.handle = int(sys.argv[1])
        self.paramstring = utils.try_decode_string(sys.argv[2][1:])
        self.params = utils.parse_paramstring(sys.argv[2][1:])
        self.item_tmdbtype = None
        self.item_dbtype = None
        self.url = {}
        self.details_tv = None
        self.plugincategory = 'TMDb Helper'
        self.containercontent = ''
        self.mixed_containercontent = ''
        self.library = 'video'
        self.updatelisting = False
        self.check_sync = False
        self.dbid_sorting = False
        self.randomlist = []
        self.numitems_dbid = 0
        self.numitems_tmdb = 0
        self.trakt_limit = 20 if self.addon.getSettingBool('trakt_extendlimit') else 10

    def start_container(self):
        xbmcplugin.setPluginCategory(self.handle, self.plugincategory)  # Container.PluginCategory
        xbmcplugin.setContent(self.handle, self.containercontent)  # Container.Content

    def finish_container(self):
        if self.params.get('random'):
            return
        for k, v in self.params.items():
            if not k or not v:
                continue
            try:
                xbmcplugin.setProperty(self.handle, u'Param.{}'.format(k), u'{}'.format(v))  # Set params to container properties
            except Exception as exc:
                utils.kodi_log(u'Error: {}\nUnable to set Param.{} to {}'.format(exc, k, v), 1)

        if self.item_dbtype in ['movie', 'tvshow', 'episode']:
            xbmcplugin.addSortMethod(self.handle, xbmcplugin.SORT_METHOD_UNSORTED)
            xbmcplugin.addSortMethod(self.handle, xbmcplugin.SORT_METHOD_EPISODE) if self.item_dbtype == 'episode' else None
            xbmcplugin.addSortMethod(self.handle, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE)
            xbmcplugin.addSortMethod(self.handle, xbmcplugin.SORT_METHOD_LASTPLAYED)
            xbmcplugin.addSortMethod(self.handle, xbmcplugin.SORT_METHOD_PLAYCOUNT)

        xbmcplugin.endOfDirectory(self.handle, updateListing=self.updatelisting)

    def set_url_params(self, url):
        if self.params.get('widget'):
            url['widget'] = self.params.get('widget')
        if self.params.get('fanarttv'):
            url['fanarttv'] = self.params.get('fanarttv')
        if self.params.get('nextpage'):
            url['nextpage'] = self.params.get('nextpage')
        return url

    def exp_fanarttv(self):
        if self.params.get('fanarttv', '').capitalize() == 'False':
            return False
        if self.params.get('fanarttv', '').capitalize() == 'True':
            return True
        if self.addon.getSettingBool('widget_fanarttv_lookup') and self.params.get('widget', '').capitalize() == 'True':
            return True

    def translate_discover(self):
        lookup_keyword = None if self.params.get('with_id') and self.params.get('with_id') != 'False' else 'keyword'
        lookup_company = None if self.params.get('with_id') and self.params.get('with_id') != 'False' else 'company'
        lookup_person = None if self.params.get('with_id') and self.params.get('with_id') != 'False' else 'person'
        lookup_genre = None if self.params.get('with_id') and self.params.get('with_id') != 'False' else 'genre'

        if self.params.get('with_genres'):
            self.params['with_genres'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_genres')),
                lookup_genre,
                separator=self.params.get('with_separator'))

        if self.params.get('without_genres'):
            self.params['without_genres'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('without_genres')),
                lookup_genre,
                separator=self.params.get('with_separator'))

        if self.params.get('with_keywords'):
            self.params['with_keywords'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_keywords')),
                lookup_keyword,
                separator=self.params.get('with_separator'))

        if self.params.get('without_keywords'):
            self.params['without_keywords'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('without_keywords')),
                lookup_keyword,
                separator=self.params.get('with_separator'))

        if self.params.get('with_companies'):
            self.params['with_companies'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_companies')),
                lookup_company,
                separator='NONE')

        if self.params.get('with_people'):
            self.params['with_people'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_people')),
                lookup_person,
                separator=self.params.get('with_separator'))

        if self.params.get('with_cast'):
            self.params['with_cast'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_cast')),
                lookup_person,
                separator=self.params.get('with_separator'))

        if self.params.get('with_crew'):
            self.params['with_crew'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_crew')),
                lookup_person,
                separator=self.params.get('with_separator'))

        if self.params.get('with_release_type'):
            self.params['with_release_type'] = self.tmdb.get_translated_list(
                utils.split_items(self.params.get('with_release_type')),
                None,
                separator='OR')

        # Translate relative dates based upon today's date
        for i in constants.USER_DISCOVER_RELATIVEDATES:
            datecode = self.params.get(i, '')
            datecode = datecode.lower()
            if not datecode or all(x not in datecode for x in ['t-', 't+']):
                continue  # No value or not a relative date so skip
            elif 't-' in datecode:
                days = utils.try_parse_int(datecode.replace('t-', ''))
                date = datetime.datetime.now() - datetime.timedelta(days=days)
            elif 't+' in datecode:
                days = utils.try_parse_int(datecode.replace('t+', ''))
                date = datetime.datetime.now() + datetime.timedelta(days=days)
            self.params[i] = date.strftime("%Y-%m-%d")

    def get_trakt_watched(self):
        if not self.addon.getSettingBool('trakt_watchedindicators'):
            return
        if self.item_dbtype == 'movie':
            return TraktAPI().get_watched('movie')
        if self.item_dbtype == 'episode':
            return TraktAPI().get_watched('show')

    def get_trakt_unwatched(self):
        if not self.addon.getSettingBool('trakt_unwatchedcounts') or not self.addon.getSettingBool('trakt_watchedindicators') or self.item_dbtype not in ['season', 'tvshow']:
            return -1
        traktapi = TraktAPI(tmdb=self.tmdb)
        self.check_sync = traktapi.sync_activities('shows', 'watched_at')
        if self.item_dbtype == 'season':
            return traktapi.get_unwatched_progress(tmdb_id=self.params.get('tmdb_id'), imdb_id=self.params.get('imdb_id'))
        if self.item_dbtype == 'tvshow':
            return

    def get_details_tv(self, tmdb_id=None, season=None, artwork_only=False, cache_only=False):
        if not tmdb_id:
            return
        if not self.details_tv:
            self.details_tv = {} if artwork_only else self.tmdb.get_detailed_item('tv', tmdb_id, season=season, cache_only=cache_only) or {}
        if self.fanarttv and self.exp_fanarttv():
            tvdb_id = self.tmdb.get_item_externalid('tv', tmdb_id, 'tvdb_id')
            artwork = self.fanarttv.get_tvshow_allart_lc(tvdb_id)
            self.details_tv['poster'] = artwork.get('poster')
            self.details_tv['clearart'] = artwork.get('clearart')
            self.details_tv['clearlogo'] = artwork.get('clearlogo')
            self.details_tv['landscape'] = artwork.get('landscape')
            self.details_tv['banner'] = artwork.get('banner')
            self.details_tv['fanart'] = self.details_tv.get('fanart') or artwork.get('fanart')

    def configure_list_items(self, items):
        if not items:
            return

        added, dbiditems, tmdbitems, lastitems, firstitems, nextpage = [], [], [], [], [], []
        mixed_movies, mixed_tvshows = 0, 0

        # Get TV Show details for Container
        if self.item_tmdbtype in ['season', 'episode'] and self.params.get('tmdb_id'):
            self.get_details_tv(self.params.get('tmdb_id'), season=self.params.get('season', None))

        # Add Up Next Season
        if self.item_tmdbtype == 'season' and self.details_tv and not self.addon.getSettingBool('hide_special_seasons'):
            item_upnext = ListItem(library=self.library, **self.details_tv)
            item_upnext.infolabels['season'] = self.addon.getLocalizedString(32043)
            item_upnext.label = self.addon.getLocalizedString(32043)
            item_upnext.url = {'info': 'trakt_upnext', 'type': 'tv'}
            items.append(item_upnext)

        for i in items:
            # Add NextPage Item to End of List
            if i.nextpage:
                i.url = self.params.copy()
                i.url['page'] = i.nextpage
                i.icon = '{0}/resources/icons/tmdb/nextpage.png'.format(self.addonpath)
                i.infoproperties['SpecialSort'] = 'bottom'
                if self.params.get('nextpage') or (self.params.get('widget') and self.addon.getSettingBool('widgets_nextpage')):
                    nextpage.append(i)
                continue

            # Filter Out Duplicate Items
            name = u'{0}{1}'.format(i.label, i.imdb_id or i.tmdb_id or i.poster)
            if name in added:
                continue
            added.append(name)

            # Count Mixed Types to Set Appropriate Container Content
            if i.mixed_type == 'tv':
                mixed_tvshows += 1
            elif i.mixed_type == 'movie':
                mixed_movies += 1

            # Lookup TVShow Details for Episode Widgets
            if self.params.get('info') in constants.EPISODE_WIDGETS:
                self.details_tv = None
                self.get_details_tv(i.tmdb_id or self.get_tmdb_id(query=i.infolabels.get('tvshowtitle'), itemtype='tv'), artwork_only=True)

            # Add TVShow Details to Item
            if self.details_tv:
                season_num = i.infolabels.get('season')
                i.cast = self.details_tv.get('cast', []) + i.cast
                i.infolabels = utils.merge_two_dicts(self.details_tv.get('infolabels', {}), i.infolabels)
                i.infoproperties = utils.merge_two_dicts(self.details_tv.get('infoproperties', {}), i.infoproperties)
                i.poster = i.poster or self.details_tv.get('poster')
                i.fanart = i.fanart if i.fanart and i.fanart != '{0}/fanart.jpg'.format(self.addonpath) else self.details_tv.get('fanart')
                i.tvshow_clearart = i.tvshow_clearart or self.details_tv.get('clearart')
                i.tvshow_clearlogo = i.tvshow_clearlogo or self.details_tv.get('clearlogo')
                i.tvshow_landscape = i.tvshow_landscape or self.details_tv.get('landscape')
                i.tvshow_banner = i.tvshow_banner or self.details_tv.get('banner')
                i.tvshow_poster = self.details_tv.get('poster') or i.poster
                i.infolabels['season'] = season_num

            # Format Episode Labels
            if not self.params.get('info') == 'details' and self.item_tmdbtype == 'episode':
                if i.infolabels.get('season') and i.infolabels.get('episode'):
                    i.label = u'{:02d}. {}'.format(utils.try_parse_int(i.infolabels.get('episode')), i.label)
                    if self.params.get('info') in constants.EPISODE_WIDGETS or self.addon.getSettingBool('flatten_seasons'):
                        i.label = u'{}x{}'.format(utils.try_parse_int(i.infolabels.get('season')), i.label)

            # Format label for future eps/movies but not plugin methods specifically about the future or details/seasons
            if self.params.get('info') not in constants.NO_LABEL_FORMATTING and self.item_tmdbtype in ['episode', 'movie', 'tv']:
                try:
                    if (not i.infolabels.get('premiered')
                            or utils.convert_timestamp(i.infolabels.get('premiered'), "%Y-%m-%d", 10) > datetime.datetime.now()):
                        i.label = '[COLOR=ffcc0000][I]{}[/I][/COLOR]'.format(i.label)
                        # Don't add if option enabled to hide
                        if self.addon.getSettingBool('hide_unaired_episodes') and self.item_tmdbtype in ['tv', 'episode']:
                            continue
                        if self.addon.getSettingBool('hide_unaired_movies') and self.item_tmdbtype in ['movie']:
                            continue
                except Exception as exc:
                    utils.kodi_log(u'Error: {}'.format(exc), 1)

            # Get DBID From Library
            i.dbid = self.get_db_info(
                info='dbid', tmdbtype=self.item_tmdbtype,
                imdb_id=i.infoproperties.get('tvshow.imdb_id') or i.infoproperties.get('imdb_id'),
                tmdb_id=i.infoproperties.get('tvshow.tmdb_id') or i.infoproperties.get('tmdb_id'),
                tvdb_id=i.infoproperties.get('tvshow.tvdb_id') or i.infoproperties.get('tvdb_id'),
                season=i.infolabels.get('season'),
                episode=i.infolabels.get('episode'))

            # Get TVSHOW DBID for episodes / seasons
            if self.item_tmdbtype in ['season', 'episode']:
                i.infoproperties['tvshow.dbid'] = i.tvshow_dbid = self.get_db_info(
                    info='dbid', tmdbtype='tv',
                    imdb_id=i.infoproperties.get('tvshow.imdb_id'),
                    tmdb_id=i.infoproperties.get('tvshow.tmdb_id'),
                    tvdb_id=i.infoproperties.get('tvshow.tvdb_id'))

            # Special Property Because Plugin Category not Available in Widgets
            i.infoproperties['widget'] = self.plugincategory

            if self.item_tmdbtype == 'season' and not i.infolabels.get('season'):
                if not self.addon.getSettingBool('hide_special_seasons'):  # Don't add specials if user set to hide
                    lastitems.append(i)  # Put special season last
            elif self.item_tmdbtype == 'season' and i.infolabels.get('season') == self.addon.getLocalizedString(32043):
                firstitems.append(i)  # Put Up Next Season First
            elif i.dbid and self.dbid_sorting:
                dbiditems.append(i)  # Put DBID Items At Front
            else:
                tmdbitems.append(i)

        if mixed_movies or mixed_tvshows:
            self.mixed_containercontent = 'tvshows' if mixed_tvshows > mixed_movies else 'movies'

        self.numitems_dbid = len(dbiditems) or 0
        self.numitems_tmdb = len(tmdbitems) or 0
        xbmcplugin.setProperty(self.handle, 'NumItems.DBID', str(self.numitems_dbid))
        xbmcplugin.setProperty(self.handle, 'NumItems.TMDB', str(self.numitems_tmdb))

        return firstitems + dbiditems + tmdbitems + lastitems + nextpage

    def get_userdiscover_listitems(self, basedir=False):
        basedir = constants.USER_DISCOVER_LISTITEMS_BASEDIR if basedir else []
        if self.params.get('type') == 'movie':
            return basedir + constants.USER_DISCOVER_LISTITEMS_MOVIES
        return basedir + constants.USER_DISCOVER_LISTITEMS_TVSHOWS

    def get_userdiscover_sortmethods(self):
        if self.params.get('type') == 'movie':
            return constants.USER_DISCOVER_SORTBY_MOVIES
        return constants.USER_DISCOVER_SORTBY_TVSHOWS

    def get_userdiscover_prop(self, name, prefix=None, **kwargs):
        prefix = 'TMDbHelper.UserDiscover.{}'.format(prefix) if prefix else 'TMDbHelper.UserDiscover'
        return utils.get_property(name, prefix=prefix, **kwargs)

    def get_userdiscover_folderpath_url(self, info='discover'):
        url = {'info': info, 'type': self.params.get('type'), 'with_id': 'True'}
        url = self.set_url_params(url)
        for i in self.get_userdiscover_listitems(basedir=True):
            k = i.get('url', {}).get('method')
            v = self.get_userdiscover_prop(k)
            if not k or not v:
                continue
            url[k] = v
        return url

    def get_userdiscover_url(self, url, label=None):
        if url.get('method') == 'open':
            return self.get_userdiscover_folderpath_url()
        if label:
            url['label'] = label
        url['type'] = self.params.get('type')
        url = self.set_url_params(url)
        return url

    def clear_userdiscover_properties(self):
        for i in self.get_userdiscover_listitems(basedir=True):
            name = i.get('url', {}).get('method')
            self.get_userdiscover_prop(name, clearproperty=True)
            self.get_userdiscover_prop(name, 'Label', clearproperty=True)

    def add_userdiscover_method_property(self, header, tmdbtype, usedetails, old_label=None, old_value=None):
        if old_label and old_value:
            if xbmcgui.Dialog().yesno(
                    u'{} {}'.format(tmdbtype.capitalize(), self.addon.getLocalizedString(32098)),
                    self.addon.getLocalizedString(32099) + ':', old_label,
                    self.addon.getLocalizedString(32100),
                    yeslabel=self.addon.getLocalizedString(32101), nolabel=self.addon.getLocalizedString(32102)):
                return
            self.new_property_label = old_label
            self.new_property_value = old_value

        new_label = utils.try_decode_string(xbmcgui.Dialog().input(header))
        if not new_label:
            return

        new_value = self.tmdb.get_tmdb_id(
            tmdbtype, query=new_label, selectdialog=True, longcache=True, usedetails=usedetails, returntuple=True)
        if not new_value:
            if xbmcgui.Dialog().yesno(self.addon.getLocalizedString(32103), self.addon.getLocalizedString(32104).format(new_label)):
                self.add_userdiscover_method_property(header, tmdbtype, usedetails)
            return

        new_value = (utils.try_encode_string(new_value[0]), new_value[1])
        self.new_property_label = '{0} / {1}'.format(self.new_property_label, new_value[0]) if self.new_property_label else new_value[0]
        self.new_property_value = '{0} / {1}'.format(self.new_property_value, new_value[1]) if self.new_property_value else '{}'.format(new_value[1])
        if xbmcgui.Dialog().yesno(u'{} {}'.format(self.addon.getLocalizedString(32106), new_value[0]), u'{}\n{}'.format(self.new_property_label, self.addon.getLocalizedString(32105))):
            self.add_userdiscover_method_property(header, tmdbtype, usedetails)

    def set_userdiscover_separator_property(self):
        choice = xbmcgui.Dialog().yesno(
            self.addon.getLocalizedString(32107),
            self.addon.getLocalizedString(32108),
            yeslabel=self.addon.getLocalizedString(32109), nolabel=self.addon.getLocalizedString(32110))
        self.new_property_value = 'OR' if choice else 'AND'
        self.new_property_label = 'ANY' if choice else 'ALL'

    def set_userdiscover_selectlist_properties(self, data_list=None, header=None, multiselect=True):
        if not data_list:
            return
        header = header or self.addon.getLocalizedString(32111)
        func = xbmcgui.Dialog().multiselect if multiselect else xbmcgui.Dialog().select
        dialog_list = [i.get('name') for i in data_list]
        select_list = func(header, dialog_list)
        if not select_list:
            return
        if not multiselect:
            select_list = [select_list]
        for i in select_list:
            label = data_list[i].get('name')
            value = data_list[i].get('id')
            if not value:
                continue
            self.new_property_label = '{0} / {1}'.format(self.new_property_label, label) if self.new_property_label else label
            self.new_property_value = '{0} / {1}'.format(self.new_property_value, value) if self.new_property_value else '{}'.format(value)

    def set_userdiscover_genre_property(self):
        data_list = self.tmdb.get_request_lc('genre', self.params.get('type'), 'list')
        if not data_list:
            return
        data_list = data_list.get('genres', [])
        self.set_userdiscover_selectlist_properties(data_list, header=self.addon.getLocalizedString(32112))

    def set_userdiscover_method_property(self):
        method = self.params.get('method')

        # Set Input Method
        affix = ''
        header = 'Search for '
        usedetails = False
        label = self.params.get('label')
        tmdbtype = self.params.get('type')
        inputtype = xbmcgui.INPUT_ALPHANUM
        if any(i in method for i in ['year', 'vote_', '_runtime', '_networks']):
            header = self.addon.getLocalizedString(32114) + ' '
            inputtype = xbmcgui.INPUT_NUMERIC
        elif '_date' in method:
            header = self.addon.getLocalizedString(32114) + ' '
            affix = ' YYYY-MM-DD\n' + self.addon.getLocalizedString(32113)
        elif '_genres' in method:
            label = xbmc.getLocalizedString(515)
            tmdbtype = 'genre'
        elif '_companies' in method:
            label = self.addon.getLocalizedString(32115)
            tmdbtype = 'company'
        elif '_networks' in method:
            label = self.addon.getLocalizedString(32116)
            tmdbtype = 'company'
        elif '_keywords' in method:
            label = self.addon.getLocalizedString(32117)
            tmdbtype = 'keyword'
        elif any(i in method for i in ['_cast', '_crew', '_people']):
            label = self.addon.getLocalizedString(32118)
            tmdbtype = 'person'
            usedetails = True
        header = '{0}{1}{2}'.format(header, label, affix)
        old_value = self.get_userdiscover_prop(method) or None
        old_label = self.get_userdiscover_prop(method, 'Label') or None

        # Route Method
        if method == 'with_separator':
            self.set_userdiscover_separator_property()
        elif '_genres' in method:
            self.set_userdiscover_genre_property()
        elif 'with_release_type' in method:
            self.set_userdiscover_selectlist_properties(constants.USER_DISCOVER_RELEASETYPES, header=self.addon.getLocalizedString(32119))
        elif 'region' in method:
            self.set_userdiscover_selectlist_properties(constants.USER_DISCOVER_REGIONS, header=self.addon.getLocalizedString(32120), multiselect=False)
        elif 'with_original_language' in method:
            self.set_userdiscover_selectlist_properties(constants.USER_DISCOVER_LANGUAGES, header=self.addon.getLocalizedString(32159), multiselect=False)
        elif 'with_runtime' not in method and 'with_networks' not in method and any(i in method for i in ['with_', 'without_']):
            self.add_userdiscover_method_property(header, tmdbtype, usedetails, old_label=old_label, old_value=old_value)
        else:
            self.new_property_label = self.new_property_value = utils.try_decode_string(
                xbmcgui.Dialog().input(header, type=inputtype, defaultt=old_value))

    def set_userdiscover_sortby_property(self):
        sort_method_list = self.get_userdiscover_sortmethods()
        sort_method = xbmcgui.Dialog().select(xbmc.getLocalizedString(39010), sort_method_list)
        self.new_property_label = self.new_property_value = sort_method_list[sort_method] if sort_method > -1 else None

    def get_userdiscover_affix(self, method):
        if self.params.get('method') == method:
            return self.new_property_label
        return self.get_userdiscover_prop(method, 'Label')

    def get_userdiscover_label(self, label, method):
        append_label = self.get_userdiscover_affix(method)
        label = label.format(utils.type_convert(self.params.get('type'), 'plural'))
        return '{0}: {1}'.format(label, append_label) if append_label else label

    def list_userdiscover_build(self, items, skipnull=False):
        for i in items:
            i = ListItem(library=self.library, **i)
            i.url = self.get_userdiscover_url(i.url, i.label)
            i.label = self.get_userdiscover_label(i.label, i.url.get('method'))
            i.create_listitem(self.handle, **i.url) if not skipnull or self.get_userdiscover_affix(i.url.get('method')) else None

    def list_userdiscover_dialog(self):
        urls = []
        dialogitems = []
        for i in self.get_userdiscover_listitems():
            i = ListItem(library=self.library, **i)
            i.url = self.get_userdiscover_url(i.url, i.label)
            i.label = self.get_userdiscover_label(i.label, i.url.get('method'))
            urls.append(i.url)
            dialogitems.append(i.set_listitem())
        idx = xbmcgui.Dialog().select('Add Rule', dialogitems)
        if idx == -1:
            return
        self.params = urls[idx]
        self.router()

    def save_userdiscover(self):
        url = self.get_userdiscover_folderpath_url()
        if not url:
            return
        url_name = xbmcgui.Dialog().input('Enter a Name to Save Settings As')
        if not url_name:
            return

        # Clean up some added params
        url.pop('widget', None)
        url.pop('nextpage', None)
        url.pop('fanarttv', None)

        labels = {}
        for k, v in url.items():
            label = utils.get_property(k, prefix='TMDbHelper.UserDiscover.Label')
            if label:
                labels[k] = label

        item = utils.set_searchhistory(query={'name': url_name, 'url': url, 'labels': labels}, itemtype='discover', maxentries=None)
        if not item:
            return
        xbmcgui.Dialog().ok(item.get('name'), 'Successfully saved {}.\nSaved list will appear in parent discover folder.'.format(item.get('name')))

    def list_userdiscover(self):
        self.updatelisting = True if self.params.get('method') else False
        self.new_property_label = self.new_property_value = None

        # Routing for discover onclick actions
        if not self.params.get('method') or self.params.get('method') == 'clear':
            self.clear_userdiscover_properties()
        elif self.params.get('method') == 'sort_by':
            self.set_userdiscover_sortby_property()
        elif self.params.get('method') == 'save':
            self.save_userdiscover()
        elif self.params.get('method') == 'add_rule':
            return self.list_userdiscover_dialog()
        elif self.params.get('method') == 'edit':
            self.updatelisting = False
        else:
            self.set_userdiscover_method_property()

        # Set / Clear Property
        if self.new_property_value:
            self.get_userdiscover_prop(self.params.get('method'), setproperty=self.new_property_value)
            self.get_userdiscover_prop(self.params.get('method'), 'Label', setproperty=self.new_property_label)
        else:
            self.get_userdiscover_prop(self.params.get('method'), clearproperty=True)
            self.get_userdiscover_prop(self.params.get('method'), 'Label', clearproperty=True)

        # Update the container to reflect changes made by user
        self.containercontent = 'files'
        self.start_container()
        self.list_userdiscover_build(constants.USER_DISCOVER_LISTITEMS_BASEDIR)
        self.list_userdiscover_build(self.get_userdiscover_listitems(basedir=False), skipnull=True)
        self.list_userdiscover_build(constants.USER_DISCOVER_LISTITEMS_ADDRULE)
        self.finish_container()

    def list_trakthistory(self):
        traktapi = TraktAPI(tmdb=self.tmdb)
        userslug = traktapi.get_usernameslug(login=True)
        self.item_tmdbtype = self.params.get('type')
        if self.params.get('info') == 'trakt_nextepisodes':
            items = traktapi.get_inprogress(userslug, limit=self.trakt_limit, episodes=True)
            self.item_tmdbtype = 'episode'
        elif self.params.get('info') == 'trakt_inprogress':
            items = traktapi.get_inprogress_movies(limit=self.trakt_limit) if self.params.get('type') == 'movie' else traktapi.get_inprogress(userslug, limit=self.trakt_limit)
        elif self.params.get('info') == 'trakt_mostwatched':
            items = traktapi.get_mostwatched(userslug, self.params.get('type'), limit=self.trakt_limit)
        elif self.params.get('info') == 'trakt_history':
            items = traktapi.get_recentlywatched(userslug, self.params.get('type'), limit=self.trakt_limit)
        self.list_items(
            items=items, url={
                'info': 'trakt_upnext' if self.params.get('info') == 'trakt_inprogress' and self.params.get('type') != 'movie' else 'details',
                'type': self.item_tmdbtype})

    def list_traktupnext(self):
        self.item_tmdbtype = 'episode'
        self.list_items(
            items=TraktAPI(tmdb=self.tmdb).get_upnext_episodes(tmdb_id=self.params.get('tmdb_id')),
            url_tmdb_id=self.params.get('tmdb_id'),
            url={'info': 'details', 'type': 'episode'})

    def list_traktcalendar_episodes(self):
        date = datetime.datetime.today() + datetime.timedelta(days=utils.try_parse_int(self.params.get('startdate', 0)))
        self.plugincategory = date.strftime('%A')  # For Trakt Calendar Custom Window
        self.item_tmdbtype = 'episode'
        self.list_items(
            items=TraktAPI(tmdb=self.tmdb, login=True).get_calendar_episodes(
                days=utils.try_parse_int(self.params.get('days', 1)),
                startdate=utils.try_parse_int(self.params.get('startdate', 0))),
            url={'info': 'details', 'type': 'episode'})

    def list_librarycalendar_episodes(self):
        kodidb = KodiLibrary(dbtype='tvshow', attempt_reconnect=True)

        # Get calendar items from Trakt and check if matching show in kodi library
        # Widened calendar date range by 24hrs each side to accomodate timezone conversion as Trakt is UTC 00:00
        trakt = TraktAPI()
        traktitems = [
            i for i in trakt.get_airingshows(
                start_date=utils.try_parse_int(self.params.get('startdate', 0)) - 1,
                days=utils.try_parse_int(self.params.get('days', 1)) + 2)
            if kodidb.get_info(
                'dbid',
                tmdb_id=i.get('show', {}).get('ids', {}).get('tmdb'),
                tvdb_id=i.get('show', {}).get('ids', {}).get('tvdb'),
                imdb_id=i.get('show', {}).get('ids', {}).get('imdb'))]

        items = []
        for i in traktitems:
            if not utils.date_in_range(
                    i.get('first_aired'), utc_convert=True,
                    start_date=utils.try_parse_int(self.params.get('startdate', 0)),
                    days=utils.try_parse_int(self.params.get('days', 1))):
                continue  # Don't add items that aren't in our timezone converted range

            # Check Trakt has a TMDb ID for the show and look-up the item if it doesn't
            if not i.get('show', {}).get('ids', {}).get('tmdb'):
                i['show']['ids']['tmdb'] = self.get_tmdb_id(
                    itemtype='tv', query=i.get('show', {}).get('title'),
                    imdb_id=i.get('show', {}).get('ids', {}).get('imdb'),
                    tvdb_id=i.get('show', {}).get('ids', {}).get('tvdb'))

            # Create our list item
            li = ListItem(library=self.library, **self.tmdb.get_detailed_item(
                itemtype='tv', tmdb_id=i.get('show', {}).get('ids', {}).get('tmdb'),
                season=i.get('episode', {}).get('season'),
                episode=i.get('episode', {}).get('number')))
            li.tmdb_id = i.get('show', {}).get('ids', {}).get('tmdb')  # Set TVSHOW ID
            li.infoproperties['tvshow.imdb_id'] = i.get('show', {}).get('ids', {}).get('imdb') or li.infoproperties.get('tvshow.imdb_id')
            li.infoproperties['tvshow.tmdb_id'] = i.get('show', {}).get('ids', {}).get('tmdb') or li.infoproperties.get('tvshow.tmdb_id')
            li.infoproperties['tvshow.tvdb_id'] = i.get('show', {}).get('ids', {}).get('tvdb') or li.infoproperties.get('tvshow.tvdb_id')

            # Get some additional properties and add our item
            items.append(trakt.get_calendar_properties(li, i))

        # Today's date to plugin category
        date = datetime.datetime.today() + datetime.timedelta(days=utils.try_parse_int(self.params.get('startdate', 0)))
        self.plugincategory = date.strftime('%A')

        # Create our list
        self.params['linklibrary'] = self.addon.getSettingBool('nextaired_linklibrary')
        self.item_tmdbtype = 'episode'
        self.list_items(items=items, url={'info': 'details', 'type': 'episode'})

    def list_calendar(self, trakt=False):
        if self.params.get('type') == 'episode':
            self.list_traktcalendar_episodes() if trakt else self.list_librarycalendar_episodes()
            return

        icon = '{0}/resources/trakt.png'.format(self.addonpath) if trakt else '{0}/resources/icons/tmdb/airing.png'.format(self.addonpath)
        calendar = constants.TRAKT_CALENDAR if trakt else constants.LIBRARY_CALENDAR
        info = 'trakt_calendar' if trakt else 'library_nextaired'
        self.start_container()
        for i in calendar:
            date = datetime.datetime.today() + datetime.timedelta(days=i[1])
            label = i[0].format(date.strftime('%A'))
            listitem = ListItem(label=label, icon=icon)
            url = {'info': info, 'type': 'episode', 'startdate': i[1], 'days': i[2]}
            url = self.set_url_params(url)
            listitem.set_url_props(self.params, 'container')
            listitem.set_url_props(url, 'item')
            listitem.create_listitem(self.handle, **url)
        self.finish_container()

    def list_traktuserlists(self):
        cat = constants.TRAKT_LISTS.get(self.params.get('info'), {})
        path = cat.get('path', '')
        traktapi = TraktAPI(login=cat.get('req_auth', False))
        if '{user_slug}' in path:
            self.params['user_slug'] = self.params.get('user_slug') or traktapi.get_usernameslug()
        path = path.format(**self.params)
        icon = '{0}/resources/trakt.png'.format(self.addonpath)

        self.start_container()
        for i in traktapi.get_response_json(path, limit=250):
            if not i:
                continue
            i = i.get('list') or i
            label = i.get('name')
            label2 = i.get('user', {}).get('name')
            infolabels = {}
            infolabels['plot'] = i.get('description')
            infolabels['rating'] = i.get('likes')
            list_slug = i.get('ids', {}).get('slug')
            user_slug = i.get('user', {}).get('ids', {}).get('slug')
            listitem = ListItem(label=label, label2=label2, icon=icon, thumb=icon, poster=icon, infolabels=infolabels)
            url = {'info': 'trakt_userlist', 'user_slug': user_slug, 'list_slug': list_slug, 'type': self.params.get('type')}
            listitem.url = self.set_url_params(url)
            listitem.set_url_props(self.params, 'container')
            listitem.set_url_props(listitem.url, 'item')
            listitem.create_listitem(self.handle, **listitem.url) if not self.params.get('random') else self.randomlist.append(listitem)
        self.finish_container()

    def list_traktcollection(self):
        items = []
        if self.params.get('type') in ['movie', 'tv']:
            items = TraktAPI(tmdb=self.tmdb, login=True).get_collection(
                self.params.get('type'), utils.try_parse_int(self.params.get('page', 1)))
        self.item_tmdbtype = self.params.get('type')
        self.list_items(items, url={'info': 'details', 'type': self.item_tmdbtype})

    def list_trakt(self):
        if not self.params.get('type'):
            return

        cat = constants.TRAKT_LISTS.get(self.params.get('info', ''), {})
        traktapi = TraktAPI(tmdb=self.tmdb, login=cat.get('req_auth', False))

        if '{user_slug}' in cat.get('path', ''):
            self.params['user_slug'] = self.params.get('user_slug') or traktapi.get_usernameslug(login=True)

        self.item_tmdbtype = 'movie' if self.params.get('type') == 'both' else self.params.get('type', '')

        params = self.params.copy()
        params['type'] = utils.type_convert(self.item_tmdbtype, 'trakt') + 's'

        rnd_list = self.trakt_limit if self.params.pop('random', False) else None
        key_list = ['movie', 'show'] if self.params.get('type') == 'both' else [utils.type_convert(self.item_tmdbtype, 'trakt')]
        usr_list = True if self.params.get('info') == 'trakt_userlist' else False
        limit = 50 if rnd_list else self.trakt_limit

        if self.params.get('info') == 'trakt_watchlist':
            params['sortmethod'] = self.addon.getSettingString('trakt_watchlistsort')

        self.list_items(
            items=traktapi.get_itemlist(
                cat.get('path', '').format(**params), page=self.params.get('page', 1), limit=limit,
                rnd_list=rnd_list, req_auth=cat.get('req_auth'), usr_list=usr_list, key_list=key_list),
            url={'info': cat.get('url_info', 'details')})

    def list_traktmanagement(self):
        if not self.params.get('trakt') in constants.TRAKT_MANAGEMENT:
            return
        with utils.busy_dialog():
            traktapi = TraktAPI()
            slug_type = 'show' if self.params.get('type') == 'episode' else utils.type_convert(self.params.get('type'), 'trakt')
            trakt_type = utils.type_convert(self.params.get('type'), 'trakt')
            slug = traktapi.get_traktslug(slug_type, 'tmdb', self.params.get('tmdb_id'))
            item = traktapi.get_details(slug_type, slug, season=self.params.get('season', None), episode=self.params.get('episode', None))
            items = {trakt_type + 's': [item]}
            if self.params.get('trakt') == 'watchlist_add':
                traktapi.sync_watchlist(slug_type, mode='add', items=items)
            if self.params.get('trakt') == 'history_add':
                traktapi.sync_history(slug_type, mode='add', items=items)
            if self.params.get('trakt') == 'collection_add':
                traktapi.sync_collection(slug_type, mode='add', items=items)
            if self.params.get('trakt') == 'watchlist_remove':
                traktapi.sync_watchlist(slug_type, mode='remove', items=items)
            if self.params.get('trakt') == 'history_remove':
                traktapi.sync_history(slug_type, mode='remove', items=items)
            if self.params.get('trakt') == 'collection_remove':
                traktapi.sync_collection(slug_type, mode='remove', items=items)
            # TODO: Check status response and add dialog
        dialog_action = self.params.get('trakt').replace('_add', '').replace('_remove', '')
        dialog_header = 'Trakt {0}'.format(dialog_action.capitalize())
        dialog_text = self.addon.getLocalizedString(32062) if '_add' in self.params.get('trakt') else self.addon.getLocalizedString(32063)
        dialog_text = dialog_text.format(self.params.get('tmdb_id'), dialog_action.capitalize())
        xbmcgui.Dialog().ok(dialog_header, dialog_text)
        self.updatelisting = True

    def list_complete(self):
        self.item_tmdbtype = self.params.get('type')

        limit = 20
        daily_list_type = None
        sorting = False
        if self.item_tmdbtype == 'movie':
            daily_list_type = 'movie'
        elif self.item_tmdbtype == 'tv':
            daily_list_type = 'tv_series'
        elif self.item_tmdbtype == 'person':
            daily_list_type = 'person'
        elif self.item_tmdbtype == 'collection':
            daily_list_type = 'collection'
        elif self.item_tmdbtype == 'network':
            daily_list_type = 'tv_network'
            sorting = True
            limit = 250
        elif self.item_tmdbtype == 'keyword':
            daily_list_type = 'keyword'
            sorting = True
            limit = 250
        elif self.item_tmdbtype == 'studio':
            daily_list_type = 'production_company'
            sorting = True
            limit = 250
        if not daily_list_type:
            return
        daily_list = self.tmdb.get_daily_list(export_list=daily_list_type, sorting=False)

        items = []
        pos_z = utils.try_parse_int(self.params.get('page', 1)) * limit
        pos_a = pos_z - limit
        for i in daily_list[pos_a:pos_z]:
            if not i.get('id'):
                continue

            if self.item_tmdbtype in ['keyword', 'network', 'studio']:
                details = {}
                details['label'] = i.get('name')
                details['tmdb_id'] = i.get('id')
            else:
                details = self.tmdb.get_detailed_item(self.item_tmdbtype, i.get('id'))

            item = ListItem(library=self.library, **details)

            if not item:
                continue

            items.append(item)

        if not items:
            return

        items = sorted(items, key=lambda k: k.label) if sorting else items

        if len(daily_list) > pos_z:  # Long list so paginate
            items.append(ListItem(
                library=self.library, label=xbmc.getLocalizedString(33078),
                nextpage=utils.try_parse_int(self.params.get('page', 1)) + 1))

        if self.item_tmdbtype == 'collection':
            url = {'info': 'collection', 'type': 'movie'}
        elif self.item_tmdbtype == 'keyword':
            url = {'info': 'keyword_movies', 'type': 'movie'}
        elif self.item_tmdbtype == 'studio':
            url = {'info': 'discover', 'type': 'movie', 'with_companies': '{tmdb_id}', 'with_id': True}
        elif self.item_tmdbtype == 'network':
            url = {'info': 'discover', 'type': 'tv', 'with_networks': '{tmdb_id}', 'with_id': True}
        else:
            url = {'info': 'details', 'type': self.item_tmdbtype}

        self.plugincategory = self.params.get('plugincategory') or self.plugincategory
        self.dbid_sorting = False
        self.list_items(items=items, url=url)

    def list_becauseyouwatched(self, mostwatched=False):
        traktapi = TraktAPI(tmdb=self.tmdb, login=True)
        userslug = traktapi.get_usernameslug()
        if not userslug:
            return
        func = traktapi.get_mostwatched if mostwatched else traktapi.get_recentlywatched
        recentitems = func(userslug, self.params.get('type'), limit=5, islistitem=False)
        recentitem = recentitems[random.randint(0, len(recentitems) - 1)]
        if not recentitem[1]:
            return
        self.plugincategory = recentitem[2]
        self.params['tmdb_id'] = recentitem[1]
        self.params['info'] = 'recommendations'
        self.list_tmdb()

    def list_getid(self):
        params = self.params
        if self.params.get('info') == 'play' and self.params.get('type') == 'episode':
            params = self.params.copy()
            params['type'] = 'tv'
        self.params['tmdb_id'] = self.get_tmdb_id(**params)

    def play_islocked(self, lock=None):
        for k, v in self.params.items():
            lock = '{}.{}={}'.format(lock, k, v) if lock else '{}={}'.format(k, v)
        cur_lock = xbmcgui.Window(10000).getProperty('TMDbHelper.Player.ResolvedUrl')
        if cur_lock == lock:
            utils.kodi_log(u'Container -- Play IsLocked:\n{0}'.format(self.params), 1)
            return cur_lock
        xbmcgui.Window(10000).setProperty('TMDbHelper.Player.ResolvedUrl', lock)

    def list_play(self):
        utils.kodi_log(u'Container -- Attempting to Play Item...:\n{0}'.format(self.params), 1)
        """
        Kodi does 5x retries to resolve url but we don't use this method so we need to catch it
        Instead we just give a blank resolved url if a strm file or do nothing if not
        Otherwise Kodi re-triggers the start of the play function causing a slow down
        Should be fixed in Kodi Kore for Matrix so won't need this hack anymore
        """
        if self.play_islocked():
            if self.params.get('islocal'):
                xbmcplugin.setResolvedUrl(self.handle, True, ListItem().set_listitem())
            return

        # Check we have a TMDb ID and do nothing if we can't get one
        self.list_getid()
        if not self.params.get('type') or not self.params.get('tmdb_id'):
            utils.kodi_log(u'Container -- Play No Type or TMDb_ID:\n{0}'.format(self.params), 1)
            return

        # Build our player script command and run it
        season, episode = self.params.get('season', ''), self.params.get('episode', '')
        command = 'islocal' if self.params.get('islocal') else ''
        command = '{},play={},tmdb_id={}'.format(command, self.params.get('type'), self.params.get('tmdb_id'))
        command = '{},season={},episode={}'.format(command, season, episode) if season and episode else command
        command = 'RunScript(plugin.video.boneplayer,{})'.format(command)
        xbmc.executebuiltin(command)

        # Resolve to empty url if using a strm file because Kodi always expects resolvedurl in library
        if self.params.get('islocal'):
            xbmcplugin.setResolvedUrl(self.handle, True, ListItem().set_listitem())

    def list_search(self):
        self.updatelisting = True if self.params.pop('updatelisting', False) else False
        org_query = self.params.get('query')
        if not self.params.get('query'):
            self.params['query'] = utils.set_searchhistory(
                query=utils.try_decode_string(xbmcgui.Dialog().input(self.addon.getLocalizedString(32044), type=xbmcgui.INPUT_ALPHANUM)),
                itemtype=self.params.get('type'))
        elif self.params.get('history', '').lower() == 'true':  # Param to force history save
            utils.set_searchhistory(query=self.params.get('query'), itemtype=self.params.get('type'))
        if self.params.get('query'):
            self.list_tmdb(query=self.params.get('query'), year=self.params.get('year'))
            if not org_query:
                self.params['updatelisting'] = 'True'
                container_url = u'plugin://plugin.video.boneplayer/?{}'.format(utils.urlencode_params(self.params))
                xbmc.executebuiltin('Container.Update({})'.format(container_url))

    def list_clearhistory(self, url=None, icon=''):
        if not url:
            return
        url['clearcache'] = 'True'
        listitem = ListItem(label=self.addon.getLocalizedString(32121), icon=icon)
        listitem.set_url_props(self.params, 'container')
        listitem.set_url_props(url, 'item')
        listitem.create_listitem(self.handle, **url)

    def list_clearcache(self, itemtype):
        if self.params.get('clearcache'):
            self.updatelisting = True
            self.params.pop('clearcache', '')
            utils.set_searchhistory(itemtype=itemtype, clearcache=True)
            container_url = 'plugin://plugin.video.boneplayer/?'
            return u'{0}{1}'.format(container_url, utils.urlencode_params(self.params))

    def list_discoverdir(self):
        # Check if asked to clear the cache
        container_url = self.list_clearcache(itemtype='discover')
        icon = '{0}/resources/icons/tmdb/search.png'.format(self.addonpath)

        self.list_basedir(finish=False)  # Construct the basic categories but don't finish the container so we can add our history

        # Create discover history from saved items
        history = utils.get_searchhistory('discover')
        history.reverse()
        for idx, item in enumerate(history):
            listitem = ListItem(label=item.get('name'), icon=icon)
            listitem.set_url_props(self.params, 'container')
            listitem.set_url_props(item.get('url'), 'item')
            listitem.set_contextmenu([
                ('Edit', 'RunScript(plugin.video.boneplayer,discover_edit={})'.format(-1 - idx)),
                ('Rename', 'RunScript(plugin.video.boneplayer,discover_rename={})'.format(-1 - idx)),
                ('Delete', 'RunScript(plugin.video.boneplayer,discover_delete={})'.format(-1 - idx))])  # Need to reverse idx because oldest entries are cached at earliest index
            listitem.create_listitem(self.handle, **self.set_url_params(item.get('url')))

        # Create clear cache item if history exists
        if history:
            self.list_clearhistory(url=self.params.copy(), icon=icon)

        # Finish container
        self.finish_container()

        # If we cleared cache we need to update the container and replace the path so we don't keep clearing cache
        if container_url:
            xbmc.executebuiltin('Container.Update({}, replace)'.format(container_url))

    def list_searchdir(self):
        # Check if asked to clear the cache
        container_url = self.list_clearcache(itemtype=self.params.get('type'))
        icon = '{0}/resources/icons/tmdb/search.png'.format(self.addonpath)

        self.start_container()

        # Re-use our current params
        url = self.params.copy()
        url['info'] = 'search'

        # Create first search item
        listitem = ListItem(label='{} {}'.format('Search', utils.type_convert(self.params.get('type'), 'plural')), icon=icon)
        listitem.set_url_props(self.params, 'container')
        listitem.set_url_props(url, 'item')
        listitem.create_listitem(self.handle, **url)

        # Create cached history searches
        history = utils.get_searchhistory(self.params.get('type'))
        history.reverse()
        for query in history:
            url['query'] = query  # Add query as param so we search it
            listitem = ListItem(label=query, icon=icon)
            listitem.set_url_props(self.params, 'container')
            listitem.set_url_props(url, 'item')
            listitem.create_listitem(self.handle, **url)

        # Create clear cache item if history exists
        if history:
            url['info'] = 'dir_search'
            url.pop('query', '')
            self.list_clearhistory(url=url, icon=icon)

        # Finish container
        self.finish_container()

        # If we cleared cache we need to update the container and replace the path so we don't keep clearing cache
        if container_url:
            xbmc.executebuiltin('Container.Update({}, replace)'.format(container_url))

    def list_items(self, items=None, url=None, url_tmdb_id=None):
        """
        Sort listitems and then display
        url= for listitem base folderpath url params
        url_tmdb_id= for listitem tmdb_id used in url
        """
        items = self.configure_list_items(items)

        if not items:
            return

        self.item_dbtype = utils.type_convert(self.item_tmdbtype, 'dbtype')
        self.containercontent = self.mixed_containercontent or utils.type_convert(self.item_tmdbtype, 'container')

        trakt_watched = self.get_trakt_watched()
        trakt_unwatched = self.get_trakt_unwatched()
        hidewatched = self.addon.getSettingBool('widgets_hidewatched') if self.params.get('widget', '').capitalize() == 'True' else False

        x = 0
        self.start_container()
        for i in items:
            i.label2 = i.infoproperties.get('role') or i.label2
            i.infoproperties['numitems.dbid'] = self.numitems_dbid
            i.infoproperties['numitems.tmdb'] = self.numitems_tmdb
            i.infoproperties['dbtype'] = self.item_dbtype
            i.get_details(self.item_dbtype, self.tmdb, self.omdb, self.params.get('localdb'))
            i.get_url(
                url, url_tmdb_id=url_tmdb_id, widget=self.params.get('widget'), fanarttv=self.params.get('fanarttv'),
                nextpage=self.params.get('nextpage'), extended=self.params.get('extended'), linklibrary=self.params.get('linklibrary'))
            i.get_extra_artwork(self.tmdb, self.fanarttv) if len(items) < 22 and self.exp_fanarttv() else None
            i.get_trakt_watched(trakt_watched) if x == 0 or self.params.get('info') != 'details' else None
            i.get_trakt_unwatched(trakt=TraktAPI(tmdb=self.tmdb), request=trakt_unwatched, check_sync=self.check_sync) if x == 0 or self.params.get('info') != 'details' else None
            i.set_url_props(self.params, 'container')
            i.set_url_props(i.url, 'item')
            if not hidewatched or self.params.get('info') == 'details' or i.infolabels.get('overlay', 4) != 5:
                i.create_listitem(self.handle, **i.url) if not self.params.get('random') else self.randomlist.append(i)
                x += 1
        self.finish_container()

    def list_tmdb(self, *args, **kwargs):
        if not self.params.get('type'):
            return

        # Construct request
        cat = constants.TMDB_LISTS.get(self.params.get('info'), {})
        kwparams = utils.merge_two_dicts(utils.make_kwparams(self.params), kwargs)
        kwparams = utils.merge_two_dicts(kwparams, utils.parse_paramstring(cat.get('url_ext', '').format(**self.params)))
        kwparams.setdefault('key', cat.get('key'))
        path = cat.get('path', '').format(**self.params)

        self.plugincategory = self.params.get('plugincategory') or self.plugincategory
        self.dbid_sorting = cat.get('dbid_sorting', False)
        self.item_tmdbtype = cat.get('item_tmdbtype', '').format(**self.params)
        self.list_items(
            items=self.tmdb.get_list(path, *args, **kwparams),
            url_tmdb_id=cat.get('url_tmdb_id', '').format(**self.params),
            url={
                'info': cat.get('url_info', ''),
                'type': cat.get('url_type', '').format(**self.params) or self.item_tmdbtype})

    def list_credits(self, key='cast'):
        self.item_tmdbtype = 'person'
        self.plugincategory = key.capitalize()
        self.list_items(
            items=self.tmdb.get_credits_list(self.params.get('type'), self.params.get('tmdb_id'), key),
            url={'info': 'details', 'type': 'person'})

    def list_flatseasons(self):
        items = []
        basepath = 'tv/{}'.format(self.params.get('tmdb_id'))
        seasons = self.tmdb.get_list(basepath, key='seasons', pagination=False)
        for season in seasons:
            if not season.infolabels.get('season'):
                continue
            path = '{}/season/{}'.format(basepath, season.infolabels.get('season'))
            episodes = self.tmdb.get_list(path, key='episodes', pagination=False)
            items = items + [i for i in episodes]
        self.item_tmdbtype = 'episode'
        self.list_items(
            items=items, url_tmdb_id=self.params.get('tmdb_id'),
            url={'info': 'details', 'type': 'episode'})

    def list_details(self):
        # Build empty container if no tmdb_id
        if not self.params.get('tmdb_id'):
            self.start_container()
            self.finish_container()
            return

        # Set detailed item arguments
        d_args = (
            ('tv', self.params.get('tmdb_id'), self.params.get('season'), self.params.get('episode'))
            if self.params.get('type') == 'episode' else (self.params.get('type'), self.params.get('tmdb_id')))

        # Check if we want to refresh cache with &amp;refresh=True
        if self.params.get('refresh') == 'True':
            with utils.busy_dialog():
                self.tmdb.get_detailed_item(*d_args, cache_refresh=True)
            xbmc.executebuiltin('Container.Refresh')
            xbmcgui.Dialog().ok(self.addon.getLocalizedString(32045), self.addon.getLocalizedString(32046))
            self.updatelisting = True

        # Get details of item and return if nothing found
        details = self.tmdb.get_detailed_item(*d_args)
        if not details:
            return

        # Merge OMDb rating details
        if self.params.get('type') in ['movie', 'episode', 'tv', 'season']:
            details = self.get_omdb_ratings(details, cache_only=False)

        # Get TOP250 ranking for movies
        if self.params.get('type') == 'movie':
            details = self.get_top250_rank(details)

        # Merge library stats for person
        if self.params.get('type') == 'person':
            details = self.get_kodi_person_stats(details)

        # Create first item
        firstitem = ListItem(library=self.library, **details)
        if self.params.get('type') == 'movie':
            firstitem.url = {'info': 'play', 'type': self.params.get('type')}
        elif self.params.get('type') == 'tv':
            if self.addon.getSettingBool('flatten_seasons'):
                firstitem.url = {'info': 'flatseasons', 'type': 'episode'}
            else:
                firstitem.url = {'info': 'seasons', 'type': self.params.get('type')}
        elif self.params.get('type') == 'episode':
            firstitem.url = {'info': 'play', 'type': self.params.get('type')}
        else:
            firstitem.url = {'info': 'details', 'type': self.params.get('type')}
        items = [firstitem]

        # Build categories
        for i in constants.DETAILED_CATEGORIES:
            if self.params.get('type') in i.get('types'):
                item = ListItem(library=self.library, **details)
                item.label = i.get('name')
                item.url = {'info': i.get('info'), 'type': self.params.get('type')}
                if i.get('url_key') and details.get(i.get('url_key')):
                    item.url[i.get('url_key')] = details.get(i.get('url_key'))
                if i.get('icon'):
                    item.poster = item.icon = i.get('icon', '').format(self.addonpath)
                items.append(item)

        # Add trakt management items if &amp;manage=True
        if self.addon.getSettingBool('trakt_management'):
            traktapi = TraktAPI()
            trakt_collection = traktapi.sync_collection(utils.type_convert(self.params.get('type'), 'trakt'), 'tmdb')
            if trakt_collection:
                boolean = 'remove' if details.get('tmdb_id') in trakt_collection else 'add'
                item_collection = ListItem(library=self.library, **details)
                item_collection.label = self.addon.getLocalizedString(32047) if boolean == 'remove' else self.addon.getLocalizedString(32048)
                item_collection.url = {'info': 'details', 'trakt': 'collection_{0}'.format(boolean), 'type': self.params.get('type')}
                items.append(item_collection)
            trakt_watchlist = traktapi.sync_watchlist(utils.type_convert(self.params.get('type'), 'trakt'), 'tmdb')
            if trakt_watchlist:
                boolean = 'remove' if details.get('tmdb_id') in trakt_watchlist else 'add'
                item_watchlist = ListItem(library=self.library, **details)
                item_watchlist.label = self.addon.getLocalizedString(32049) if boolean == 'remove' else self.addon.getLocalizedString(32050)
                item_watchlist.url = {'info': 'details', 'trakt': 'watchlist_{0}'.format(boolean), 'type': self.params.get('type')}
                items.append(item_watchlist)
            trakt_history = traktapi.sync_history(utils.type_convert(self.params.get('type'), 'trakt'), 'tmdb')
            if trakt_history:
                boolean = 'remove' if details.get('tmdb_id') in trakt_history else 'add'
                item_history = ListItem(library=self.library, **details)
                item_history.label = self.addon.getLocalizedString(32051) if boolean == 'remove' else self.addon.getLocalizedString(32052)
                item_history.url = {'info': 'details', 'trakt': 'history_{0}'.format(boolean), 'type': self.params.get('type')}
                items.append(item_history)

        # Add refresh cache item
        refresh = ListItem(library=self.library, **details)
        refresh.label = self.addon.getLocalizedString(32053)
        refresh.url = {'info': 'details', 'refresh': 'True', 'type': self.params.get('type')}
        refresh.poster = refresh.icon = '{0}/resources/icons/tmdb/refresh.png'.format(self.addonpath)
        items.append(refresh)

        # Build our container
        self.item_tmdbtype = self.params.get('type')
        self.list_items(items=items, url_tmdb_id=self.params.get('tmdb_id'))

    def list_traktrandom(self):
        self.params['info'] = constants.RANDOM_TRAKT.get(self.params.get('info'), {})
        self.params['random'] = True
        self.router()

    def list_random(self):
        self.params['info'] = constants.RANDOM_LISTS.get(self.params.get('info'), {})
        self.params['random'] = True
        self.router()
        if not self.randomlist:
            return
        index = 0
        if len(self.randomlist) > 1:
            index = random.randint(0, len(self.randomlist) - 1)
        item = self.randomlist[index]
        self.plugincategory = item.label
        self.params = item.url
        self.router()

    def list_basedir(self, finish=True):
        cat = constants.BASEDIR_PATH.get(self.params.get('info'), {})
        basedir = cat.get('folders', [constants.BASEDIR_MAIN])
        types = cat.get('types', [None])
        self.start_container()
        for folder in basedir:
            for i in folder:
                for t in types:
                    if t not in i.get('types', [None]):
                        continue
                    url = {'info': i.get('info'), 'type': t} if t else {'info': i.get('info')}

                    if not xbmc.getCondVisibility("Window.IsMedia"):
                        url['widget'] = 'True'

                    if self.addon.getSettingBool('fanarttv_lookup') and xbmc.getCondVisibility("Window.IsMedia"):
                        url['fanarttv'] = 'True'

                    if xbmc.getCondVisibility("Window.IsMedia"):
                        url['nextpage'] = 'True'

                    # Format label of items
                    if i.get('info') == 'all_items':  # Special format info=all_items even if in specific type category
                        label = i.get('name').format(utils.type_convert(t, 'plural'), ' ')
                    elif self.params.get('info') in ['dir_movie', 'dir_tv', 'dir_person']:
                        label = i.get('name').format('', '')
                    else:
                        label = i.get('name').format(utils.type_convert(t, 'plural'), ' ')

                    listitem = ListItem(label=label, icon=i.get('icon', '').format(self.addonpath))
                    listitem.set_url_props(self.params, 'container')
                    listitem.set_url_props(url, 'item')
                    listitem.create_listitem(self.handle, **url)
        if finish:
            self.finish_container()

    def router(self):
        # FILTERS AND EXCLUSIONS
        self.tmdb.filter_key = self.params.get('filter_key', None)
        self.tmdb.filter_value = utils.split_items(self.params.get('filter_value', None))[0]
        self.tmdb.exclude_key = self.params.get('exclude_key', None)
        self.tmdb.exclude_value = utils.split_items(self.params.get('exclude_value', None))[0]

        # ROUTER LIST FUNCTIONS
        if self.params.get('info') == 'play':
            self.list_play()
        elif self.params.get('info') == 'textviewer':
            self.textviewer(xbmc.getInfoLabel('ListItem.Label'), xbmc.getInfoLabel('ListItem.Plot'))
        elif self.params.get('info') == 'imageviewer':
            self.imageviewer(xbmc.getInfoLabel('ListItem.Icon'))
        elif self.params.get('info') in ['details', 'refresh']:
            self.list_getid()
            self.list_traktmanagement()
            self.list_details()
        elif self.params.get('info') == 'flatseasons':
            self.list_getid()
            self.list_flatseasons()
        elif self.params.get('info') in constants.RANDOM_LISTS:
            self.list_random()
        elif self.params.get('info') in constants.RANDOM_TRAKT:
            self.list_traktrandom()
        elif self.params.get('info') == 'user_discover':
            self.list_userdiscover()
        elif self.params.get('info') == 'discover':
            self.translate_discover()
            self.list_tmdb()
        elif self.params.get('info') in ['cast', 'crew']:
            self.list_getid()
            self.list_credits(self.params.get('info'))
        elif self.params.get('info') == 'search':
            self.list_search()
        elif self.params.get('info') == 'dir_search':
            self.list_searchdir()
        elif self.params.get('info') == 'dir_discover':
            self.list_discoverdir()
        elif self.params.get('info') == 'library_nextaired':
            self.list_calendar(trakt=False)
        elif self.params.get('info') == 'trakt_becauseyouwatched':
            self.list_becauseyouwatched()
        elif self.params.get('info') == 'trakt_becausemostwatched':
            self.list_becauseyouwatched(mostwatched=True)
        elif self.params.get('info') == 'all_items':
            self.list_complete()
        elif self.params.get('info') in constants.TMDB_LISTS:
            self.list_getid()
            self.list_tmdb()
        elif self.params.get('info') in constants.TRAKT_HISTORY:
            self.list_trakthistory()
        elif self.params.get('info') == 'trakt_upnext':
            self.list_getid()
            self.list_traktupnext()
        elif self.params.get('info') == 'trakt_calendar':
            self.list_calendar(trakt=True)
        elif self.params.get('info') == 'trakt_collection':
            self.list_traktcollection()
        elif self.params.get('info') in constants.TRAKT_USERLISTS:
            self.list_traktuserlists()
        elif self.params.get('info') in constants.TRAKT_LISTS:
            self.list_trakt()
        elif not self.params or self.params.get('info') in constants.BASEDIR_PATH:
            self.list_basedir()
