class Favourites:
    def __init__(self, path):
        """ Initializes a Favourites class that can be use to show, add and delete favourites.

        :param str path: The path to store the favourites file

        """

        self.__filePattern = "%s-%s.xotfav"
        self.__pickler = Pickler()

        self.FavouriteFolder = path

    def add(self, channel, item, action_url):
        """ Adds a favourite for a specific channel.

        :param channel:       The channel
        :param item:          The mediaitem
        :param str action_url:     The mediaitem's actionUrl

        """

        Logger.debug("Adding item %s\nfor channel %s\n%s", item, channel,
                     action_url)
        file_name = self.__filePattern % (channel.guid, item.guid)
        file_path = os.path.join(self.FavouriteFolder, file_name)
        pickle = self.__pickler.pickle_media_item(item)

        # Just double check for folder existence
        if not os.path.isdir(self.FavouriteFolder):
            os.makedirs(self.FavouriteFolder)

        # replacing to pickle in the actionUrl to save space
        action_url = self.__remove_pickle(action_url)

        try:
            with io.open(file_path, mode='w', encoding='utf-8') as file_handle:
                file_handle.write(
                    "%s\n%s\n%s\n%s" %
                    (channel.channelName, item.name, action_url, pickle))
        except:
            Logger.error("Error saving favourite", exc_info=True)
            raise
        return

    # noinspection PyUnusedLocal
    def remove(self, item):
        """ Adds a favourite for a specific channel

        :param item:          The mediaitem

        """

        path_mask = os.path.join(self.FavouriteFolder,
                                 "*-%s.xotfav" % (item.guid, ))

        Logger.debug("Removing favourites for mask: %s", path_mask)
        for fav in glob.glob(path_mask):
            Logger.trace("Removing item %s\nFileName: %s", item, fav)
            os.remove(fav)
        return

    def list(self, channel=None):
        """ Lists favourites. If a channel was specified it will limit them to that.

        :param channel: The channel to limit the favourites to.

        :return: A list of tupples (action_url, pickle)
        :rtype: list

        """

        favs = []

        if channel:
            path_mask = os.path.join(self.FavouriteFolder,
                                     "%s-*.xotfav" % (channel.guid, ))
        else:
            path_mask = os.path.join(self.FavouriteFolder, "*.xotfav")

        Logger.debug("Fetching favourites for mask: %s", path_mask)
        for fav in glob.glob(path_mask):
            Logger.trace("Fetching %s", fav)

            try:
                with io.open(fav, mode='r', encoding='utf-8') as file_handle:
                    channel_name = file_handle.readline().rstrip()
                    name = file_handle.readline().rstrip()
                    action_url = file_handle.readline().rstrip()
                    if "pickle=" in action_url and "pickle=%s" not in action_url:
                        # see issue https://bitbucket.org/basrieter/xbmc-online-tv/issues/1037
                        Logger.debug(
                            "Found favourite with full pickle, removing the pickle as we should use the one from the file."
                        )
                        action_url = self.__remove_pickle(action_url)

                    pickle = file_handle.readline()
            except:
                Logger.error("Error fetching favourite", exc_info=True)
                raise

            if channel_name == "" or name == "" or action_url == "" or pickle == "":
                Logger.error(
                    "Apparently the file had too few lines, corrupt Favourite, removing it:\n"
                    "Pickle: %s\n"
                    "Channel: %s\n"
                    "Item: %s\n"
                    "ActionUrl: %s\n"
                    "Pickle: %s", fav, channel_name, name, action_url, pickle)

                # Remove the invalid favourite
                os.remove(fav)
                continue

            Logger.debug("Found favourite: %s", name)
            try:
                item = self.__pickler.de_pickle_media_item(pickle)
            except Exception:
                Logger.error("Cannot depickle item.", exc_info=True)
                # Let's not remove them for now. Just ignore.
                # os.remove(fav)
                continue

            validation_error = self.__pickler.validate(
                item, logger=Logger.instance())
            if validation_error:
                Logger.error(
                    "Invalid Pickled Item: %s\nRemoving favourite: %s",
                    validation_error, fav)

                # Remove the invalid favourite
                os.remove(fav)
                continue

            # add the channel name
            if channel is None:
                item.name = "%s [%s]" % (item.name, channel_name)

            item.clear_date()

            item.actionUrl = action_url % (pickle, )
            favs.append(item)
        return favs

    def __remove_pickle(self, action_url):
        pickle = Regexer.do_regex("pickle=([^&]+)", action_url)
        if not pickle:
            return action_url

        return action_url.replace(pickle[0], "%s")
