import java.text.SimpleDateFormat;
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.apache.commons.lang.*
import groovy.json.*

/**
 * Web resource plugin for Serviio Media Server
 * Targeted at MTG sites tv3play, tv6play, tv8play, tv10play in different top domains
 * 
 * Add individual shows as Web Resource feeds, e.g.:
 * http://www.tv3play.se/program/blacklist
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 */
 class Tv3_6_8_10_Play extends org.serviio.library.online.WebResourceUrlExtractor {
    
    final boolean debug = false;

    Tv3_6_8_10_PlayCommon mtgPlayCommon = new Tv3_6_8_10_PlayCommon(debug)

    final VALID_RESOURCE_URL = '^http(?:s)?://play\\.(?:tv(?:3|6|8|10)|viasat4)\\.[a-z.]*?[a-z]{2,}/.*$'
    final RSS_RESOURCE_URL = '^http(?:s)?://play\\.tv(?:3|6|8|10)\\.[a-z.]*?[a-z]{2,}/rss/.*$'

    final int VERSION = 2;
        
    private static SimpleDateFormat dfHourMinute = new SimpleDateFormat( "HH:mm" )
    
    static main(args) {
    
        def TestUrlSe = new URL("http://www.tv3play.se/program/blacklist")
        def TestUrlEe = new URL("http://www.tv3play.ee/sisu/suure-paugu-teooria")
        def TestUrlLt = new URL("http://www.tv3play.lt/programos/adrenalinas")
        def TestUrlNo = new URL("http://www.viasat4play.no/programmer/seinfeld")
        def TestUrlDk = new URL("http://www.tv3play.dk/programmer/aktionen")
        
        def TestUrlRtmp = new URL("http://www.tv3play.se/program/svenska-hollywoodfruar/229271")


        def TestUrlRss = new URL("http://www.tv8play.se/rss/recent")
        def TestUrlRss2 = new URL("http://legacy.tv3play.se/rss/recent")
        def TestUrlExternal = new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") 
        
        Tv3_6_8_10_Play extractor = new Tv3_6_8_10_Play()

        assert extractor.extractorMatches( TestUrlSe )
        assert extractor.extractorMatches( TestUrlEe )
        assert extractor.extractorMatches( TestUrlLt )
        assert extractor.extractorMatches( TestUrlNo )
        assert extractor.extractorMatches( TestUrlDk )
        assert extractor.extractorMatches( TestUrlRtmp )
        assert !extractor.extractorMatches( TestUrlRss )
        assert !extractor.extractorMatches( TestUrlRss2 )
        assert !extractor.extractorMatches( TestUrlExternal )
        
        println "PluginName                  : " + extractor.getExtractorName();
        println "Plugin version              : " + extractor.getVersion();
        
        WebResourceContainer containerHttp = extractor.extractItems(TestUrlSe, -1);
        println 'containerHttp.getItems().size(): ' + containerHttp.getItems().size()
        ContentURLContainer result1Http = null
        if (containerHttp.getItems().size() > 0)
        {
            println ' containerHttp.getItems()[0].releaseDate: ' + containerHttp.getItems()[0].releaseDate
            result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.HIGH)
            println ' result1Http: ' + result1Http
            assert (result1Http != null)

            for (int index = 0; index < containerHttp.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerHttp.getItems()[index].title + ', episodeUrl: ' + containerHttp.getItems()[index].getAdditionalInfo()['episodeUrl']
            }
        }

        containerHttp = extractor.extractItems(TestUrlEe, 4);
        
        WebResourceContainer containerNo = extractor.extractItems(TestUrlNo, -1);
        println 'containerNo.getItems().size(): ' + containerNo.getItems().size()
        ContentURLContainer resultNo = null
        if (containerNo.getItems().size() > 0)
        {
            println ' containerNo.getItems()[0].releaseDate: ' + containerNo.getItems()[0].releaseDate
            resultNo = extractor.extractUrl(containerNo.getItems()[0], PreferredQuality.MEDIUM)
            // Geo restrictions will make this fail in Sweden
            // println 'resultNo.contentUrl: ' + resultNo.contentUrl
        }

        WebResourceContainer containerRtmp = extractor.extractItems(TestUrlRtmp, -1);
        println 'containerRtmp.getItems().size(): ' + containerRtmp.getItems().size()
        ContentURLContainer resultRtmp = null
        if (containerRtmp.getItems().size() > 0)
        {
            println ' containerRtmp.getItems()[0].releaseDate: ' + containerRtmp.getItems()[0].releaseDate
            resultRtmp = extractor.extractUrl(containerRtmp.getItems()[0], PreferredQuality.MEDIUM)
            println ' resultRtmp: ' + resultRtmp
        }
        if (containerRtmp.getItems().size() > 1)
        {
            println ' containerRtmp.getItems()[1].releaseDate: ' + containerRtmp.getItems()[1].releaseDate
            resultRtmp = extractor.extractUrl(containerRtmp.getItems()[1], PreferredQuality.LOW)
            println ' resultRtmp: ' + resultRtmp
        }
    
    }
    
    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return (resourceUrl ==~ VALID_RESOURCE_URL) && !(resourceUrl ==~ RSS_RESOURCE_URL)
    }
    
     WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
        log( getExtractorName() + ': extracting items for url: ' +  resourceUrl + ' , maxItemsToRetrieve: ' + maxItemsToRetrieve)
        def resourceHtml
        
        def connection = resourceUrl.openConnection()
        if(connection.responseCode == 200){
            resourceHtml = connection.content.text
        }
        else{
            return null
        }

        // find out the title
        def title
        
        //<title>The Blacklist - TV3 Play</title>
        def matcherTitle = (resourceHtml =~ '<title>(.*?)</title>')
        if(matcherTitle.size() > 0)
        {
            title = StringEscapeUtils.unescapeHtml(matcherTitle[0][1])
            if (debug) println 'title - ' + title
        }
        
        // Find a thumbnail image
        //<meta property="og:image:url" content="http://cdn.playapi.mtgx.tv/imagecache/250x250/inbox/469749/25b559464dc1d4e15732065be4363922.jpg"/>
        def thumbnailUrl
        def matcherLogo = (resourceHtml =~ '(?s)<meta property="og\\:image\\:url" content="([^"]*?)"')
        if (matcherLogo.size() > 0)
        {
            thumbnailUrl = matcherLogo[0][1]
                        
            // 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 (debug) println 'thumbnailUrl - ' + ((thumbnailUrl == null) ? 'null' : thumbnailUrl)
        }

        // Older shows may be streamed through the rtmp protocol.
        // See if we have an updated reference to the swf file, which must be passed as a parameter along woth the stream url.
        // data-flashplayer-url="http://flvplayer.viastream.viasat.tv/flvplayer/play/swf/MTGXPlayer-1.9.1.swf"
        def swfUrl        
        def swfUrlMatcher = (resourceHtml =~ 'data-flashplayer-url="([^"]*?)"')
        if (swfUrlMatcher != null && swfUrlMatcher.getCount() > 0)
        {
            swfUrl = swfUrlMatcher[0][1]
        }
        if (debug) println 'swfUrl: ' + swfUrl
        
        List<WebResourceItem> items = [] 
        // Parse items from markup, return only max items

        def strServiioEpisodesSectionHTML = ""
        def episodesSectionMatcher = (resourceHtml =~ '(?s)<section [^>]*?data-section="episodelist".*?</section>')
        if (episodesSectionMatcher != null && episodesSectionMatcher.getCount() > 0)
        {
            strServiioEpisodesSectionHTML = episodesSectionMatcher[0]
        }
    
        if (debug) 
        {
           // println 'strServiioEpisodesSectionHTML: ' + strServiioEpisodesSectionHTML 
        }
        
        def episodesMatcher = strServiioEpisodesSectionHTML =~ '(?s)<div class=\"clip [^>]*?>.*?<div class=\"clip-additional-info.*?</div>.*?</div>.*?</div>'
        if (debug) println ' episodesMatcher.getCount(): ' + episodesMatcher.getCount()
        def episodeTitle = ""
        def strEpisodeUrl = ""
        def strEpisodeThumbnailUrl = ""
        int itemsAdded = 0;        
        for (int i = 0; i < episodesMatcher.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
            //if (debug) println 'episodesMatcher[' + i + ']: ' + episodesMatcher[i]
            episodeTitle = ""
            strEpisodeUrl = ""
            strEpisodeThumbnailUrl = ""
            def episodeTitleMatcher = (episodesMatcher[i] =~ 'data-tracking-event-label="[^-]*? - ([^-]*?) -')
            if (episodeTitleMatcher != null && episodeTitleMatcher.getCount() > 0)
            {
                episodeTitle =  org.apache.commons.lang.StringEscapeUtils.unescapeHtml(episodeTitleMatcher[0][1])
            }
            if (debug) println ' episodeTitle[' + i + ']: ' + episodeTitle
            
            
            def episodeUrlMatcher = (episodesMatcher[i] =~ '(?s)<a[^>]*?href=\"([^\"]*?)\"[^>]*?data-tracking-event-action=\"play\\.videopage')
            if (episodeUrlMatcher != null && episodeUrlMatcher.getCount() > 0)
            {
                strEpisodeUrl = episodeUrlMatcher[0][1]
            }
            if (debug) println ' strEpisodeUrl[' + i + ']: ' + strEpisodeUrl

//            <div class="clip-image">
//                <img class="lazyload" src="http://cdn.playstatic.mtgx.tv/static/ui/img/clip-small-placeholder.png" data-src="http://cdn.playapi.mtgx.tv/imagecache/230x150/inbox/469749/25b559464dc1d4e15732065be4363922.jpg"/>
//            </div>
            def episodeThumbnailUrlMatcher = (episodesMatcher[i] =~ '(?s)<div class=\"clip-image\">.*?<img [^>]*?data-src=\"([^\"]*?)\"')
            if (episodeThumbnailUrlMatcher != null && episodeThumbnailUrlMatcher.getCount() > 0)
            {
                strEpisodeThumbnailUrl = episodeThumbnailUrlMatcher[0][1]
            }
            if (debug) println ' strEpisodeThumbnailUrl[' + i + ']: ' + strEpisodeThumbnailUrl
            
            WebResourceItem item = new WebResourceItem(title: episodeTitle, additionalInfo: ['episodeUrl': strEpisodeUrl, 'thumbnailUrl': strEpisodeThumbnailUrl, 'live': 'false', 'swfUrl': swfUrl])
            items << item
            itemsAdded++             
        }
        def webResourceContainer = new WebResourceContainer()
        webResourceContainer.title = title
        webResourceContainer.thumbnailUrl = thumbnailUrl
        webResourceContainer.items = items
        
        log( getExtractorName() + ': finished extracting items for url: ' +  resourceUrl + ' . Found ' + itemsAdded + ' items' )
        if (debug) println  getExtractorName() + ': finished extracting items for url: ' +  resourceUrl + ' . Found ' + itemsAdded + ' items'
        return webResourceContainer
     }
    
    ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
    
        log ( getExtractorName() + ': extracting Url for WebResourceItem at ' + item.getAdditionalInfo()['videoArticleUrl'])
        boolean blnIsLive = false
        def strIsLive = item.getAdditionalInfo()['live']
        if (strIsLive != null && strIsLive.equals("true"))
            blnIsLive = true

        return mtgPlayCommon.extractUrl(new URL(item.getAdditionalInfo()['episodeUrl']), requestedQuality, item.getAdditionalInfo()['thumbnailUrl'], blnIsLive, item.releaseDate, item.getAdditionalInfo()['swfUrl'])
    }    
}

