#
# Copyright 2012 Henning Saul, Joern Schumacher
# Copyright 2021 Christian Prasch
#
# 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/>.
#

try: import json
except ImportError: import simplejson as json
import re, urllib.request, xbmc, xbmcaddon, time
from datetime import datetime
#import web_pdb
#web_pdb.set_trace()

# -- Constants ----------------------------------------------
ADDON_ID = 'plugin.video.tagesschau'
base_url = "https://www.tagesschau.de/api2u/"
base_url_json = "https://www.tagesschau.de/json/"

addon = xbmcaddon.Addon(id=ADDON_ID)
showage = addon.getSettingBool('ShowAge')
tt_listopt = addon.getSetting('tt_list')
result_count = int(addon.getSetting('result_count'))
hide_europadruck = addon.getSettingBool('hide_europadruck')
hide_wolkenfilm = addon.getSettingBool('hide_wolkenfilm')

class VideoContent(object):
    """Represents a single video or broadcast.

    Attributes:
        tsid: A String with the video's id
        title: A String with the video's title
        timestamp: A datetime when this video was broadcast
        imageurls: A dict mapping image variants Strings to their URL Strings
        videourls: A dict mapping video variant Strings to their URL Strings
        duration: An integer representing the length of the video in seconds
        description: A String describing the video content
    """
    def __init__(self, tsid, title, timestamp, videourls=None, imageurls=None, duration=None, description=""):
        """Inits VideoContent with the given values."""
        self.tsid = tsid
        self.title = title
        # datetime
        self.timestamp = timestamp
        # video/mediadata names mapped to urls
        self._videourls = videourls
        # image variant names mapped to urls
        self._imageurls = imageurls
        # duration in seconds
        self.duration = duration
        # description of content
        self.description = description

    def __eq__(self, other):
        return self.timestamp == other.timestamp

    def __lt__(self, other):
        return self.timestamp > other.timestamp

    def video_id(self):
        return self.tsid

    def video_url(self, quality):
        """Returns the video URL String for the given quality.

        Falls back to lower qualities if no corresponding video is found.

        Args:
            quality: One of 'S', 'M', 'L' or 'X'

        Returns:
            A URL String for the given quality or None if no URL could be found.

        Raises:
            ValueError: If the given quality is invalid
        """
        if (not quality in ['S', 'M', 'L', 'X']):
            raise ValueError("quality must be one of 'S', 'M', 'L', 'X'")

        videourl = None

        if quality == 'X':
            if "h264xl" in self._videourls:
                videourl = self._videourls.get("h264xl")
        if quality == 'L' or not videourl:
            if "h264m" in self._videourls:
                videourl = self._videourls.get("h264m")
        if quality == 'M' or not videourl:
            if "h264s" in self._videourls:
                videourl = self._videourls.get("h264s")
        if quality == 'S' or not videourl:
            if "h264s" in self._videourls:
                videourl = self._videourls.get("h264s")

        #nothing found if it is a livestream
        if videourl == None:
            if "adaptivestreaming" in self._videourls:
                videourl = self._videourls.get("adaptivestreaming")
            else:
                videourl = self._videourls

        return videourl

    def image_url(self):
        """Returns the URL String of the image for this video."""
        if "16x9-640" in self._imageurls:
            imageurl = self._imageurls.get("16x9-640")
        elif "16x9-960" in self._imageurls:
            imageurl = self._imageurls.get("16x9-960")
        elif "16x9-512" in self._imageurls:
            imageurl = self._imageurls.get("16x9-512")
        else:
            imageurl = self._imageurls
        return imageurl

    def fanart_url(self):
        """Returns the URL String of the highres image for this video."""
        return self.image_url()

    def __str__(self):
        """Returns a String representation for development/testing."""
        if(self.timestamp):
            tsformatted = self.timestamp.isoformat()
        else:
            tsformatted = str(None)
        s = "VideoContent(tsid=" + self.tsid + ", title='" + self.title + "', timestamp=" + tsformatted + ", "\
            "duration=" + str(self.duration) + ", videourl=" + str(self.video_url('L')) + ", "\
            "imageurl=" + str(self.image_url()) + ", description='" + str(self.description) + "')"
        return s.encode('utf-8', 'ignore')


