import groovy.util.XmlParser

import org.serviio.library.metadata.*
import org.serviio.library.online.*

/**
 * Hulu.com content URL extractor plugin. 
 * 
 * Based on http://gitorious.org/get-flash-videos-plugins/gfv-plugins/blobs/raw/release/Hulu.pm
 * 
 * @author Petr Nejedly
 *
 */
class Hulu extends FeedItemUrlExtractor {
    
    final VALID_FEED_URL = '^(?:https?://)?(www\\.|rss\\.)?hulu\\.com/.*$'
	final PLAYER_VERSION = '888324234'
	final PLAYER_URL = 'http://www.hulu.com/site-player/player.swf'
	final SMIL_KEY = 'd6dac049cc944519806ab9a1b5e29ccfe3e74dabb4fa42598a45c35d20abdd28';
	final SMIL_IV = '27b9bedf75ccA2eC';
	
    String getExtractorName() {
        return 'Hulu (US only)'
    }
    
    boolean extractorMatches(URL feedUrl) {
        return feedUrl ==~ VALID_FEED_URL
    }
    
    ContentURLContainer extractUrl(Map links, PreferredQuality requestedQuality) {
        def linkUrl = links.default
        def contentUrl
		def cacheKey
		
		String pageHtml = linkUrl.getText()
		String regex = "www.hulu.com/embed.html\\?eid=?([a-zA-Z0-9-_]+)"
		def pageMatcher = pageHtml =~ regex
		assert pageMatcher != null
		def eId = pageMatcher[0][1]
		
		String rUrl = "http://r.hulu.com/videos?eid=$eId"
		def contentDetailsNode = new XmlParser().parse( rUrl )		
		def videoId = contentDetailsNode.video.'content-id'.text()

		Integer timestamp = (System.currentTimeMillis() / 1000) + (24 * 60 * 60);
		String bcsRaw = "dp_idhulunp1pphuluts${timestamp}v${PLAYER_VERSION}video_id${videoId}vp1"
		String bcs = generateMAC(bcsRaw,'f6daaa397d51f568dd068709b0ce8e93293e078f7dfc3b40dd8c32d36d2b3ce1','HmacMD5')
		String smilFileUrl = "http://s.hulu.com/select.ashx?video_id=${videoId}&v=${PLAYER_VERSION}&ts=${timestamp}&np=1&vp=1&pp=hulu&dp_id=hulu&bcs=${bcs}"
		def smilContent = new URL(smilFileUrl).getText()
		String decodedSmil = trimSmilXml(decryptAES(smilContent, SMIL_KEY, SMIL_IV))
		assert decodedSmil != null

		def smilNode = new XmlParser().parseText( decodedSmil )
		// get all media items that are either video or audio and have a supported connection sub-element
		List mediaItems = smilNode.body.'switch'[1].video.findAll { it -> (it.'@cdn' == 'akamai') }
		assert mediaItems.size() > 0 : 'No video sources found, you are probably outside the US'
		// sort media items by bitrate, lowest first and get an item
		List sortedItems = mediaItems.sort { it.'@system-bitrate'.toInteger() }
		Node selectedMediaItem = findSuitableItem(sortedItems, requestedQuality)

		String app = (selectedMediaItem.'@server' =~ 'rtmpe?://.*?/(.*)')[0][1]
		contentUrl = "${selectedMediaItem.'@server'}?${selectedMediaItem.'@token'} app=${app}?${selectedMediaItem.'@token'} playpath=${selectedMediaItem.'@stream'} swfUrl=${PLAYER_URL} swfVfy=1"	
		cacheKey = "hulu_${videoId}_${requestedQuality}"
		return new ContentURLContainer(contentUrl: contentUrl, expiresImmediately: true, cacheKey : cacheKey)
    }
    	
	def Node findSuitableItem(List items, PreferredQuality requestedQuality) {
		if(requestedQuality == PreferredQuality.LOW) {
			// worst quality, get the first from the list
			return items.head()
		} else if (requestedQuality == PreferredQuality.MEDIUM) {
			// get item from the middle
			return items.get(Math.floor(items.size()/2).toInteger())
		} else {
			// best quality, take the last url
			return items.last()
		}
	}
	
	def String trimSmilXml(String smil) {
		def matcher = smil =~ '(?s)^<smil.*</smil>'
		//log(smil)
		assert matcher != null
		assert matcher.count == 1 : 'Invalid SMIL XML, content probably expired'
		return matcher[0]
	}
	
    static void main(args) {
		// this is just to test
        Hulu extractor = new Hulu()
		
		assert extractor.extractorMatches( new URL("http://www.hulu.com/feed/recent/tv") )
		assert extractor.extractorMatches( new URL("http://rss.hulu.com/HuluPopularVideosThisWeek?format=xml") )
		
		
        Map links = ['default': new URL('http://rss.hulu.com/~r/HuluPopularVideosThisWeek/~3/VGE00M3j8Rk/406212')] 
		//Map links = ['default': new URL('http://www.hulu.com/watch/59796/emergency-back-up#http%3A%2F%2Fwww.hulu.com%2Ffeed%2Fshow%2F172%2Fepisodes')]
				
        ContentURLContainer result = extractor.extractUrl(links, PreferredQuality.MEDIUM)
        println "Result: $result"
    }
}