/*
* Helper class for Web resource plugin as well as RSS feed plugin, 
* inherinting from AbstractUrlExtractor in order to get access to the shared log() method.
* Targeted at MTG sites tv3play, tv6play, tv8play, tv10play in different top domains.
*
* This class will help extract stream URL:s from playable web page URL:s as well as from 
* links grabbed from RSS feeds.
*
* Due to problems importing classes between groovy files,
* a copy of this class also resides in the RSS feed plugin file.
*/
class Tv3_6_8_10_PlayCommon extends AbstractUrlExtractor {

    final VALID_RESOURCE_URL = '^http(?:s)?://(?:play\\.)?(?:tv(?:3|6|8|10)|viasat4)\\.[a-z.]*?[a-z]{2,}/.*$'
    final RSS_RESOURCE_URL = '^http(?:s)?://(?:play\\.)?(?:tv(?:3|6|8|10)|viasat4)\\.[a-z.]*?[a-z]{2,}/rss/.*$'

    boolean debug = true;
    
    private static SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mmz" )
    
    public Tv3_6_8_10_PlayCommon(boolean debug)
    {
        this.debug = debug
    }

    String getExtractorName() {
        return getClass().getName()
    }

    boolean extractorMatches(URL resourceUrl) {
        // Should never be called
        return (resourceUrl ==~ VALID_RESOURCE_URL) && !(resourceUrl ==~ RSS_RESOURCE_URL)
    }

