import java.text.SimpleDateFormat

import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.serviio.util.*
import groovy.json.*


/**
 * Content URL extractor plugin for ABC iView (Australia only) 
   Processes feeds like http://tviview.abc.net.au/iview/rss/recent.xml       - 72 items on it when I counted
                        http://tviview.abc.net.au/iview/rss/category/pre-school.xml
			http://tviview.abc.net.au/iview/rss/last-chance.xml   - 95 items on it when I counted
                        http://tviview.abc.net.au/iview/rss/category/docs.xml 43 items on it when I counted
 URL matcher is       '^http(s)*://tviview.abc.net.au/iview/rss/.*$'

check http://www.abc.net.au/tv/iview/feeds.htm for a list of available feeds
 *  
 * @author ttguy
 Based on python-iview by Jeremy Visser https://jeremy.visser.name/2012/05/python-iview-update-fixes-downloading-for-metered-users/

 version 0.1 - 2 Sept 2012. 
  Does not respect PreferredQuality - iView might have different quality videos. Not sure. But this plug-in
                    gets the default quality.  
  It does swf validation on the feed items
  There was some discussion on how to get the swf validation on the serviio forum - see http://forum.serviio.org/viewtopic.php?p=51705#p51705 - implementing swf verification thread
  See also comments below around contentUrl
   
To Execute outside of serviio for debuging 
     cd /usr/bin/serviio-0.6.2/
      groovy -cp lib/serviio.jar:lib/slf4j-api.jar:lib/slf4j-log4j12.jar:lib/log4j.jar:lib/org.restlet.jar plugins/ABCiView.groovy
 *

 */
class ABCiView extends FeedItemUrlExtractor {
    final version='0.1'
    final VALID_FEED_URL = '^http(s)*://tviview.abc.net.au/iview/rss/.*$'
   
    
	final API_VERSION = '383'
	final swf_hash    = '96cc76f1d5385fb5cda6e2ce5c73323a399043d0bb6c687edd807e5c73c42b37'
	final swf_size    = '2122'
	final config_url   = 'http://www.abc.net.au/iview/xml/config.xml?r=%d' + API_VERSION 
	final series_url   = 'http://www.abc.net.au/iview/api/series_mrss.htm?id=%s'
	final swf_url     = 'http://www.abc.net.au/iview/images/iview.jpg'
 

	final auth_url = 'http://tviview.abc.net.au/iview/auth/?v2'
	final api_url ='http://tviview.abc.net.au/iview/api2/?'
	final akamai_playpath_prefix = 'flash/playback/_definst_/'


	final USER_AGENT = 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1'

	def EpMap= [:]   // populated by extractorMatches() method when the plugin loads (and hopefully when it re-loads when a feed expries)

    String getExtractorName() {
        return 'ABC iView (Australia only)'
    }
    
		/*
		boolean extractorMatches(URL feedUrl)
		Input parameters:
		feedUrl – URL of the whole feed, as entered by the user
		Description:
		Called once for the whole feed, it returns true if the feed's items can be processed by this plugin.
		For each feed which needs a plugin Serviio tries to match all available plugins to the feed's URL by
		calling this method and uses the first plugin that returns true. Use of regular expression is
		recommended.

		The method used to find ABC iView playpaths involves getting a list of all the series currently available 
		on iView and the program IDs associtated with each of these series. So that we only do this once
		we call a method GetSeriesList() to do this job from the extractorMatches() method and we populate 
                a global Map object with this data.
		 
		*/
    boolean extractorMatches(URL feedUrl) {


        //VALID_FEED_URL = '^http(s)*://tviview.abc.net.au/iview/rss/.*$'
	def result = feedUrl ==~ VALID_FEED_URL
	if (result)
	{
		log("version $version")
		EpMap=GetSeriesList()
	}
        return result
    }

