import java.text.SimpleDateFormat;
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.apache.commons.lang.*

/**
 * SvtPlay.se content URL extractor plugin. 
 * 
 * ##############################################
 * Resource URL instructions
 * ##############################################
 * 
 * This is a plugin of the type "Web Resource" ("Webbresurs").
 * 
 * Find URLs for categories by navigating to your desired category from http://www.svtplay.se/kategorier
 * Right click in the "Senaste program" button and copy the shortcut to the clipboard. For documentaries
 * ths URL is http://www.svtplay.se/kategorier/dokumentar?tab=episodes&sida=1 This has 8 shows. Increase 
 * trailing 1 to a higher number to get more shows (multiply by 8).
 *
 * You can also add individual shows, links to which are found at:
 * http://www.svtplay.se/program
 *
 * ##############################################
 * Version history
 * ##############################################
 *
 * @version 1.1.3 
 *
 * Fixed in version 1.1: Properly parse publishing date.
 * Fixed in version 1.1: Return standard url from Pirate API even if not matching any of the quality criteria. (dynamic)
 * Fixed in version 1.1.2: Handle bug in the pirateplay API which sometimes returns the wrong stream urls bundled with the correct ones.
 *                         Added new getVersion() method to conform to updated plugin API
 *                         Restructured these instructions to remove focus from the pre Serviio 1.1 challenges.
 * 
 * Fixed in version 1.1.3: Remove dependency on Pirate API when m3u8 stream url can be parsed by plugin.
 *                         Avoid returning feed items pointing to the starting page of shows.

 * TODO: Add support for rss files which are still sparsely used at SVT.
 * TODO: Add support for user preference regarding shows vs clips, depending on URL request parameters
 * TODO: Explore support for live shows
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 * 
 * The plugin re-uses some of the extraction methods from the original svtplay implementation by Tomas Falemo
 * It relies on the Pirateplay API to get http-based stream urls, see http://pirateplay.se/
 *
 * ##############################################
 * Special instructions for serviio versions older 
 * than 1.1
 * ##############################################
 *
 * Most shows (e.g. the local news show ABC) are delivered as Akamai hls streams.
 * The way serviio 1.0.1 or below tried to find out media information for those streams was broken.
 * To get the content to work properly, use one of the  FFMpeg Wrappers (.bat on Windows or .sh on Linux)
 * provided in the svtplay-extras.zip archive.
 * Put the wrapper in your lib directory, and add a line to the file at bin/ServiioService.exe.vmoptions:
 * The new line should be:
 * -Dffmpeg.location=/path/to/serviio/lib/FFMPegwrapper.sh
 * On my Windows box:
 * -Dffmpeg.location="C:\Program Files\Serviio\lib\FFMPegWrapper.bat"
 * Edit a line at the top of the wrapper to properly set the path to the actual FFMPeg exe.
 *
 * NOTE: When upgrading Serviio in minor versions pre 1.1 (e.g. from 1.0.0 to 1.0.1) the ServiioService.exe.vmoptions
 * file will be overwritten in the upgrade process. Thus you will need to add the -Dffmpeg.location option again
 * after an upgrade.
 *
 * The plugin uses the org.apache.commons.lang.StringEscapeUtils class to convert escaped html entities
 * found in scraped web pages into proper characters. That library was be added in the Serviio 1.1
 * distribution. For older distributions, the jar file (included in the svtplay-extras.zip archive) will have to be added 
 * to the lib directory.
 *
 */
class SvtPlayWebResouceExtractor extends org.serviio.library.online.WebResourceUrlExtractor {
    final VALID_RESOURCE_URL = '^http(s)?://www.svtplay.se/.*$'


    final int VERSION = 3;
    