    ContentURLContainer extractUrl(URL videoArticleUrl, PreferredQuality requestedQuality, String thumbnailUrl, boolean blnIsLive, Date releaseDate, String swfUrl) {
        
        
        // The video article URL may be on the format http://www.tv3play.se/program/blacklist/466763?autostart=true
        // We want to grab the id
        def strVideoId = ""
        def idExtractorMatcher = (videoArticleUrl =~ 'http(?:s)?://(?:play\\.)?(?:tv(?:3|6|8|10)|viasat4)\\.[a-z.]*?[a-z]{2,}/.*/([0-9]*?)(?:\\?autostart=true$|$)')
        if (idExtractorMatcher != null && idExtractorMatcher.getCount() > 0)
        {
            strVideoId = idExtractorMatcher[0][1]
        }
        else
        {
            return null;
        }
        
        if (debug)
        {            
            println 'strVideoId: ' + strVideoId
        }
                
        // Parse the json resource url from the data-json attribute.
        def videoJsonUrl = new URL('http://playapi.mtgx.tv/v3/videos/' + strVideoId)

        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)
        if (debug)
        {
            println ' videoJsonContent: ' + videoJsonContent
        }
        
        def jsonSlurper = new JsonSlurper()
        def videoJsonObject = jsonSlurper.parseText(videoJsonContent);
        