    /*
	ContentURLContainer extractUrl(Map<String,URL> links, PreferredQuality requestedQuality)

		Input parameters:
		links – a map of links as found in the feed item XML document. Keys are values of rel attribute
		of link elements. 
		There are also special key names:
		• default – for a link element without rel attribute
		• thumbnail – includes URL of the item's thumbnail, if available
		requestedQuality – includes value (HIGH, MEDUIM, LOW) of enumeration
		org.serviio.library.online.PreferredQuality. It should be taken into consideration if the
		online service offers multiple quality-based renditions of the content.
		Description:
		Performs the actual extraction of content information using the provided information. It returns an
		instance of org.serviio.library.online.ContentURLContainer. These are the properties of
		the class:
		• String contentUrl – URL of the feed item's content; mandatory
		• String thumbnailUrl – URL of the feed item's thumbnail; optional
		• org.serviio.library.metadata.MediaFileType fileType – file type of the feed item;
		default is VIDEO
		• Date expiresOn – local date the feed item's content of contentUrl expires; optional
		• boolean expiresImmediately – if true Serviio will extract the URL again when the play
		request is received to get URL that will be valid for the whole playback; optional, mutually
		exclusive with expiresOn
		• String cacheKey – a unique identifier of the content (i.e. this item with this quality) used
		as a key to technical metadata cache; optional if expiresOn or expiresImmediately is
		provided
		• boolean live – identifies the content as a live stream; optional (default is false)
		These can be set either via a setter method (e.g. setContentUrl() ) or via named constructor, e.g.
		new ContentURLContainer(contentUrl: someContentUrl, thumbnailUrl:
		someThumbnailUrl).
		If the object cannot be constructed the method should return null or throw an exception.

 returns ContentURLContainer(fileType: fileType, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, expiresOn: authExpiryDate, cacheKey: cacheKey, live: live)
*/

    ContentURLContainer extractUrl(Map links, PreferredQuality requestedQuality) {
     
        def linkUrl = links.alternate != null ? links.alternate : links.default //  if links.alternate != null then linkUrl = links alternate
										// else linkUrl = links.default
							//  linkUrl class java.net.URL
	// println "config_url $config_url "
	// println "linkUrl $linkUrl"
	// serviio log reports DEBUG [FeedItemUrlExtractor] ABC iView (Australia only): linkUrl null
	// from this call
	// log("linkUrl $linkUrl")
        def contentUrl
       
	
	def live = false
	def expiresImmediately = true // you have to get an auth token before playing each feed item and this token expires
					// thus we are seting this to true to ensure serviio refetchs the auth token when you go to play the file

	def matcher = linkUrl =~ '^.+/view/(.+)$'      // regular expression matching and capturing the match in the matcher object
		//    ^.+/view  = start of the line, one or more any characters, forward slash view
		//  (.+) means one or more any char and the () means capture the match. This matching retrieves the   960640
		// from  http://www.abc.net.au/iview/#/view/960640      

		// http://www.abc.net.au/iview/#/view/960640      - video starts
		// http://www.abc.net.au/iview/#/program/959718  - program front page
               // http://www.abc.net.au/iview/#/view/780456  - video starts 
		
		assert matcher != null
		assert matcher.hasGroup()

		def videoId = matcher[0][1] // This gets us the number portion from a URL like http://www.abc.net.au/iview/#/view/780456  
 		//println " videoId $videoId"
		def seriesID= EpMap[ (videoId) ].seriesID // EpMap is a Map (assocative array type of structure) keyed by program numbers and created by GetSeriesList() method
				// Here we retrieve the series ID for this episode			
		Date ExpireDate = ParseDateString( EpMap[ (videoId) ].ExpDate) // Get the expiry date from the EpMap too
		//println "seriesID $seriesID"  
		//println " ExpireDate $ExpireDate"
		String cacheKey=videoId;  // our data in the cache the plug in creates will be keyed by the program number
		log (" videoId $videoId")
		// load the auth token page

		def AuthNodes = new XmlParser().parse( auth_url)
		assert AuthNodes.token.size()==1
		def authString=AuthNodes.token[0].text() // get our authorisation token from the auth_url page
		//println "authString $authString"

		Map EpData = GetEpisodeData(seriesID, videoId) // get another Map with details on the episode in question
               
		def PlayPathSuffix = EpData.PlayPath // Get our playpath 
		def thumbnailUrl = EpData.Thumbnail  // 


		MediaFileType fileType = MediaFileType.VIDEO;
               
		//   I can get a rtmpdump command to download an iView file - completely - eg 
		// rtmpdump -V --rtmp rtmp://cp53909.edgefcs.net:1935 --app 'ondemand?auth=daEd5bbb.djagdyb5cNcNblcidmb0b3atbV-bqqFvX-8-qlm_tHAnL&aifp=v001' --playpath mp4:flash/playback/_definst_/catalyst_13_15.mp4 --swfVfy http://www.abc.net.au/iview/images/iview.jpg  -o /home/god/Videos/catalyst3.mp4
		// 
		// the --swfVfy http://www.abc.net.au/iview/images/iview.jpg appears strange. But it works. Normally this would point to a .swf file
		//  But I think iView has renamed a .swf to .jpg to obsufcate.
		// an equivalent ffmpeg command would be 
		//    ffmpeg -report -i        "rtmp://cp53909.edgefcs.net:1935/mp4:flash/playback/_definst_/catalyst_13_15.mp4 app=ondemand?auth=daEdzaBaJaNdmcyd3d0b9aIataUbEc8a7av-bqqHkc-8-oks_wEAqH&aifp=v001 playpath=mp4:flash/playback/_definst_/catalyst_13_15.mp4 swfUrl=http://www.abc.net.au/iview/images/iview.jpg swfVfy=1" 
		// in ffmped swfVfy is a boolean switch meaning do Swf Verification
		// You supply the file to verify against using swfUrl
// ffmpeg manual says use   rtmp://<server>[:<port>][/<app>][/<playpath>]
//             I am using
		//     rtmp://<server>:<port>/<playpath>  and supplyin app in a parameter. Also sending the playpath in again as a parameter
//                     and verifying against swfUrl=http://www.abc.net.au/iview/images/iview.jpg and saying true the verify by specifying  swfVfy=1
//                    This syntax seems to work.
		// 
    //   ffmpeg -report -i   "rtmp://cp53909.edgefcs.net:1935/mp4:flash/playback/_definst_/catalyst_13_15.mp4 app=ondemand?auth=daEdzaBaJaNdmcyd3d0b9aIataUbEc8a7av-bqqHkc-8-oks_wEAqH&aifp=v001 playpath=mp4:flash/playback/_definst_/catalyst_13_15.mp4 swfUrl=http://www.abc.net.au/iview/images/iview.jpg swfVfy=1" 
 		contentUrl = "rtmp://cp53909.edgefcs.net:1935/mp4:$akamai_playpath_prefix$PlayPathSuffix app=ondemand?auth=$authString playpath=mp4:$akamai_playpath_prefix$PlayPathSuffix swfUrl=$swf_url swfVfy=1"  
              //     ffmpeg -report -i        "rtmp://cp53909.edgefcs.net:1935/mp4:flash/playback/_definst_/catalyst_13_15.mp4 app=ondemand?auth=daEdzaBaJaNdmcyd3d0b9aIataUbEc8a7av-bqqHkc-8-oks_wEAqH&aifp=v001 playpath=mp4:flash/playback/_definst_/catalyst_13_15.mp4 swfUrl=http://www.abc.net.au/iview/images/iview.jpg swfVfy=1" 
		//return new ContentURLContainer(fileType: fileType, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, expiresImmediately: expiresImmediately, live: live, cacheKey: cacheKey)			
		return new ContentURLContainer(fileType: fileType, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, expiresImmediately: expiresImmediately, live: live, cacheKey: cacheKey, expiresOn: ExpireDate)		


    }