    private static SimpleDateFormat df    = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mmz" )

    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return resourceUrl ==~ VALID_RESOURCE_URL
    }

    WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
        log( getExtractorName() + ': extracting items for url: ' +  resourceUrl + ' , maxItemsToRetrieve: ' + maxItemsToRetrieve)
        def resourceHtml
        def url = new URL(''+resourceUrl)
        
        
        def connection = url.openConnection()
        if(connection.responseCode == 200){
            resourceHtml = connection.content.text
        }
        else{
            return null
        }
        
        // find out the title
        def title
        //<title>Barn | SVT Play</title>
        def matcherTitle = (resourceHtml =~ '<title>(.*?)</title>')
        if(matcherTitle.size() > 0)
        {
            title = StringEscapeUtils.unescapeHtml(matcherTitle[0][1])
            // println 'title - ' + title
        }
        
        // Find a category thumbnail by matching known categories
        def thumbnailUrl
        if (resourceUrl ==~ '.*/kategorier/barn.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/barn.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/dokumentar.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/dokumentar.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/filmochdrama.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/filmochdrama.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/kulturochnoje.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/kulturochnoje.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/nyheter.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/nyheter.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/samhalleochfakta.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/samhalleochfakta.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/sport.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/sport.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/os.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/os.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/oppetarkiv.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/oppetarkiv.png"
        }
        else if (resourceUrl ==~ '.*/kategorier/live.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/live.png"
        }
        else if (resourceUrl ==~ '^http(s)*://www.svtplay.se/program$')
        {
            // All programs. Use SVT Play logo.
            //<img alt="SVT Play" src="/public/2012.65/images/svt-play-2x.png">
            def matcherLogo = (resourceHtml =~ '<img alt="SVT Play" src="([^\\"]*?)">')
            if(matcherTitle.size() > 0)
            {
                thumbnailUrl = "http://www.svtplay.se" + matcherTitle[0][1]
            }
        }
        else
        {
            // Individual show page.
            // TODO
        }
        // println 'thumbnailUrl - ' + thumbnailUrl
        
        List<WebResourceItem> items = [] 
        // Parse items from markup, return only max items
        /*
              <article class="svtUnit svtNth-4 svtMediaBlock playPositionRelative playJsInfo-Core playJsInfo-Hover playIEFixedHeight"
                 data-title="ABC - 9/11 18:10"
                 data-description=""
                 data-length="5 min"
                 data-available="5 dagar kvar"
                 data-broadcasted="fre 9 nov"
                 data-published=""
                 data-broadcastStartTime="">
                        <div class="playDisplayTable">
                <a href="/video/574063/9-11-18-10" class="playLink playBoxWithClickArea playIELinkFix">
                    <div>
                        <div class="svtMBFig-L-O-O-O svtMediaBlockFig-L playJsInfo-ImageContainer playPositionRelative">
                            <img class="playGridThumbnail" alt="ABC - 9/11 18:10" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                                                    </div>
                        <span class="playDisplayTouch-N-I-I-I playJsInfo-Open playIconInline playIcon-ArrowDown playFloatRight"></span>
                        <h5 class="playGridHeadline">
                                                            9/11 18:10
                                                                                </h5>
                                                    <p class="svtXColorWhite">Sändes:
                                <time datetime="2012-11-09T18:10+01:00">
                                fre 9 nov
                                </time>
                            </p>
                                                                    </div>
                </a>
                <a href="?" class="playClickArea playJsInfo-Open">
                    <span class="playIconInline playIcon-ArrowDown svtHide-Gte-S"></span>
                </a>
            </div>
        </article>        
        */
        // Let's assume that we ONLY want to have the div with the data-tabname="episodes" if it exists.
        def episodesMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname="episodes"[^>]*?>.*?</body>'
        if (episodesMatcher != null && episodesMatcher.getCount() > 0)
        {
            resourceHtml = episodesMatcher[0]
//            println 'Kapad resourceHtml: ' + resourceHtml
        }
        
        def articleMatches = resourceHtml =~ '(?s)<article [^>]*?>.*?</article>'
        def itemsAdded = 0;
        for( int i = 0; i < articleMatches.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
            //println 'articleMatches['+ i+ ']: ' + articleMatches[i]            
            // Find out if this has a playlink
//            def playlinkMatcher = articleMatches[i][0] =~ '(?s).*data-title="([^"]*?)".*?<a href="([^"]*?)" class="playLink'
            def playlinkMatcher = articleMatches[i] =~ '(?s)data-title=\"([^\"]*?)\".*?<a href=\"([^\"]*?)\" class=\"playLink'
            if (playlinkMatcher != null)
            {
                def webResourceItemTitle
                String strReleaseDate
                Date releaseDate
                def videoArticleThumbnailUrl
                def videoArticleUrl         
                
                def validDate = false       
                
                if (playlinkMatcher.getCount() > 0)
                {
                    // Try parsing the releas date
                    // <time datetime="2012-11-09T18:10+01:00">
                    log(' Found matching playlink')
                    def timeMatcher = articleMatches[i] =~ '(?s)<time datetime=\"([^\"]*?)\"'
                    if (timeMatcher != null)
                    {
                        if (timeMatcher.getCount() > 0)
                        {
                            strReleaseDate = timeMatcher[0][1]
                            // println 'strRelease: ' + strReleaseDate
                            try
                            {
                                // Reformat date to parsable 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 ( strReleaseDate.endsWith( "Z" ) ) {
                                    strReleaseDate = strReleaseDate.substring( 0, strReleaseDate.length() - 1) + "GMT-00:00";
                                } else {
                                    int inset = 6;
                                
                                    String s0 = strReleaseDate.substring( 0, strReleaseDate.length() - inset );
                                    String s1 = strReleaseDate.substring( strReleaseDate.length() - inset, strReleaseDate.length() );
                        
                                    strReleaseDate = s0 + "GMT" + s1;
                                }
                                
                                // println 're-structured strRelease: ' + strReleaseDate
                                
                                releaseDate = df.parse( strReleaseDate );
                                validDate = true
                                //log(' releaseDate: ' + releaseDate)
                                // println ' releaseDate: ' + releaseDate
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse release date (strReleaseDate: ' + strReleaseDate + ')')
                                 //println 'Error parsing date: ' + ex.getMessage()
                            }
                        }
                    }

                    // Try parsing the thumbnail
                    //<img class="playGridThumbnail" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                    def thumbnailMatcher = articleMatches[i] =~ '(?s)<img class=\"playGridThumbnail\" [^>]*?src=\"([^\"]*?)\"'
                    if (thumbnailMatcher != null)
                    {
                        if (thumbnailMatcher.getCount() > 0)
                        {
                            videoArticleThumbnailUrl = thumbnailMatcher[0][1]
                            log(' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl)
                        }
                    }
                    
                    webResourceItemTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(playlinkMatcher[0][1])
                    videoArticleUrl = 'http://www.svtplay.se' + playlinkMatcher[0][2]
                    log(' webResourceItemTitle: ' + webResourceItemTitle)
                    //println 'webResourceItemTitle: ' + webResourceItemTitle
                    log(' videoArticleUrl: ' + videoArticleUrl)
 
                    WebResourceItem item = new WebResourceItem(title: webResourceItemTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl])
                    if (validDate)
                    {
                        item.releaseDate = releaseDate                        
                    }
                    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' )
        return webResourceContainer
        
    }

    ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
    
        log ( getExtractorName() + ': extracting Url for WebResourceItem at ' + item.getAdditionalInfo()['videoArticleUrl'])
        // Get the html page which displays the video widget
        def videoArticleUrl = new URL(item.getAdditionalInfo()['videoArticleUrl'])
        def videoArticleContentHtml
        def connection = videoArticleUrl.openConnection()
        if(connection.responseCode == 200){
            videoArticleContentHtml = connection.content.text
        }
        else{
            return null
        }
        
        //<a id="player" class="svtplayer playJsRemotePlayer playSvtplayer" data-id="547626"
        def videoArticleMatcher = (videoArticleContentHtml =~ '<a id="player" [^>]* data-id="([^\\"]*?)"')
        def videoArticleId
        assert videoArticleMatcher != null
        if(videoArticleMatcher.size() > 0)
        {
            videoArticleId = videoArticleMatcher[0][1]
            log(' videoArticleId - ' + videoArticleId)
        }
        else
        {
            log(' No video article id found')
            return null
        }

        // Parse the json resource url from the data-json attribute.
        def videoJsonMatcher = (videoArticleContentHtml =~ '<a id="player" [^>]* data-json-href="([^\\"]*?)"')
        def videoJsonPath
        def videoJsonUrl
        if (videoJsonMatcher != null && videoJsonMatcher.size() > 0)
        {
            videoJsonUrl = new URL('http://www.svtplay.se' + videoJsonMatcher[0][1] + '?output=json')
        }
        else
        {
            videoJsonUrl = new URL('http://www.svtplay.se/video/' + videoArticleId + '?output=json')
        }
        log('videoJsonUrl: ' + videoJsonUrl)
        //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'

        def thumbnail = item.getAdditionalInfo()['videoArticleThumbnailUrl']
                
        return new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: contentUrl, thumbnailUrl: thumbnail)
    }

    
    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','704x396','576x324','480x270','320x180']
                    }else{
                        if(requestedQuality == PreferredQuality.MEDIUM){
                            priorityListHttp = ['704x396','576x324','1024x576','480x270','1280x720','320x180']
                        }else{
                            // LOW
                            priorityListHttp = ['320x180','480x270','576x324','704x396','1024x576', '1280x720']
                        }
                    }       

                    def httpMatch = httpMatcher[0][1]