        if (debug) println 'videoJsonObject.id: ' + videoJsonObject.id
        if (debug) println 'videoJsonObject._links.stream.href: ' + videoJsonObject._links.stream.href
        if (debug) println 'videoJsonObject.sami_path: ' + videoJsonObject.sami_path
        if (videoJsonObject.sami_path != null)
        {
            log ('Subtitles for episode can be found at: ' + videoJsonObject.sami_path)
        }
        
        def streamJsonContent
        def streamJsonUrl = new URL(videoJsonObject._links.stream.href)
        def streamJsonConnection = streamJsonUrl.openConnection()
        if(streamJsonConnection.responseCode == 200){
            streamJsonContent = streamJsonConnection.content.text
        }
        else{
            return null
        }
        if (debug) println 'streamJsonContent: ' + streamJsonContent
        
        def streamJsonObject = jsonSlurper.parseText(streamJsonContent)
        if (debug) println 'streamJsonObject.streams.hls: ' + streamJsonObject.streams.hls
        if (debug) println 'streamJsonObject.streams.high: ' + streamJsonObject.streams.high
        if (debug) println 'streamJsonObject.streams.medium: ' + streamJsonObject.streams.medium
        
        def strStreamUrl
        if (streamJsonObject.streams.hls != null)
        {
            strStreamUrl = streamJsonObject.streams.hls
        }
        else if (streamJsonObject.streams.high != null)
        {
            strStreamUrl = streamJsonObject.streams.high
        }
        else if (streamJsonObject.streams.medium != null)
        {
            strStreamUrl = streamJsonObject.streams.medium
        }
        if (debug) println 'strStreamUrl: ' + strStreamUrl
        