class ParameterParser(object):
    def __init__(self, addon_name, params):
        """

        :param str addon_name:  The name of the add-on
        :param str params:      The parameteters used to start the ParameterParser

        """

        Logger.debug("Parsing parameters from: %s", params)

        # Url Keywords
        self.keywordPickle = "pickle"  # : Used for the pickle item
        self.keywordAction = "action"  # : Used for specifying the action
        self.keywordChannel = "channel"  # : Used for the channel
        self.keywordChannelCode = "channelcode"  # : Used for the channelcode
        self.keywordCategory = "category"  # : Used for the category
        self.keywordRandomLive = "rnd"  # : Used for randomizing live items
        self.keywordSettingId = "settingid"  # : Used for setting an encrypted setting
        self.keywordSettingActionId = "settingactionid"  # : Used for passing the actionid for the encryption
        self.keywordSettingName = "settingname"  # : Used for setting an encrypted settings display name
        self.keywordSettingTabFocus = "tabfocus"  # : Used for setting the tabcontrol to focus after changing a setting
        self.keywordSettingSettingFocus = "settingfocus"  # : Used for setting the setting control to focus after changing a setting
        self.keywordLanguage = "lang"  # : Used for the 2 char language information
        self.keywordProxy = "proxy"  # : Used so set the proxy index
        self.keywordLocalIP = "localip"  # : Used to set the local ip index

        # Url Actions
        self.actionFavourites = "favourites"  # : Used to show favorites for a channel
        self.actionAllFavourites = "allfavourites"  # : Used to show all favorites
        self.actionRemoveFavourite = "removefromfavourites"  # : Used to remove items from favorites
        self.actionAddFavourite = "addtofavourites"  # : Used to add items to favorites
        self.actionDownloadVideo = "downloadVideo"  # : Used to download a video item
        self.actionPlayVideo = "playvideo"  # : Used to play a video item
        self.actionUpdateChannels = "updatechannels"  # : Used to update channels
        self.actionListFolder = "listfolder"  # : Used to list a folder
        self.actionListCategory = "listcategory"  # : Used to show the channels from a category
        self.actionConfigureChannel = "configurechannel"  # : Used to configure a channel
        self.actionSetEncryptionPin = "changepin"  # : Used for setting an application pin
        self.actionSetEncryptedValue = "encryptsetting"  # : Used for setting an application pin
        self.actionResetVault = "resetvault"  # : Used for resetting the vault
        self.actionPostLog = "postlog"  # : Used for sending log files to pastebin.com
        self.actionProxy = "setproxy"  # : Used for setting a proxy

        self.propertyRetrospect = "Retrospect"
        self.propertyRetrospectChannel = "RetrospectChannel"
        self.propertyRetrospectChannelSetting = "RetrospectChannelSettings"
        self.propertyRetrospectFolder = "RetrospectFolder"
        self.propertyRetrospectVideo = "RetrospectVideo"
        self.propertyRetrospectCloaked = "RetrospectCloaked"
        self.propertyRetrospectCategory = "RetrospectCategory"
        self.propertyRetrospectFavorite = "RetrospectFavorite"
        self.propertyRetrospectAdaptive = "RetrospectAdaptive"

        # determine the query parameters
        self.params = self.__get_parameters(params)
        self.pluginName = addon_name

        # We need a picker for this instance
        self._pickler = Pickler()

    def _create_action_url(self, channel, action, item=None, category=None):
        """ Creates an URL that includes an action.

        Arguments:
        channel : Channel -
        action  : string  -

        Keyword Arguments:
        item : MediaItem -

        :param ChannelInfo|Channel channel:     The channel object to use for the URL.
        :param str action:                      Action to create an url for
        :param MediaItem item:                  The media item to add
        :param str category:                    The category to use.

        :return: a complete action url with all keywords and values
        :rtype: str|unicode

        """

        if action is None:
            raise Exception("action is required")

        # catch the plugin:// url's for items and channels.
        if item is not None and item.url and item.url.startswith("plugin://"):
            return item.url

        if item is None and channel is not None and channel.uses_external_addon:
            return channel.addonUrl

        params = dict()
        if channel:
            params[self.keywordChannel] = channel.moduleName
            if channel.channelCode:
                params[self.keywordChannelCode] = channel.channelCode

        params[self.keywordAction] = action

        # it might have an item or not
        if item is not None:
            params[self.keywordPickle] = self._pickler.pickle_media_item(item)

            if action == self.actionPlayVideo and item.isLive:
                params[self.keywordRandomLive] = random.randint(10000, 99999)

        if category:
            params[self.keywordCategory] = category

        url = "%s?" % (self.pluginName, )
        for k in params.keys():
            url = "%s%s=%s&" % (url, k, params[k])

        url = url.strip('&')
        # Logger.Trace("Created url: '%s'", url)
        return url

    def __get_parameters(self, query_string):
        """ Extracts the actual parameters as a dictionary from the passed in querystring.
        This method takes the self.quotedPlus into account.

        :param str query_string:    The querystring

        :return: dict() of keywords and values.
        :rtype: dict[str,str|None]

        """
        result = dict()
        query_string = query_string.strip('?')
        if query_string == '':
            return result

        try:
            for pair in query_string.split("&"):
                (k, v) = pair.split("=")
                result[k] = v

            # if the channelcode was empty, it was stripped, add it again.
            if self.keywordChannelCode not in result:
                Logger.debug(
                    "Adding ChannelCode=None as it was missing from the dict: %s",
                    result)
                result[self.keywordChannelCode] = None
        except:
            Logger.critical("Cannot determine query strings from %s",
                            query_string,
                            exc_info=True)
            raise

        return result