//                    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
//                            println 'm3u8Content: ' + m3u8Content
                            List m3u8Lines = m3u8Content.readLines()
                            priorityListHttp.each{
                                
                                if(match == null){
                                    for(int index = 0; index < m3u8Lines.size() - 1; index++)
                                    {
//                                        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)
//                                            println ' found m3u8 http stream url. Best match for requested quality: ' + 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
    }

    static main(args) {
        // this is just to test
//        def TestUrl = new URL("http://www.svtplay.se/kategorier/barn?tab=episodes&sida=1")
//        def TestUrlHttp = new URL("http://www.svtplay.se/abc")
//        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/dokumentar?tab=episodes&sida=2")
//        def TestUrlHttp = new URL("http://www.svtplay.se/?tab=news&sida=5")
//        def TestUrlHttp = new URL("http://www.svtplay.se/palme")
        def TestUrlHttp = new URL("http://www.svtplay.se/kategorier/filmochdrama?tab=episodes&sida=3")
//        def TestUrlHttp = new URL("http://www.svtplay.se/lognen?tab=episodes&sida=2")
        
        def TestUrlRtmp = new URL("http://www.svtplay.se/homeland")
        
        SvtPlayWebResouceExtractor extractor = new SvtPlayWebResouceExtractor()
        println "PluginName               : " + extractor.getExtractorName();
        println "Plugin version           : " + extractor.getVersion();
        println "TestMatch HTTP           : " + extractor.extractorMatches(TestUrlHttp);
        println "TestMatch Rtmp           : " + extractor.extractorMatches(TestUrlRtmp);
    
        println ''
        
        assert extractor.extractorMatches( TestUrlHttp )
        assert extractor.extractorMatches( TestUrlRtmp )
        assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
        
        WebResourceContainer containerHttp = extractor.extractItems(TestUrlHttp, 24);

        ContentURLContainer result1Http = null
         if (containerHttp.getItems().size() > 0)
         {
            println ' containerHttp.getItems()[0].releaseDate: ' + containerHttp.getItems()[0].releaseDate
            result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)

            for (int index = 0; index < containerHttp.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerHttp.getItems()[index].title + ', videoArticleUrl: ' + containerHttp.getItems()[index].getAdditionalInfo()['videoArticleUrl']
            }
         }
//        ContentURLContainer result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)
        ContentURLContainer result2Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.MEDIUM)
        ContentURLContainer result3Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.HIGH)
        println "Result low HTTP: $result1Http"
        println "Result medium HTTP: $result2Http"
        println "Result high HTTP: $result3Http"

        println ''

        WebResourceContainer containerRtmp = extractor.extractItems(TestUrlRtmp, 3)

        if (containerRtmp.getItems().size() > 0)
        {
            ContentURLContainer result1Rtmp = extractor.extractUrl(containerRtmp.getItems()[0], PreferredQuality.LOW)
            println "Result low Rtmp: $result1Rtmp"
        }
        else
        {
            println 'No qbrick items found!'
        }
        if (containerRtmp.getItems().size() > 1)
        {
            ContentURLContainer result2Rtmp = extractor.extractUrl(containerRtmp.getItems()[1], PreferredQuality.MEDIUM)
            println "Result medium Rtmp: $result2Rtmp"
        }
        if (containerRtmp.getItems().size() > 2)
        {
            ContentURLContainer result3Rtmp = extractor.extractUrl(containerRtmp.getItems()[2], PreferredQuality.HIGH)
            println "Result high Rtmp: $result3Rtmp"
        }
    }
}