        def contentUrl = GetBestMatch(strStreamUrl, requestedQuality, swfUrl)  
        if (contentUrl == null)
            return null
         
        
        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
            container.cacheKey = strCacheKey
            container.expiresOn = releaseDate
        }
        return container
    }

    String GetBestMatch(String streamUrl, PreferredQuality requestedQuality, String swfUrl) {
        String match = null                                        
        if (streamUrl.contains("m3u8"))
        {
            def m3u8URL = new URL(streamUrl)
            def m3u8Connection = m3u8URL.openConnection()
            if (m3u8Connection.responseCode == 200){
                def  m3u8Content = m3u8Connection.content.text
                if (debug) println 'm3u8Content: ' + m3u8Content
                
                List m3u8LinesByBitrate = m3u8Content.readLines()
                def qualityList = []
                for(int index = 0; index < m3u8LinesByBitrate.size() - 1; index++)
                {
                    def m3u8QualityMatcher = (m3u8LinesByBitrate.get(index) =~ '#EXT-X-STREAM-INF.*?BANDWIDTH=(\\d+?)(?:[^\\d]|$)')
                    
                    if (m3u8QualityMatcher != null && m3u8QualityMatcher.size() > 0)
                    {
                        qualityList << ['url': m3u8LinesByBitrate.get(index+1), 'bitrate': m3u8QualityMatcher[0][1].toInteger()] 
                    }
                }
                if (qualityList.size() > 0)
                {
                    if (debug)
                    {
                        for (int listIndex = 0; listIndex < qualityList.size(); listIndex++)
                        {
                            println 'qualityList[' + listIndex + '].bitrate: ' + qualityList[listIndex].bitrate
                            println 'qualityList[' + listIndex + '].url: ' + qualityList[listIndex].url
                        }
                    }
                    int priorityIndex
                    if(requestedQuality == PreferredQuality.HIGH){
                        priorityIndex = 0
                    } else {
                        if (requestedQuality == PreferredQuality.MEDIUM){
                            priorityIndex = qualityList.size() / 2
                        } else {
                            // LOW
                            priorityIndex = qualityList.size() - 1
                        }
                    }
                    
                    if (debug) println 'priorityIndex: ' + priorityIndex
                    
                    qualityList.sort { a, b ->
                        // Compare bandwidth descending
                        b.bitrate <=> a.bitrate
                    }
                    match = qualityList[priorityIndex].url 
                    if (debug) println 'match: ' + match
                }
                
                if (match == null)
                {
                    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']
                        }
                    }
                                
                    List m3u8Lines = m3u8Content.readLines()
                    priorityListHttp.each{
                        
                        if(match == null)
                        {
                            for(int index = 0; index < m3u8Lines.size() - 1; 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('')                                    
                                }
                            }
                        }                                    
                    }
                }
            }
            if (match != null)
            {
                if (!(match ==~ 'http(?:s)?://.*'))
                {
                    def baseURLMatcher = streamUrl =~ '(http(?:s)?://.*?/)[^/]*$'
                    if (baseURLMatcher != null && baseURLMatcher.getCount() > 0)
                    {
                        match = baseURLMatcher[0][1] + match
                    }
                }
                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
            }
        }
        else 
        {
            if (swfUrl == null)
            {
                swfUrl = 'http://flvplayer.viastream.viasat.tv/flvplayer/play/swf/MTGXPlayer-1.9.1.swf'
            }
            def parts = streamUrl.split('/mp4:')
            
            if(parts.size() > 1)
            {
                match = parts[0] + '/ swfUrl=' + swfUrl + ' playpath=mp4:'+parts[1]+' swfVfy=1'
            }
            else 
            {
                match = streamUrl + ' swfUrl=' + swfUrl + ' swfVfy=1'
            }
        }

        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
        }
        
        if (debug) println 're-structured strMarkupDate: ' + strMarkupDate
        
        return df.parse( strMarkupDate )
    }    
}