import java.text.SimpleDateFormat;
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.apache.commons.lang.*
import groovy.json.*

/**
 * RSS / Atom feed plugin for Serviio Media Server
 * Works with SvtPlay.se
 * 
 * On almost all pages in the SvtPlay site, you may append /rss.xml at the end 
 * to get an RSS feed for items on that page.
 *
 * Example:
 * Latest documentaries are found at:
 * http://www.svtplay.se/dokumentar/
 * 
 * The RSS feed for those items are at:
 * http://www.svtplay.se/dokumentar/rss.xml 
 *
 * Fixed in version 2: Accomodate for altered markup when extracting video json url.
 *                      Better support for various m3u8 formats in SvtPlayCommon.
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 */

class SvtPlayRss extends FeedItemUrlExtractor {

    final boolean debug = false;

    SvtPlayCommon svtPlayCommon = new SvtPlayCommon(debug)

    final RSS_RESOURCE_URL = '^http(?:s)?://www.svtplay.se/.*/rss.xml$'

    final int VERSION = 2;
        
    private static SimpleDateFormat dfHourMinute = new SimpleDateFormat( "HH:mm" )

    static main(args) {
        
        // The main method is a unit test of the plugin
        def TestUrlOrdinary = new URL("http://www.svtplay.se/dokumentar")        
        def TestUrlRss = new URL("http://www.svtplay.se/dokumentar/rss.xml")
        def TestUrlExternal = new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") 
        
        SvtPlayRss extractor = new SvtPlayRss()
        
        println "PluginName                  : " + extractor.getExtractorName();
        println "Plugin version              : " + extractor.getVersion();
        println "TestMatch Rss               : " + extractor.extractorMatches(TestUrlRss);
        println "TestMatch Ordinary              : " + extractor.extractorMatches(TestUrlOrdinary);
        println "TestMatch external (Google) : " + extractor.extractorMatches(TestUrlExternal);
    
        println ''
        
        assert extractor.extractorMatches( TestUrlRss )
        assert !extractor.extractorMatches( TestUrlOrdinary )
        assert !extractor.extractorMatches( TestUrlExternal )
        
         // Fake the extraction of rss feed items
        java.util.Map<String,URL> links = new java.util.HashMap<String,URL>()
        links.put('default', new URL('http://www.svtplay.se/video/5703074/lake-placid-1980-miracle-on-ice/lake-placid-1980-miracle-on-ice-avsnitt-1'))
        // links.put('thumbnail', new URL('http://foo.com/thumnailURL.png'))
        ContentURLContainer result = extractor.extractUrl(links, PreferredQuality.HIGH)
        println 'result.contentUrl: ' + result.contentUrl
        println 'result.thumbnailUrl: ' + result.thumbnailUrl
       
    }
    
    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return (resourceUrl ==~ RSS_RESOURCE_URL)
    }
    
    ContentURLContainer extractUrl(java.util.Map<String,URL> links, PreferredQuality requestedQuality) {
       // link
       URL videoArticleUrl = links.get("default")
       URL thumnailImageUrl = links.get("thumbnail")

       String strThumnailImageUrl = null
       if (thumnailImageUrl != null)
       {
           strThumnailImageUrl = thumnailImageUrl.toString()
       }
       
       if (debug) println 'videoArticleUrl: ' + videoArticleUrl
       log('SvtPlayRss.extractUrl, videoArticleUrl: ' + videoArticleUrl)
       for (Map.Entry<String, String> entry : links.entrySet())
       {
           if (debug) println entry.getKey() + "/" + entry.getValue()
           log.debug(entry.getKey() + "/" + entry.getValue());
       }   

       if (debug) println 'SvtPlayRss.extractUrl, strThumnailImageUrl: ' + strThumnailImageUrl
       log('SvtPlayRss.extractUrl, strThumnailImageUrl: ' + strThumnailImageUrl)
       return svtPlayCommon.extractUrl(videoArticleUrl, requestedQuality, strThumnailImageUrl, false, null)
        
    }    
    
}

