import groovy.json.*

import org.serviio.library.metadata.*
import org.serviio.library.online.*

/**
 * YouTube.com content URL extractor plugin, uses youtube-dl
 * 
 * @see https://rg3.github.io/youtube-dl/
 *  
 *
 */
class YouTubeDL extends WebResourceUrlExtractor {
	//set to full path to youtube-dl (e.g. c:\\Utils\\youtubedl\\youtube-dl.exe)
	final YOUTUBE_DL = "youtube-dl.exe"
	final AUDIO_ONLY = false

    final VALID_RESOURCE_URL = '^https?://www.youtube.com/.*$'
	/* Listed in order of quality */
    final availableFormats = ['37', '46', '22', '45', '35', '34', '18', '44', '43', '6', '5']
	final audioFormats = ['141', '140', '139', '172', '171', '251', '250', '249']
	
    
    String getExtractorName() {
        return getClass().getName()
    }
    
    boolean extractorMatches(URL feedUrl) {
        return feedUrl ==~ VALID_RESOURCE_URL
    }
	
	public int getVersion() {
		return 2;
	}
	
	protected int getExtractItemsTimeout() {
		return 60;
	}
	
	@Override
	WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
      if (maxItemsToRetrieve == -1) maxItemsToRetrieve = 50
	  
	  def items = getItemsYoutube_dl(resourceUrl, maxItemsToRetrieve)
		  
	  def containerThumbnailUrl = items?.find { it -> it.additionalInfo['thumb'] != null }?.additionalInfo['thumb']

      return new WebResourceContainer(items: items, thumbnailUrl: containerThumbnailUrl)
    }

	@Override
	protected ContentURLContainer extractUrl(WebResourceItem wrItem, PreferredQuality requestedQuality) {
        def contentUrl
		def expiryDate
		def expiresImmediately
		def cacheKey
		def format
 		def thumbnailUrl = wrItem.additionalInfo['thumb']
		 
		if (AUDIO_ONLY) {
			format = audioFormats.find { wrItem.additionalInfo[it] != null }
		} else {
			if(requestedQuality == PreferredQuality.HIGH) {
				// best quality, get the first from the list
				format = availableFormats.find { wrItem.additionalInfo[it] != null }
			} else if (requestedQuality == PreferredQuality.MEDIUM) {
				// work with subset of available formats, starting at the position of format 35 and then take the best quality from there
				format = availableFormats.getAt(4..availableFormats.size-1).find { wrItem.additionalInfo[it] != null }
			} else {
				format = availableFormats.reverse().find { wrItem.additionalInfo[it] != null }
			}
		}
		if (format == null)
			throw new Exception("could not find suitable video/audio format")
		contentUrl = wrItem.additionalInfo[format]
		
		expiresImmediately = true
		if(contentUrl.startsWith('http')) {
			// http URL
			def contentUrlParameters = [:]
			contentUrl.split('&').each{item -> addParameter(item, contentUrlParameters, '=')}
			if( contentUrlParameters['expire'] != null ) {
				expiryDate = new Date(Long.parseLong(contentUrlParameters['expire'])*1000)
				expiresImmediately = false
			}
		}
		 
		cacheKey = getCacheKey(wrItem.additionalInfo['videoId'], format)
		
		return new ContentURLContainer(fileType: (AUDIO_ONLY ? MediaFileType.AUDIO : MediaFileType.VIDEO),
			contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, expiresOn: expiryDate, expiresImmediately: expiresImmediately, cacheKey: cacheKey)

    }
 
    def addParameter(parameterString, parameters, separator) {
        def values = parameterString.split(separator)
        if( values.length == 2 ) {    
             parameters.put(values[0], values[1])
        }
    }   
	
	def String getCacheKey(String videoId, String qualityId) {
		"youtubedl_${videoId}_${qualityId}"
	}

	def getItemsYoutube_dl(URL resourceUrl, int maxItemsToRetrieve) {
		def items = []
		def outputProcessor = { BufferedReader r ->
			String line
			def parser = new JsonSlurper()//.setType(JsonParserType.INDEX_OVERLAY);
			while ((line = r.readLine()) != null) {
				//println line;
				def json = parser.parseText(line)
				def additionalInfo = json.formats.collectEntries { [(it.format_id): it.url] }
				additionalInfo.thumb = json.thumbnail
				additionalInfo.videoId = json.id
				items.add(new WebResourceItem(title : json.title,
					additionalInfo :  additionalInfo))
			}
			items.size()
		}
		executeYoutube_dl(outputProcessor, "--max-downloads",  maxItemsToRetrieve, "-j", resourceUrl)
		return items 
	}
	
	def executeYoutube_dl(Closure outputProcessor, Object... args) {
		runUpdateIfNeeded()
		def params = [ YOUTUBE_DL ] + args.toList()
		def proc = params.execute()
		def serr = new StringBuffer()
		proc.consumeProcessErrorStream(serr);
		def ok = outputProcessor(new BufferedReader(new InputStreamReader(proc.getInputStream())));
		proc.waitFor();
		if (!ok) {
			log("YOUTUBE_DL returned " + proc.exitValue()+": "+serr)
			throw new Exception("YOUTUBE_DL failed to retrieve info")
		}
	}
	
	/////////////////////////////
	// auto-update
	/////////////////////////////
	final int UPDATE_INTERVAL = 1 //in days
	Date lastUpdate = new Date().minus(UPDATE_INTERVAL);
	
	def runUpdateIfNeeded() {
		if (new Date().after(lastUpdate.plus(UPDATE_INTERVAL))) {
			def proc = [YOUTUBE_DL, "-U"].execute()
			proc.waitFor()
			log("YOUTUBE_DL run update: ${proc.in.text}")
			lastUpdate = new Date();
		}
	}
	/////////////////////////////////

    static void main(args) {
	// this is just to test
        YouTubeDL extractor = new YouTubeDL()
		
	assert extractor.extractorMatches( new URL("https://www.youtube.com/watch?v=AJtDXIazrMo&list=PLDcnymzs18LVXfO_x0Ei0R24qDbVtyy66") )
		
	WebResourceContainer container = extractor.extractItems( new URL("https://www.youtube.com/watch?v=AJtDXIazrMo&list=PLDcnymzs18LVXfO_x0Ei0R24qDbVtyy66"), 10)
	println container
	ContentURLContainer result = extractor.extractUrl(container.getItems()[2], PreferredQuality.MEDIUM)
	println result
    }

}