import groovy.json.JsonSlurper
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.serviio.util.*

/**
 * WebResource extractor plugin for YouTubeTop100.
 * 
 * @author jhb50
 * V1 - start parameter - start=nn
 * V2 - Added res and codec parameters - res=1080,720,480,360  - codec=264,8
 * V3 - Add format code to cachekey to fix transcoding issue
 * V4 - Removed 'live' attribute
 * V5 - Updated to ignore new YT 3D formats, enclose video number in []
 * V6 - Remove 640 option, add daily option - type=daily
 * v7 - Decode HTML characters, add genre option  - type=nnn
 * v8 - Add Number Format Option - nform=xbox
 * v9 - Correct Display of Extended Characters in Song Titles
 * v10 - Added res=240 and Usage comments - Remove 404 test so Serviio 1.0 will not timeout
 * v11 - Added version number to the debug log.
 * v12 - Support new YouTube Format
 * v13 - Oct 21, 2012 - Support another new YouTube Format , add 'for' parameter
 * v14 - Nov 30, 2012 - correct 'for' parameter
 * v15 - Dec  9, 2012 - Remove daily/weekly option.. only daily now available
 * v16 - Dec  9, 2012 - Changed again to spoof weekly option 
 * v17 - Dec 20, 2012 - Support new YouTube format
 * v18 - Dec 20, 2012 - Correct V17 Thumbnail error
 * v19 - Dec 30, 2012 - Remove language dependency
 * v20 - Feb  3, 2013 - Add YouTube Expiry Date, correct type codes
 * v21 - Feb 16, 2013 - Support new YouTube format

 *
 * Usage:
 *  http://www.youtubetop100.com
 *   
 *  with optional parameters
 *
 *  for list type type=weekly, daily or nnn 
 *  where nnn is genre number
 *   474 Alternative, 455 Blues, 471 Comedy/Spoken, 453 Country, 454 Easy Listening, 466 Electronic, 456 Folk, 
 *   462 International, 460 Jazz, 476 K-Pop, 465 Latin, 475 Metal, 458 New Age, 472 Pop, 470 R&B, 451 Rap, 
 *   461 Reggae, 464 Religious, 473 Rock, 463 Stage & Screen, 459 Vocal
 *
 *  for codec codec=8 for web8 or 264 for h264
 *    default is 264
 *
 *  for resolution res=nnnn where nnnn = 1080, 720, 480, 360, 240
 *    default is console quality value
 *
 *  for starting video start=nnn where nnn=1 to 999
 *    default is 1
 *
 *  for number of videos to display starting at 'start'  for=nnn where nnn=1 to 101-start
 *    default is 100
 *
* for xbox [nnn] numbering nform=xbox
 *
 */
class YouTubeTop100 extends WebResourceUrlExtractor {
	
	final VALID_FEED_URL = '^http://www\\.youtubetop100\\.com.*?'

	String getExtractorName() {
		return 'YouTubeTop100'
	}
	
	int getVersion() {
		return 21
	}

	boolean extractorMatches(URL feedUrl) {
		return feedUrl ==~ VALID_FEED_URL
	}
	
	    Boolean streamNotSupported(
        String htmlText, 
        String streamNotSupportedChecker
    ) {        
        def notsupported_check = htmlText =~ streamNotSupportedChecker
        if (notsupported_check.count > 0) {
            return true
        }
        return false
    }
	