	/*
		GetSeriesList() - processes the JSON file that lists all currently available iView programs
		The JSON file has seriesID and episode ID. This method returns the series ID for each episode it finds
		It also grabs a PubDate and ExpDate for each episode.
	*/
	Map GetSeriesList()
	{
		def ReturnMap= [:]
		//http://tviview.abc.net.au/iview/api2/?seriesIndex
		String SeriesListWebPage = openURL(new URL(api_url + "seriesIndex"), USER_AGENT)
			// SeriesListWebPage is a JSON formated document.
			// {"a":"3368988","b":"Trailer: Sunday Best",       "e":"trailers recent"         ,
			//    "f":[{"a":"994744","f":"2012-08-30 00:00:00","g":"2012-09-10 00:00:00"}]}
			// {"a":"3284240","b":"Future Forum"		,"e":"news abc1 abc4"	       ,
			//  ,"f":[{"a":"972349","f":"2012-07-22 21:01:00","g":"2012-09-16 21:00:00"},{"a":"972356","f":"2012-07-22 21:00:00","g":"2012-09-16 21:00:00"}]}] 
		//println "SeriesListWebPage $SeriesListWebPage"
		JsonSlurper slurper = new JsonSlurper()
		 Object result = slurper.parseText(SeriesListWebPage)
		def  seriesIDS = result.a;//java.util.ArrayList = array list of each seriesID  - list of string
		def Episodes =   result.f; //java.util.ArrayList = a parallel array list of the collection of each episode available for each series
		//println "seriesIDS  classname $seriesIDS.metaClass().getName()"
		// println "Episodes  classname $Episodes.metaClass().getName()"
		
		def count = seriesIDS.size()
		
		for ( i in 0..count-1 ) {   // for each series
		     
			//println seriesIDS[i]
			def anEpisode = Episodes[i]        // ArrayList
			
			def EpisodeID=anEpisode.a
			def PubDates=anEpisode.f
			def ExpDates=anEpisode.g
			def count2=EpisodeID.size()
			for ( j in 0..count2-1 ) {  // for each episode in the series
				//println EpisodeID[j]
				Map epData=['seriesID' : seriesIDS[i] , 'PubDate' : PubDates[j] , 'ExpDate' : ExpDates[j] ]
				//ReturnMap [ (EpisodeID[j]) ]= seriesIDS[i]   // the map is keyed by EpisodeID and contains seriesIDs as values.
				ReturnMap [ (EpisodeID[j]) ]= epData
				
			}
			
			
		}		

		return ReturnMap
	}
/*
	GetEpisodeData. This uses the iView api url to return a JSON file for the passed in SeriesID. It looks through the JSON file to
        find the data for the EpisodeId. It returns the PlayPath and Thumbnail URL for this episode.
*/
	Map GetEpisodeData(String SeriesID, String EpisodeID)
	{
		Map ReturnMap = [:]
		String SeriesData = openURL(new URL(api_url + "series=" + SeriesID), USER_AGENT)
		//println "SeriesData $SeriesData"
		JsonSlurper slurper = new JsonSlurper()
		 Object result = slurper.parseText(SeriesData)
		def  episodeIDs = result.f.a[0]
		def  PlayPaths = result.f.n[0]
		def  Thumbnails = result.f.s[0]
		def epCount = episodeIDs.size()
		//println "epCount $epCount"
		for ( i in 0..epCount-1 ) {
			if (EpisodeID== episodeIDs[i])
			{
				ReturnMap=['PlayPath' :PlayPaths[i],'Thumbnail' : Thumbnails[i]   ]
				//println episodeIDs[i]
				//println PlayPaths[i]
				//println Thumbnails[i]
			}
		}		
		return ReturnMap
    	}
	