class VideoContentParser(object):
    """Parses JSON/Python structure into VideoContent."""

    def parse_video(self, jsonvideo):
        """Parses the video JSON into a VideoContent object."""
        tsid = jsonvideo["sophoraId"]
        timestamp = self._parse_date(jsonvideo["date"])
        imageurls = {}
        imageurls = self._parse_image_urls(jsonvideo["teaserImage"]["imageVariants"])
        videourls = self.parse_video_urls(jsonvideo["streams"])
        duration = int(jsonvideo["tracking"][1]["length"])

        age = datetime.now() - timestamp
        if age.seconds > 3600:
            agestr = str(age.seconds//3600) + "h " + str(age.seconds // 60 % 60) +"min"
        else:
            agestr = str(age.seconds // 60 % 60) +"min"

        agostr = addon.getLocalizedString(30103)
        if agostr == "ago":
            agostr = agestr + " " + agostr
        else:
            agostr = agostr + " " + agestr

        if showage:
            title = agostr + ": " + jsonvideo["title"]
        else:
            title = jsonvideo["title"]

        description = agostr + "\n" + jsonvideo["title"]

        return VideoContent(tsid, title, timestamp, videourls, imageurls, duration, description)

    def parse_broadcast(self, jsonbroadcast, title="" ):
        """Parses the video JSON into a VideoContent object."""
        tsid = jsonbroadcast["sophoraId"]
        if( title == "" ):
            title = jsonbroadcast["title"]
        timestamp = self._parse_date(jsonbroadcast["date"])
        if(timestamp):
            title = title + timestamp.strftime(' vom %d.%m.%Y  %H:%M')
        imageurls = {}
        imageurls = self._parse_image_urls(jsonbroadcast["teaserImage"]["imageVariants"])
        videourls = self.parse_video_urls(jsonbroadcast["streams"])
        duration = int(jsonbroadcast["tracking"][1]["length"])
        description = title
        return VideoContent(tsid, title, timestamp, videourls, imageurls, duration, description)

    def parse_livestream(self, jsonlivestream):
        """Parses the video JSON into a VideoContent object."""
        tsid = jsonlivestream["sophoraId"]
        title = jsonlivestream["title"]

        if( "date" in jsonlivestream ): 
            timestamp = self._parse_date(jsonlivestream["date"])
        else:
            timestamp = datetime.now()
            
        if( title.lower() == "tagesschau" ):
            title = title + timestamp.strftime(' vom %d.%m.%Y  %H:%M')
            
        imageurls = {}
        imageurls = self._parse_image_urls(jsonlivestream["teaserImage"]["imageVariants"])
        videourls = self.parse_video_urls(jsonlivestream["streams"])
        duration = int(jsonlivestream["tracking"][1]["length"])
        description = title
        return VideoContent(tsid, title, timestamp, videourls, imageurls, duration, description)

    def parse_video_urls(self, jsonvariants):
        """Parses the video mediadata JSON into a dict mapping variant name to URL."""
        variants = {}
        for name, url in list(jsonvariants.items()):
            variants[name] = url
        return variants

    def parse_jsonurl_result(self, entry):
        """Parses the search result of the json-url  into a VideoContent object."""
        tsid = "0"
        title = entry["headline"]

        # workaround for strptime error in embedded python
        try:
            timestamp = datetime.strptime( entry["datetime"], "%d.%m.%Y • %H:%M")
        except TypeError:
            timestamp = datetime.fromtimestamp(time.mktime(time.strptime( entry["datetime"], "%d.%m.%Y • %H:%M" )))
        
        description = title
        
        # Get url of mp4 stream from HTML-page
        page = urllib.request.urlopen("https://www.tagesschau.de" + entry["url"]).read()
        found = re.search(b'<script type="application/ld\+json">(.*?"@type" : "VideoObject",.*?)</script>', page, flags=re.DOTALL)
        data = json.loads( found[1] )
        videourls = data["contentUrl"]
        imageurls = data["image"][0]["url"]

        found = re.search('PT(\d+)M(\d+)S', data["duration"])
        duration = int(found[1]) * 60 + int(found[2])
        
        return VideoContent(tsid, title, timestamp, videourls, imageurls, duration, description)

    def _parse_date(self, isodate):
        """Parses the given date in iso format into a datetime."""
        if(not isodate):
            return None
        # ignore time zone part
        isodate = isodate[:-6]
        return datetime(*list(map(int, re.split('[^\d]', isodate))))

    def _parse_image_urls(self, jsonvariants):
        """Parses the image variants JSON into a dict mapping variant name to URL."""
        variants = {}
        for name, url in list(jsonvariants.items()):
            variants[name] = url
        return variants


class VideoContentProvider(object):
    """Provides access to the VideoContent offered by the tagesschau JSON API."""

    def __init__(self):
        self._parser = VideoContentParser()

    def livestreams(self):
        """Retrieves the livestream(s) currently on the air.

            Returns:
                A list of VideoContent object for livestream(s) on the air.
        """
        videos = []

        url = base_url + "channels"
        data = json.loads( urllib.request.urlopen(url).read() )

        for jsonstream in data["channels"]:
            video = self._parser.parse_livestream(jsonstream)
            videos.append(video)

        return videos

    def latest_videos(self):
        """Retrieves the latest videos.

            Returns:
                A list of VideoContent items.
        """
        videos = []
        
        url = base_url + "news"
        data = json.loads( urllib.request.urlopen(url).read() )

        for jsonvideo in data["news"]:
            try:
                if( (jsonvideo["type"] == "video") and (jsonvideo["tracking"][0]["src"] == "tagesschau") ):
                    if( hide_europadruck and ("Europadruck" in jsonvideo["title"]) ):
                        pass
                    elif( hide_wolkenfilm and ("Wolkenfilm" in jsonvideo["title"]) ):
                        pass
                    else:
                        video = self._parser.parse_video(jsonvideo)
                        videos.append(video)
            except:
                pass

        return videos

    def latest_broadcasts(self):
        """Retrieves the latest broadcast videos.

            Returns:
                A list of VideoContent items.
        """
        videos = []

        url = base_url + "channels"
        data = json.loads( urllib.request.urlopen(url).read() )

        for jsonbroadcast in data["channels"]:
            try:
                if( ("date" in jsonbroadcast) and ("title" in jsonbroadcast) ):  # Filter out livestream which has no date
                    video = self._parser.parse_broadcast(jsonbroadcast)
                    videos.append(video)
            except:
                pass

        return videos

    def tagesschau_20(self):
        """Retrieves tagesschau 20:00 videos

            Returns:
                A list of VideoContent items.
        """
        videos = []

        data = self.search_jsonurl("tagesschau%2020:00%20Uhr")
        for entry in data["documentTypes"][0]["items"]:
            if "description" in entry:
                if entry["description"] == "tagesschau 20:00 Uhr":
                    video = self._parser.parse_jsonurl_result(entry)
                    videos.append(video)
                    
                    if len(videos) >= result_count:
                        break

        return videos

    def tagesthemen(self):
        """Retrieves tagesthemen videos

            Returns:
                A list of VideoContent items.
        """
        videos = []

        for page in range(2):        
            url = base_url + "search/?searchText=tagesthemen&pageSize=50&resultPage=" + str(page)
            data = json.loads( urllib.request.urlopen(url).read() )

            for jsonvideo in data["searchResults"]:
                try:
                    if( jsonvideo["type"] == "video" ):
                        length = int(jsonvideo["tracking"][1]["length"])
                        video = self._parser.parse_broadcast(jsonvideo)

                        if( tt_listopt == "0" ):
                            if( length >= 1100 ):
                                videos.append(video)
                        elif( tt_listopt == "1" ):
                            if( length < 1100 ):
                                videos.append(video)
                        else:
                            videos.append(video)
                except:
                    pass

        return videos

    def search_jsonurl( self, searchstr, documentType = "video" ):
        url = base_url_json + "/search/?searchText=" + searchstr + "&documentType=" + documentType
        data = json.loads( urllib.request.urlopen(url).read() )
        return data
