'''
   YouTube plugin for XBMC
    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

import sys
import urllib
import cgi
try: import simplejson as json
except ImportError: import json

class YouTubePlayer():
    fmt_value = {
        5: "240p h263 flv container",
        18: "360p h264 mp4 container | 270 for rtmpe?",
        22: "720p h264 mp4 container",
        26: "???",
        33: "???",
        34: "360p h264 flv container",
        35: "480p h264 flv container",
        37: "1080p h264 mp4 container",
        38: "720p vp8 webm container",
        43: "360p h264 flv container",
        44: "480p vp8 webm container",
        45: "720p vp8 webm container",
        46: "520p vp8 webm stereo",
        59: "480 for rtmpe",
        78: "seems to be around 400 for rtmpe",
        82: "360p h264 stereo",
        83: "240p h264 stereo",
        84: "720p h264 stereo",
        85: "520p h264 stereo",
        100: "360p vp8 webm stereo",
        101: "480p vp8 webm stereo",
        102: "720p vp8 webm stereo",
        120: "hd720",
        121: "hd1080"
        }

    # YouTube Playback Feeds
    urls = {}
    urls['video_stream'] = "http://www.youtube.com/watch?v=%s&safeSearch=none"
    urls['embed_stream'] = "http://www.youtube.com/get_video_info?video_id=%s"
    urls['video_info'] = "http://gdata.youtube.com/feeds/api/videos/%s"

    def __init__(self):
        self.xbmcgui = sys.modules["__main__"].xbmcgui
        self.xbmcplugin = sys.modules["__main__"].xbmcplugin

        self.pluginsettings = sys.modules["__main__"].pluginsettings
        self.storage = sys.modules["__main__"].storage
        self.settings = sys.modules["__main__"].settings
        self.language = sys.modules["__main__"].language
        self.dbg = sys.modules["__main__"].dbg

        self.common = sys.modules["__main__"].common
        self.utils = sys.modules["__main__"].utils
        self.cache = sys.modules["__main__"].cache
        self.core = sys.modules["__main__"].core
        self.login = sys.modules["__main__"].login
        self.subtitles = sys.modules["__main__"].subtitles

    def playVideo(self, params={}):
        self.common.log(repr(params), 3)
        get = params.get

        (video, status) = self.buildVideoObject(params)

        if status != 200:
            self.common.log(u"construct video url failed contents of video item " + repr(video))
            self.utils.showErrorMessage(self.language(30603), video["apierror"], status)
            return False

        listitem = self.xbmcgui.ListItem(label=video['Title'], iconImage=video['thumbnail'], thumbnailImage=video['thumbnail'], path=video['video_url'])

        listitem.setInfo(type='Video', infoLabels=video)

        self.common.log(u"Playing video: " + repr(video['Title']) + " - " + repr(get('videoid')) + " - " + repr(video['video_url']))

        self.xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)

        if self.settings.getSetting("lang_code") != "0" or self.settings.getSetting("annotations") == "true":
            self.subtitles.addSubtitles(video)

        if (get("watch_later") == "true" and get("playlist_entry_id")):
            self.common.log(u"removing video from watch later playlist")
            self.core.remove_from_watch_later(params)

        self.storage.storeValue("vidstatus-" + video['videoid'], "7")

    def getInfo(self, params):
        get = params.get
        video = self.cache.get("videoidcache" + get("videoid"))
        if len(video) > 0:
            self.common.log(u"returning cache ")
            return (eval(video), 200)

        result = self.core._fetchPage({"link": self.urls["video_info"] % get("videoid"), "api": "true"})

        if result["status"] == 200:
            video = self.core.getVideoInfo(result["content"], params)

            if len(video) == 0:
                self.common.log(u"- Couldn't parse API output, YouTube doesn't seem to know this video id?")
                video = {}
                video["apierror"] = self.language(30608)
                return (video, 303)
        else:
            self.common.log(u"- Got API Error from YouTube!")
            video = {}
            video["apierror"] = result["content"]

            return (video, 303)

        video = video[0]
        self.cache.set("videoidcache" + get("videoid"), repr(video))
        return (video, result["status"])

    def selectVideoQuality(self, params, links):
        get = params.get

        print "links: " + repr(type(links).__name__)
        link = links.get
        video_url = ""

        self.common.log(u"")

        if get("action") == "download":
            hd_quality = int(self.settings.getSetting("hd_videos_download"))
            if (hd_quality == 0):
                hd_quality = int(self.settings.getSetting("hd_videos"))

        else:
            if (not get("quality")):
                hd_quality = int(self.settings.getSetting("hd_videos"))
            else:
                if (get("quality") == "1080p"):
                    hd_quality = 3
                elif (get("quality") == "720p"):
                    hd_quality = 2
                else:
                    hd_quality = 1

        # SD videos are default, but we go for the highest res
        if (link(35)):
            video_url = link(35)
        elif (link(59)):
            video_url = link(59)
        elif link(44):
            video_url = link(44)
        elif (link(78)):
            video_url = link(78)
        elif (link(34)):
            video_url = link(34)
        elif (link(43)):
            video_url = link(43)
        elif (link(26)):
            video_url = link(26)
        elif (link(18)):
            video_url = link(18)
        elif (link(33)):
            video_url = link(33)
        elif (link(5)):
            video_url = link(5)

        if hd_quality > 1:  # <-- 720p
            if (link(22)):
                video_url = link(22)
            elif (link(45)):
                video_url = link(45)
            elif link(120):
                video_url = link(120)
        if hd_quality > 2:
            if (link(37)):
                video_url = link(37)
            elif link(121):
                video_url = link(121)

        if link(38) and False:
            video_url = link(38)

        for fmt_key in links.iterkeys():
            if link(int(fmt_key)):
                if self.dbg:
                    text = repr(fmt_key) + " - "
                    if fmt_key in self.fmt_value:
                        text += self.fmt_value[fmt_key]
                    else:
                        text += "Unknown"

                    if (link(int(fmt_key)) == video_url):
                        text += "*"
                    self.common.log(text)
            else:
                self.common.log(u"- Missing fmt_value: " + repr(fmt_key))

        if hd_quality == 0 and not get("quality"):
            return self.userSelectsVideoQuality(params, links)

        if not len(video_url) > 0:
            self.common.log(u"- construct_video_url failed, video_url not set")
            return video_url

        if get("action") != "download":
            video_url += '|' + urllib.urlencode({'User-Agent':self.common.USERAGENT})

        self.common.log(u"Done")
        return video_url

    def userSelectsVideoQuality(self, params, links):
        levels =    [([37,121], u"1080p"),
                     ([22,45,120], u"720p"),
                     ([35,44], u"480p"),
                     ([18], u"380p"),
                     ([34,43],u"360p"),
                     ([5],u"240p"),
                     ([17],u"144p")]

        link = links.get
        quality_list = []
        choices = []

        for qualities, name in levels:
            for quality in qualities:
                if link(quality):
                    quality_list.append((quality, name))
                    break

        for (quality, name) in quality_list:
            choices.append(name)

        dialog = self.xbmcgui.Dialog()
        selected = dialog.select(self.language(30518), choices)

        if selected > -1:
            (quality, name) = quality_list[selected]
            return link(quality)

        return u""

    def checkForErrors(self, video):
        status = 200

        if "video_url" not in video or video[u"video_url"] == u"":
            status = 303
            vget = video.get
            if vget(u"live_play"):
                video[u'apierror'] = self.language(30612)
            elif vget(u"stream_map"):
                video[u'apierror'] = self.language(30620)
            else:
                video[u'apierror'] = self.language(30618)

        return (video, status)

    def buildVideoObject(self, params):
        self.common.log(repr(params))

        (video, status) = self.getInfo(params)

        if status != 200:
            video[u'apierror'] = self.language(30618)
            return (video, 303)

        video_url = self.subtitles.getLocalFileSource(params, video)
        if video_url:
            video[u'video_url'] = video_url
            return (video, 200)

        (links, video) = self.extractVideoLinksFromYoutube(video, params)

        if len(links) != 0:
            video[u"video_url"] = self.selectVideoQuality(params, links)
        elif "hlsvp" in video:
            #hls selects the quality based on available bitrate (adaptive quality), no need to select it here
            video[u"video_url"] = video[u"hlsvp"]
            self.common.log("Using hlsvp url %s" % video[u"video_url"])

        (video, status) = self.checkForErrors(video)

        self.common.log(u"Done")

        return (video, status)

    def removeAdditionalEndingDelimiter(self, data):
        pos = data.find("};")
        if pos != -1:
            self.common.log(u"found extra delimiter, removing")
            data = data[:pos + 1]
        return data

    def extractFlashVars(self, data):
        flashvars = {}
        found = False

        for line in data.split("\n"):
            if line.strip().find(";ytplayer.config = ") > 0:
                found = True
                p1 = line.find(";ytplayer.config = ") + len(";ytplayer.config = ") - 1
                p2 = line.rfind(";")
                if p1 <= 0 or p2 <= 0:
                    continue
                data = line[p1 + 1:p2]
                break

        data = self.removeAdditionalEndingDelimiter(data)

        if found:
            data = json.loads(data)
            flashvars = data["args"]

        self.common.log(u"flashvars: " + repr(flashvars), 2)
        return flashvars

    def scrapeWebPageForVideoLinks(self, result, video):
        self.common.log(u"")
        links = {}

        flashvars = self.extractFlashVars(result[u"content"])
        if not flashvars.has_key(u"url_encoded_fmt_stream_map"):
            return links

        if flashvars.has_key(u"ttsurl"):
            video[u"ttsurl"] = flashvars[u"ttsurl"]

        if flashvars.has_key(u"hlsvp"):                               
            video[u"hlsvp"] = flashvars[u"hlsvp"]    

        for url_desc in flashvars[u"url_encoded_fmt_stream_map"].split(u","):
            url_desc_map = cgi.parse_qs(url_desc)
            self.common.log(u"url_map: " + repr(url_desc_map), 2)
            if not (url_desc_map.has_key(u"url") or url_desc_map.has_key(u"stream")):
                continue

            key = int(url_desc_map[u"itag"][0])
            url = u""
            if url_desc_map.has_key(u"url"):
                url = urllib.unquote(url_desc_map[u"url"][0])
            elif url_desc_map.has_key(u"conn") and url_desc_map.has_key(u"stream"):
                url = urllib.unquote(url_desc_map[u"conn"][0])
                if url.rfind("/") < len(url) -1:
                    url = url + "/"
                url = url + urllib.unquote(url_desc_map[u"stream"][0])
            elif url_desc_map.has_key(u"stream") and not url_desc_map.has_key(u"conn"):
                url = urllib.unquote(url_desc_map[u"stream"][0])

            if url_desc_map.has_key(u"sig"):
                url = url + u"&signature=" + url_desc_map[u"sig"][0]
            elif url_desc_map.has_key(u"s"):
                sig = url_desc_map[u"s"][0]
                url = url + u"&signature=" + self.decrypt_signature(sig)

            links[key] = url

        return links

    def decrypt_signature(self, s):
        ''' use decryption solution by Youtube-DL project '''
        if len(s) == 88:
            return s[48] + s[81:67:-1] + s[82] + s[66:62:-1] + s[85] + s[61:48:-1] + s[67] + s[47:12:-1] + s[3] + s[11:3:-1] + s[2] + s[12]
        elif len(s) == 87:
            return s[62] + s[82:62:-1] + s[83] + s[61:52:-1] + s[0] + s[51:2:-1]
        elif len(s) == 86:
            return s[2:63] + s[82] + s[64:82] + s[63]
        elif len(s) == 85:
            return s[76] + s[82:76:-1] + s[83] + s[75:60:-1] + s[0] + s[59:50:-1] + s[1] + s[49:2:-1]
        elif len(s) == 84:
            return s[83:36:-1] + s[2] + s[35:26:-1] + s[3] + s[25:3:-1] + s[26]
        elif len(s) == 83:
            return s[52] + s[81:55:-1] + s[2] + s[54:52:-1] + s[82] + s[51:36:-1] + s[55] + s[35:2:-1] + s[36]
        elif len(s) == 82:
            return s[36] + s[79:67:-1] + s[81] + s[66:40:-1] + s[33] + s[39:36:-1] + s[40] + s[35] + s[0] + s[67] + s[32:0:-1] + s[34]

        else:
            self.common.log(u'Unable to decrypt signature, key length %d not supported; retrying might work' % (len(s)))

    def getVideoPageFromYoutube(self, get):
        login = "false"

        if self.pluginsettings.userHasProvidedValidCredentials():
            login = "true"

        page = self.core._fetchPage({u"link": self.urls[u"video_stream"] % get(u"videoid"), "login": login})

        if not page:
            page = {u"status":303}

        return page

    def isVideoAgeRestricted(self, result):
        error = self.common.parseDOM(result['content'], "div", attrs={"id": "watch7-player-age-gate-content"})
        self.common.log(repr(error))
        return len(error) > 0

    def extractVideoLinksFromYoutube(self, video, params):
        self.common.log(u"trying website: " + repr(params))
        get = params.get

        result = self.getVideoPageFromYoutube(get)
        if self.isVideoAgeRestricted(result):
            self.common.log(u"Age restricted video")
            if self.pluginsettings.userHasProvidedValidCredentials():
                self.login._httpLogin({"new":"true"})
                result = self.getVideoPageFromYoutube(get)
            else:
                self.utils.showMessage(self.language(30600), self.language(30622))

        if result[u"status"] != 200:
            self.common.log(u"Couldn't get video page from YouTube")
            return ({}, video)

        links = self.scrapeWebPageForVideoLinks(result, video)

        if len(links) == 0 and not( "hlsvp" in video ):
            self.common.log(u"Couldn't find video url- or stream-map.")

            if not u"apierror" in video:
                video[u'apierror'] = self.core._findErrors(result)

        self.common.log(u"Done")
        return (links, video)