/*
* Helper class, inherinting from AbstractUrlExtractor in order to get access to the shared log() method.
*
* This class will help extract stream URL:s from playable web page URL:S 
*/
class SvtPlayCommon extends AbstractUrlExtractor {

    boolean debug = true;
    
    private static SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mmz" )
    
    public SvtPlayCommon(boolean debug)
    {
        this.debug = debug
    }

    String getExtractorName() {
        return getClass().getName()
    }

    boolean extractorMatches(URL resourceUrl) {
        // Should never be called
        return (resourceUrl ==~'^http(s)?://www.svtplay.se/.*$') 
    }

    ContentURLContainer extractUrl(URL videoArticleUrl, PreferredQuality requestedQuality, String thumbnailUrl, boolean blnIsLive, Date releaseDate) {
            
        // Get the html page which displays the video widget
        def videoArticleContentHtml
        def connection = videoArticleUrl.openConnection()
        if(connection.responseCode == 200){
            videoArticleContentHtml = connection.content.text
            //if (debug) println 'videoArticleContentHtml: \n' + videoArticleContentHtml
        }
        else{
            if (debug) println 'connection.url: ' + connection.url + ', connection.responseCode: ' + connection.responseCode
            return null
        }

        // Parse the json resource url from the data-json attribute.
        def videoJsonMatcher = (videoArticleContentHtml =~ '(?s)<a [^>]*?id=\"player\\_[0-9]*\"[^>]*?data-json-href=\"([^\\"]*?)\"')
        // The above does not work as of 2016-03-11. The value of the data-json-href attribute does NOT contain a link to the JSON file.
        // So, we set it to NULL and  go into the else-clause below.
        videoJsonMatcher = null
        
        def videoJsonPath
        def videoJsonUrl
        if (videoJsonMatcher != null && videoJsonMatcher.size() > 0)
        {
            if (debug) println ' videoJsonMatcher[0][1]: ' +  videoJsonMatcher[0][1]
            videoJsonUrl = new URL('http://www.svtplay.se' + videoJsonMatcher[0][1] + '?output=json')
            if (debug) println 'videoJsonUrl: ' + videoJsonUrl
        }
        else
        {
            //<a id="player" class="svtplayer playJsRemotePlayer playSvtplayer" data-id="547626"
            def videoArticleMatcher = (videoArticleContentHtml =~ '(?s)<a [^>]*?id=\"player\\_[0-9]*\"[^>]*?data-id=\"([^\\"]*?)\"')
            def videoArticleId
            assert videoArticleMatcher != null
            if(videoArticleMatcher.size() > 0)
            {
                videoArticleId = videoArticleMatcher[0][1]
                log(' videoArticleId - ' + videoArticleId)
                if (debug) println ' videoArticleId - ' + videoArticleId
            }
            else
            {
            
                log(' No video article id or json href found')
                if (debug) println ' No video article id found'
                return null
            }
            videoJsonUrl = new URL('http://www.svtplay.se/video/' + videoArticleId + '?output=json')
        }
        log('videoJsonUrl: ' + videoJsonUrl)
        if (debug) println 'videoJsonUrl: ' + videoJsonUrl
        def videoJsonContent
        def connectionJson = videoJsonUrl.openConnection()
        if(connectionJson.responseCode == 200){
            videoJsonContent = connectionJson.content.text
        }
        else{
            return null
        }
        log(' videoJsonContent: ' + videoJsonContent)
//        println ' videoJsonContent: ' + videoJsonContent

        // Find the url of the script from which we can parse the swfUrl
        // <script type="text/javascript" src="/public/2012.65/javascripts/script-built.js"></script>
        def swfUrl
        def scriptPathMatcher = (videoArticleContentHtml =~ '<script [^>]* src="([^\\"]*?\\/script-built\\.js)"')
        def scriptPath
        if(scriptPathMatcher != null && scriptPathMatcher.size() > 0)
        {
            scriptPath = scriptPathMatcher[0][1]
            log(' js scriptPath: ' + scriptPath)
            def scriptContent
            URL scriptUrl = new URL('http://www.svtplay.se' + scriptPath)
            def scriptConnection = scriptUrl.openConnection()
            if(scriptConnection.responseCode == 200){
                scriptContent = scriptConnection.content.text
                // svtplayer.SVTPlayer.SWF_PATH="/statiskt/swf/video/";svtplayer.SVTPlayer.SPRITE_PATH="/statiskt/skins/svt/css/img/svtplayer/";svtplayer.SVTPlayer.PLAYER_FILE="svtplayer-2012.48.swf";svtplayer.SVTPlayer.BUILD_VERSION="2012.48";    
                def swfUrlMatcher = scriptContent =~ 'svtplayer\\.SVTPlayer\\.PLAYER_SWF="([^\\"]*?)"'
                if(swfUrlMatcher != null && swfUrlMatcher.size() > 0)
                {
                    swfUrl = 'http://www.svtplay.se' + swfUrlMatcher[0][1] 
                    log(' swfUrl: ' + swfUrl)
                }
            }
        }
        else
        {
           log( ' Found no path to js script')
        }        

        def contentUrl = GetBestMatch(videoJsonContent, requestedQuality, videoArticleUrl)  
        if (contentUrl == null)
            return null
                              
//        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = '"' + contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1 pageUrl=' + videoArticleUrl + '"'
        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1'
        
        
//        println 'blnIsLive: ' + blnIsLive
        
        if (thumbnailUrl == null || thumbnailUrl.length() == 0) {
            // <meta property="og:image" content="http://www.svt.se/barnkanalen/cachable_image/1401757500000/incoming/article45810.svt/ALTERNATES/extralarge/default_title"/>
            def matcherLogo = (videoArticleContentHtml =~ '(?s)<meta property="og:image" content="([^"]*?)"')
            if(matcherLogo.size() > 0)
            {
                thumbnailUrl = matcherLogo[0][1]
                if (!thumbnailUrl.contains("http://")) thumbnailUrl = "http://www.svtplay.se" + thumbnailUrl
                
                // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                if (thumbnailUrl != null) {
                    thumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(thumbnailUrl)
                }
            }
        }
        if (thumbnailUrl != null) {
            thumbnailUrl = (thumbnailUrl =~ '(/ALTERNATES/extralarge/)').replaceAll('/ALTERNATES/medium/')
        }

        
        ContentURLContainer container =  new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, live: blnIsLive)
        if (blnIsLive) {
            // Set a cache key and mark this as expired on its startTime
            def strCacheKey = "" + videoArticleUrl + '_' + requestedQuality
//            println 'strCacheKey: ' + strCacheKey
            container.cacheKey = strCacheKey
            container.expiresOn = releaseDate
        }
        return container
    }

    String GetBestMatch(String jsonContent, PreferredQuality requestedQuality, URL videoUrl){
        String match = null
        String[] priorityList
        if(requestedQuality == PreferredQuality.HIGH){
            priorityList = ['e','d','c','b','a']
        }else{
            if(requestedQuality == PreferredQuality.MEDIUM){
                priorityList = ['c','b','d','a','e']
            }else{
                // LOW
                priorityList = ['a','b','c','d','e']
            }
        }       
        
        // example of match: rtmp://fl11.c91005.cdn.qbrick.com/91005/_definst_/kluster/20110922/PG-1146034-001A-MITTINATUREN2-02-mp4-e-v1.mp4 
        priorityList.each{
            if(match == null){
                def matcher = (jsonContent =~ '.*"url"\\:"(rtmp.*-'+it+'-v1.*?)","bitrate"\\:')
                if(matcher.size() > 0)
                {
                    match = matcher[0][1]
                    log(' found qbrick rtmp url. Best match for requested quality: ' + match)
                }
            }
        }
        
        if (match == null)
        {
            // This might be an AKAMAI video.
            // {"url":"http://svtplay1n-f.akamaihd.net/z/world/open/20121113/1308899-062A_ABC/REGIONALA_NYHET-062A-abc-4fc14b301c7a9fe6_,900,320,420,620,1660,2760,.mp4.csmil/manifest.f4m","bitrate":0,"playerType":"flash"}
            // {"url":"http://svtplay9i-f.akamaihd.net/i/world/open/20121230/1130774-003A/OLOF_PALME-003A-13e3178289b3be47_,900,320,420,620,1660,2760,.mp4.csmil/master.m3u8","bitrate":0,"playerType":"ios"}
            
                // We prefer the hls stream (ios) because ffmpeg has support for it and it is never encrypted
                def httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"ios"\\}')
                // the flash version is more likely to be unplayable by ffmpeg, but we can try it.
                if(httpMatcher == null || httpMatcher.size() == 0)
                    httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"flash"\\}')
                if(httpMatcher.size() > 0)
                {

                    String[] priorityListHttp
                    if(requestedQuality == PreferredQuality.HIGH){
                        priorityListHttp = ['1280x720','1024x576','768x432','704x396','576x324','512x288','480x270','320x180']
                    }else{
                        if(requestedQuality == PreferredQuality.MEDIUM){
                            priorityListHttp = ['768x432','704x396','576x324','1024x576','512x288','480x270','1280x720','320x180']
                        }else{
                            // LOW
                            priorityListHttp = ['320x180','480x270','512x288','576x324','704x396','768x432','1024x576', '1280x720']
                        }
                    }       

                    def httpMatch = httpMatcher[0][1]
                    if (debug)  println 'httpMatch: ' + httpMatch
                    
                    if (httpMatch.contains("m3u8"))
                    {
//                        println 'httpMatch är en m3u8-fil'
                        def m3u8URL = new URL(httpMatch)
                        def m3u8Connection = m3u8URL.openConnection()
                        if (m3u8Connection.responseCode == 200){
                            def  m3u8Content = m3u8Connection.content.text
                            //if (debug) println 'm3u8Content: ' + m3u8Content
                            List m3u8Lines = m3u8Content.readLines()
                            priorityListHttp.each{
                                if(match == null){
                                    if (debug) println 'priority resolution: ' + it
                                    for(int index = 0; index < m3u8Lines.size() - 1; index++)
                                    {
                                        //if (debug)  println '      ' + index + ': ' + m3u8Lines.get(index);
                                        def httpm3u8QualityMatcher = (m3u8Lines.get(index) =~ '#EXT-X-STREAM-INF\\:.*,RESOLUTION=' + it +',')
                                        if(httpm3u8QualityMatcher.size() > 0 )
                                        {
                                            match = m3u8Lines.get(index+1)
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found m3u8 http stream url. Best match for requested quality: ' + match)
                                            if (debug)  println ' found m3u8 http stream url. Best match for requested quality: ' + match
                                        }
                                    }
                                }                                    
                            }
                        }
                        
                        // Make sure the contentURl is absolute
                        if (match != null)
                        {
                            if (!match.startsWith('http') && !match.startsWith('rtmp'))
                            {
                                if (debug) println('match is relative. Attempt to parse base url')
                                def m3u8Position = httpMatch.indexOf("m3u8")
                                if (debug) println('m3u8Position: ' + m3u8Position)
                                def baseM3u8UrlEndPosition = httpMatch.lastIndexOf("/", m3u8Position)
                                if (debug) println('baseM3u8UrlEndPosition: ' + baseM3u8UrlEndPosition)
                                def baseM3u8Url = httpMatch.substring(0, baseM3u8UrlEndPosition + 1)
                                if (debug) println('baseM3u8Url: ' + baseM3u8Url)
                                match = baseM3u8Url + match
                                if (debug) println('match after being appended to base url: ' + match)
                            }
                        }                     
                    }
                    
                    if (match == null)
                    {
                        // Använd Pirate API
                    
                        // En bugg i pirateplay API:t gör att vi behöver filtrera bort ovidkommande klipp.
                        // Här hittar vi en mall för vad vi ska leta efter bland de strömmar som pirateplay föreslår
                        def httpMatchIntro = (httpMatch =~ /manifest\.f4m$/).replaceAll('')
                        httpMatchIntro = (httpMatchIntro =~ /master\.m3u8$/).replaceAll('')
                        //println 'httpMatchIntro: ' + httpMatchIntro
                    
                    
                        // Get the url from the pirateplayer API
                        def pirateURL = new URL('http://pirateplay.se/api/get_streams.js?url=' + java.net.URLEncoder.encode(videoUrl.toString()))
                        // println 'pirateURL: ' + pirateURL
                     
                        def pirateContent
                           
                        def pirateConnection = pirateURL.openConnection()
                        if (pirateConnection.responseCode == 200){
                            pirateContent = pirateConnection.content.text    
                            //println 'pirateContent: ' + pirateContent
                                            
                            priorityListHttp.each{
                                if(match == null){
                                    def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                    if(httpPirateMatcher.size() > 0)
                                    {
                                        for(int i=0; i < httpPirateMatcher.size(); i++)
                                        {
                                            //println 'httpPirateMatcher[i][1]: ' + httpPirateMatcher[i][1]
                                            if (httpMatchIntro.length() > 0 && httpPirateMatcher[i][1].startsWith(httpMatchIntro))
                                            {
                                                match = httpPirateMatcher[i][1]
                                                
                                                // clean up ugly stuff in the url
                                                match = (match =~ /\\?null=&/).replaceAll('')
                                                match = (match =~ /&id=$/).replaceAll('')
                                                log(' found pirateAPI http stream url based matching the svtplay json url entry. Best match for requested quality: ' + match)
                                                //println ' found pirateAPI http url based on svtplay json. Best match for requested quality: ' + match
                                             }
                                        }
                                    }
                                }
                            }
                            
                            if(match == null){
                                priorityListHttp.each{
                                    if(match == null){
                                        def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                        if(httpPirateMatcher.size() > 0)
                                        {
                                            match = httpPirateMatcher[0][1]
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found http pirateAPI stream url NOT based on svtplay json entry. Best match for requested quality: ' + match)
                                            //println ' found http pirate stream url NOT based on svtplay json entry. Best match for requested quality: ' + match
                                        }
                                    }
                                }
                            }
    
                            if (match == null) {
                                // The stream might be dynamic. Just grab the first one.
                                def defaultMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"')
                                if(defaultMatcher.size() > 0)
                                {
                                    match = defaultMatcher[0][1]
                                    
                                    // clean up ugly stuff in the url
    //                                match = (match =~ /\\?null=&/).replaceAll('')
    //                                match = (match =~ /&id=$/).replaceAll('')
                                    log(' found default http stream url, not matched for quality: ' + match)
                                    // Nota bene: this is most likely a f4m manifest, which is not playable in ffmpeg.
                                    // Need to work on hds decoding?
                                    // Ticket #1964 has been opened with ffmpeg!: 
                                    // https://ffmpeg.org/trac/ffmpeg/ticket/1964
                                }
    
                            }
                        }
                    }
                }
        }
                        
        return match
    }

    Date GetValidDate(strMarkupDate)
    {
        // Reformat date from html markup to parsable date string
        //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
        //things a bit.  Before we go on we have to repair this.

        //this is zero time so we need to add that TZ indicator for 
        if ( strMarkupDate.endsWith( "Z" ) ) {
            strMarkupDate = strMarkupDate.substring( 0, strMarkupDate.length() - 1) + "GMT-00:00";
        } else {
            int inset = 6;
        
            String s0 = strMarkupDate.substring( 0, strMarkupDate.length() - inset );
            String s1 = strMarkupDate.substring( strMarkupDate.length() - inset, strMarkupDate.length() );

            strMarkupDate = s0 + "GMT" + s1;
        }
        
        // println 're-structured strRelease: ' + strReleaseDate
        
        return df.parse( strMarkupDate );
    }    
}