	WebResourceContainer extractItems(URL resourceUrl, int maxItems) {
	
		log("Parsing with Top100 V21")

		List<WebResourceItem> items = []
		def itemsAdded = 0
		String pageTitle = ""
		String pageThumb = ""
		String videoUrl = ""
		String videoTitle = ""
		String thumbUrl = ""
		Short Start = 0 
		Short For = 100 
		Boolean ForSet = false
		Short Res = 0
		Short Codec = 264
		String Type = "weekly"
		String Nform = ""
		
		def parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?start=([0-9]+)'
		def parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?start=[0-9]+.*?'
		if (parmMatch){
			Start = parmMatcher[0][1].trim().toShort() - 1
			if(Start < 0 || Start > 99) Start = 0
		}

		parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?for=([0-9]+)'
		parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?for=[0-9]+.*?'
		if (parmMatch){
			For = parmMatcher[0][1].trim().toShort()
			ForSet = true
			if(For < 1 || For > 100) ForSet = false
		}

		parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?type=([a-z0-9]+)'
		parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?type=[a-z]+.*?'
		if (parmMatch){
			Type = parmMatcher[0][1].trim()
			if(Type == "daily") Type = "."
			else Type = "weekly"
		}
		else{
			parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?type=[0-9]+.*?'
			if (parmMatch){
				Type = parmMatcher[0][1].trim()
				// 474 Alternative, 455 Blues, 471 Comedy/Spoken, 453 Country, 454 Easy Listening, 466 Electronic, 456 Folk, 
				// 462 International, 460 Jazz, 476 K-Pop, 465 Latin, 475 Metal, 458 New Age, 472 Pop, 470 R&B, 451 Rap, 
				// 461 Reggae, 464 Religious, 473 Rock, 463 Stage & Screen, 459 Vocal
				String ValidCodes = "451,453,454,455,456,458,459,460,461,462,463,464,465,466,470,471,472,473,474,475,476"	
				if(ValidCodes.contains(Type)) Type = ".." + Type
				else Type = "0"
			}
		}

		parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?res=([0-9]+)'
		parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?res=[0-9]+.*?'
		if (parmMatch){
			Res = parmMatcher[0][1].trim().toShort() 
			if(Res != 1080 && Res != 720 && Res != 480 && Res != 360 && Res != 240) Res = 0
		}

		parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?codec=([0-9]+)'
		parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?codec=[0-9]+.*?'
		if (parmMatch){
			Codec = parmMatcher[0][1].trim().toShort() 
			if(Codec != 8 && Codec != 264) Codec = 264
		}
		
		parmMatcher = resourceUrl =~ '^http://www\\.youtubetop100\\.com.*?nform=([a-z]+)'
		parmMatch = resourceUrl ==~ '^http://www\\.youtubetop100\\.com.*?nform=[a-z]+.*?'
		if (parmMatch){
			Nform = parmMatcher[0][1].trim()
			if(Nform != "xbox" ) Nform = ""
		}

		String html 

		if (Type == "weekly"){
		
/*			html = new URL("http://www.youtube.com/music").getText()
			def titleMatcher = html =~ '(?s)<div id="weekly-hits".*?list=MCUS.(.+?)"'
			String listDate = titleMatcher[0][1].trim()
*/			
			String listDate = new Date(System.currentTimeMillis()).format("yyyyMMdd").trim()	 

			pageTitle = "You Tube Weekly Top Tracks"
			

			int j
			for( j = 1; j < 15; j++ ) {

				html = new URL("http://www.youtube.com/playlist?list=MCUS."+listDate).getText("utf-8")
				if (html.count('<li class="playlist-video-item') == 0){
					listDate = new Date(System.currentTimeMillis()).minus(j).format("yyyyMMdd").trim()
				}
				else break
			}
			
			if (j == 15) return null
		}
		else {
			pageTitle = "You Tube Daily Top Tracks"
			
			html = new URL("http://www.youtube.com/playlist?list=MCUS" + Type).getText("utf-8")
		}


	    if (html.count('<li class="playlist-video-item') > 0 ){

	 
			
			def videoMatcher = html =~ '(?s)<li.class="playlist-video-item.*?<img.(.*?)width.*?<a href="(.+?)&amp.*?<span.class="title.video-title.*?>(.*?)<'

			if (ForSet) maxItems = For

			for( int i = Start; i < videoMatcher.size() && (maxItems == -1 || itemsAdded < maxItems); i++ ) {
				videoUrl = "http://www.youtube.com" + videoMatcher[i][2].trim()
				
				String Nformat = i+1
				if(Nform == "xbox"){
					if(i<9) Nformat = "00" + Nformat
					else
					if(i<99) Nformat = "0" + Nformat
				}
				
				videoTitle = "[" + Nformat + "] " + videoMatcher[i][3].trim()
				
				videoTitle = videoTitle.replaceAll("&#39;","'")
				videoTitle = videoTitle.replaceAll("&amp;","&")
				videoTitle = videoTitle.replaceAll("&quot;",'"')
				
				if(videoTitle.contains("Private Video")) continue
				
				thumbUrl = videoMatcher[i][1].trim()
				def thumbMatcher = thumbUrl =~ '(?s)src="(.*?)"'
				if(thumbUrl.contains("data-thumb=")) thumbMatcher = thumbUrl =~ '(?s).*?data-thumb="(.*?)"'
				thumbUrl = "http:" + thumbMatcher[0][1]
				
				
				WebResourceItem item = new WebResourceItem(title: videoTitle, additionalInfo: ['videoUrl':videoUrl,'thumbUrl':thumbUrl, itemNum: i, 'Res': Res, 'Codec': Codec])
				
				items << item
				itemsAdded++
			}
		}
		return new WebResourceContainer(title: pageTitle, thumbnailUrl: pageThumb, items: items)
	}

	
	ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {		
		
		String videoTitle = item.title
		String videoUrl = item.getAdditionalInfo()['videoUrl']
		String thumbnailUrl = item.getAdditionalInfo()['thumbUrl']
		String itemNum = item.getAdditionalInfo()['itemNum'] + 1
		Short Res = item.getAdditionalInfo()['Res']
		Short Codec = item.getAdditionalInfo()['Codec']
		
		String videohtml = new URL(videoUrl).getText()
		//def flashMatch = videohtml ==~ '(?s).*?url_encoded_fmt_stream_map=.*?'
		def flashMatch = videohtml ==~ '(?s).*?url_encoded_fmt_stream_map":.*?'
		if (!flashMatch){
			log("NO ENTRIES FOR THIS ITEM - \'$videoTitle\'") 
			return null
		}

		
		def videoMatch = videohtml =~ '(?s)stream_map":.*?"(.*?)".*?'
		
		String fmt
		String linkUrl
	/*
	high quality
	'38',4096x3072 MP4-AVC/AAC
    '46',1920x1080 WEBM-VP8/Vorbis
	'37',1920x1080 MP4-AVC/AAC
	'45',1280x720  WEBM-VP8/Vorbis
	'22',1280x720  MP4-AVC/AAC
	medium quality
	'44 , 854x480  WEBM-VP8/Vorbis 
	'35', 854x480  flv-AVC/AAC   
	'43', 640x360  WEBM-VP8/Vorbis 
	'18', 640x360  MP4-AVC/AAC
	'34', 640x360  flv-AVC/AAC
	lowquality
    '6',  480x270  flv-h263/MP3
	'5',  400x240  flv-h263/MP3
	'36'  320x240  3gp-MPEG4/AAC
	'17', 176x144  3gp-MPEG4/AAC  2Mbps
	'13'  176x144  3gp-MPEG4/AAC .5Mbps

	*/    
 		String high8_1080Formats    = "46, 37, 45, 22, 44, 35, 43, 18, 34, 6, 5"
		String high8_720Formats     =         "45, 22, 44, 35, 43, 18, 34, 6, 5"
		String high264_1080Formats  = "37, 22, 35, 18, 34, 6, 5"
 		String high264_720Formats   =     "22, 35, 18, 34, 6, 5"
        String medium8_480Formats   = "35, 44, 18, 34, 43, 6, 5"
        String medium8_360Formats   =         "18, 34, 43, 6, 5"
		String medium264_480Formats = "35, 18, 34, 6, 5"
		String medium264_360Formats =     "18, 34, 6, 5"
		
        String lowFormats = "6, 5"

		def videoMatcher = videoMatch[0][1] =~ '(?s)(.*?)itag%3D(\\d\\d?)(.*?),'

		int i
		for( i = 0; i < videoMatcher.size(); i++ ) {
		
			fmt = videoMatcher[i][2].trim()
			

			if (Res == 0){
				if (Codec == 8) { 
					if(requestedQuality == PreferredQuality.HIGH && high8_1080Formats.contains(fmt)) break
					else
					if(requestedQuality == PreferredQuality.MEDIUM && medium8_480Formats.contains(fmt))	break
				}
				else
				if (Codec == 264) {
					if(requestedQuality == PreferredQuality.HIGH && high264_1080Formats.contains(fmt)) break
					else
					if(requestedQuality == PreferredQuality.MEDIUM && medium264_480Formats.contains(fmt)) break
				}	
				else if(requestedQuality == PreferredQuality.LOW && lowFormats.contains(fmt)) break
			}
			else if(Res == 1080) {
				if (Codec == 8 && high8_1080Formats.contains(fmt)) break
				else
				if (Codec == 264 && high264_1080Formats.contains(fmt)) break
			}
			else if(Res == 720) {
				if (Codec == 8 && high8_720Formats.contains(fmt)) break
				else
				if (Codec == 264 && high264_720Formats.contains(fmt)) break
			}
			else if(Res == 480) {
				if (Codec == 8 && medium8_480Formats.contains(fmt)) break
				else
				if (Codec == 264 && medium264_480Formats.contains(fmt)) break
			}
			else if(Res == 360) {
				if (Codec == 8 && medium8_360Formats.contains(fmt)) break
				else
				if (Codec == 264 && medium264_360Formats.contains(fmt)) break
			}
			else if(Res == 240) {
				if (lowFormats.contains(fmt)) break
			}
		}
		
		linkUrl = videoMatcher[i][1] + "itag%3D" + videoMatcher[i][2] + videoMatcher[i][3] + "&,"
		
		linkUrl = linkUrl.replaceAll(".u0026","&")

		videoMatcher = linkUrl =~ '(.*?)url=(.*?),'
		linkUrl = videoMatcher[0][2] + videoMatcher[0][1] + ","

		videoMatcher = linkUrl =~ '(.*?)type=.*?&(.*?),'
		linkUrl = videoMatcher[0][1] + videoMatcher[0][2] + ","

		videoMatcher = linkUrl =~ '(.*?)fallback_host=.*?&(.*?),'
		linkUrl = videoMatcher[0][1] + videoMatcher[0][2] + ","

		videoMatcher = linkUrl =~ '(.*?)itag=.*?&(.*?),'
		linkUrl = videoMatcher[0][1] + videoMatcher[0][2] + ","
		
		linkUrl = linkUrl.replaceFirst("sig","signature")

		linkUrl = linkUrl.replaceFirst("&,","")

		videoMatcher = linkUrl =~ 'expire%3D(.*?)[%&]'
		def expiryDate = new Date(videoMatcher[0][1].trim().toLong()*1000)


		linkUrl = URLDecoder.decode(linkUrl)
		linkUrl = URLDecoder.decode(linkUrl)
		linkUrl = URLDecoder.decode(linkUrl)

		def cacheKey = videoUrl  + "_" +  fmt
	
		return new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: linkUrl, thumbnailUrl: thumbnailUrl, cacheKey: cacheKey, expiresOn: expiryDate, expiresImmediately: false)
	}
	
	static void main(args) {
		YouTubeTop100 extractor = new YouTubeTop100()
				
		assert extractor.extractorMatches( new URL("http://www.youtubetop100.com") )
		assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
		//WebResourceContainer container = extractor.extractItems( new URL("http://www.youtubetop100.com?res=720&codec=264&start=1"), 5)    
		WebResourceContainer container = extractor.extractItems( new URL("http://www.youtubetop100.com?res=720&start=1&for=20"), -1)    
		println container
		
		//ContentURLContainer result = extractor.extractUrl(container.getItems()[1], PreferredQuality.HIGH)
		//print result
		
		container.getItems().each {
            ContentURLContainer result = extractor.extractUrl(it, PreferredQuality.MEDIUM)
            println result
        }
	
		println "Finished"	

	}
}