	def Date ParseDateString(String dateString) {
		// 2012-08-30 18:30:00
		SimpleDateFormat df = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss')
		//df.setTimeZone(TimeZone.getTimeZone("GMT"))
		return df.parse(dateString)		
	}	
    static void main(args) {
		// this is just to test
       		ABCiView extractor = new ABCiView()
		//http://tviview.abc.net.au/iview/rss/recent.xml
		assert extractor.extractorMatches( new URL("http://tviview.abc.net.au/iview/rss/recent.xml") )
	//	assert extractor.extractorMatches( new URL("http://tviview.abc.net.au/iview/rss/category/pre-school.xml") )
	//	assert !extractor.extractorMatches( new URL("http://feeds.bbc.co.uk/iplayer/categories/drama/tv/list") )
	//	assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
		
        //   Map videoLinks = ['alternate': new URL('http://www.abc.net.au/iview/#/view/813340'),
	//			'default' : new URL('http://www.abc.net.au/iview/#/view/813340')] 
 	//  Map videoLinks = ['default' : new URL('http://www.abc.net.au/iview/#/view/25509')]  // Compass Series 26 Episode 25 My Brother's Cult
	//  Map videoLinks = ['default' : new URL('http://www.abc.net.au/iview/#/program/962380')]   // A world of wonders s2 e13  (series ID = 3510358 as reported from /usr/bin/./iview-cli -i )

	//  Map videoLinks = ['default' : new URL('http://www.abc.net.au/iview/#/view/962380')] 

	// Map videoLinks = ['default' : new URL('http://www.bbc.co.uk/iplayer/episode/b01k28sh/EastEnders_Omnibus_23_06_2012/')] 

	// Map videoLinks = ['default' : new URL('http://www.abc.net.au/iview/#/view/780456')] //Doctor Who: Confidential Series 6 ( 3201631) Episode 7 The Born Identity	(drwhoconfidential_06_07.mp4)
	Map videoLinks = ['default' : new URL('http://www.abc.net.au/iview/#/view/792638')] //Timmy Time Series 3 Episode 12 Fireman Timmy

        ContentURLContainer result = extractor.extractUrl(videoLinks, PreferredQuality.LOW)		
        println "Result: $result"
		 

    }
}
