import groovy.json.JsonSlurper
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.serviio.util.*
/**
 * WebResource extractor plugin for tunein.com.
 * 
 * @author Petr Nejedly
 * @updated In-com Software - 06.02.2021
 * 
 */
class TuneIn extends WebResourceUrlExtractor {
	final String VALID_FEED_URL = "^https?://(?:[^\\.]*.)?tunein\\.com/(radio/).+/\\?attributes=filter=s&offset=0&limit=200"
	final String VALID_SEARCHFEED_URL = "^https?://(?:[^\\.]*.)?tunein\\.com/(radio/|search/\\?query=).*\$"
	final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85) Gecko/20100101 Firefox/85"
	
	String getExtractorName() {
		return TuneIn
	}
	
	boolean extractorMatches(URL feedUrl) {
		return (feedUrl ==~ VALID_FEED_URL) || (feedUrl ==~ VALID_SEARCHFEED_URL)
	}
	
	int getExtractItemsTimeout() {
		return 90
	}
	
	int getVersion() {
		return 11
	}
	
	WebResourceContainer extractItems(URL feedUrl, int maxItems) {
		final String html = openURL(feedUrl, USER_AGENT)
		def titleMatcher = html =~ '(?s)<h1.*?>(.+?)<'
		final String pageTitle = titleMatcher[0][1].trim()
		List<WebResourceItem> items = []
		int initPos = html.indexOf('INITIAL_STATE=') + 14
		def json = new JsonSlurper().parseText(html.substring(initPos, html.indexOf(';</script>', initPos)))
		def containerNode = null
		if(!json.categories.isEmpty()) {
			containerNode = json.categories.iterator().next().value
		} else if(!json.search.isEmpty()) {
			containerNode = json.search.searchResults
		} else if(!json.profiles.isEmpty()) {
			containerNode = json.profiles.iterator().next().value
		}
		if(containerNode) {
			def stationsNode = containerNode.containerItems.find { it.containerType == 'Stations' || it.containerType == 'TrendingStations' || it.containerType == 'LocalStations' || it.containerType == 'LiveStations' }
			if (stationsNode != null) {
				def stations = stationsNode.children
				def itemsAdded = 0
				for (int i = 0; i < stations.size() && (maxItems == -1 || itemsAdded < maxItems); i++) {
					def stationNode = stations.get(i)
					final String stationTitle = stationNode.title.trim()
					final String stationImageUrl = stationNode.image
					final String stationGuideId = stationNode.guideId
					WebResourceItem item = new WebResourceItem(title: stationTitle, additionalInfo: ['guideId': stationGuideId, 'imageUrl': stationImageUrl])
					items << item
					itemsAdded++
				}
				return new WebResourceContainer(title: pageTitle, items: items)
			}
		}
		return new WebResourceContainer(title: pageTitle, items: [])
	}
	
	ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality HIGH) {
		final String guideId = item.getAdditionalInfo()['guideId']
		assert guideId != null
		final String jsonUrl = "https://opml.radiotime.com/Tune.ashx?id=$guideId&render=json&itemToken=BgcHAAYABgACAAIACwsAAgcFAAA&formats=mp3,aac,ogg,flash,html,hls&type=station&us_privacy=1YNY&paln=&gdpr=0&serial=ad460bb5-b5c4-40f7-b322-70ab1ab5f195&partnerId=RadioTime&version=4.4101&itemUrlScheme=secure&reqAttempt=1"
		final String playerJson = openURL(new URL(jsonUrl), USER_AGENT)
		def jsonResult = new JsonSlurper().parseText(playerJson)
		final String streamUrl = getStreamUrl(jsonResult)
		if(streamUrl == null) {
			String RetryStreamsUrl = jsonResult['streamUrl']
			if(RetryStreamsUrl != null) {
				final String RetryJsonUrl = "https://opml.radiotime.com/Tune.ashx?id=$guideId&render=json&itemToken=BgcHAAYABgACAAIACwsAAgcFAAA&formats=mp3,aac,ogg,flash,html,hls&type=station&us_privacy=1YNY&paln=&gdpr=0&serial=ad460bb5-b5c4-40f7-b322-70ab1ab5f195&partnerId=RadioTime&version=4.4101&itemUrlScheme=secure&reqAttempt=1"
				final String RetryPlayerJson = openURL(new URL(RetryJsonUrl), USER_AGENT)
				def RetryJsonResult = new JsonSlurper().parseText(RetryPlayerJson)
				streamUrl = RetryGetStreamUrl(RetryJsonResult)
			}
			if(streamUrl == null) {
				log('No suitable streams found')
				return null
			}
		}
		def live = true
		return new ContentURLContainer(fileType: MediaFileType.AUDIO, contentUrl: streamUrl, thumbnailUrl: item.getAdditionalInfo()['imageUrl'], expiresImmediately: false, live: live)
	}
	
	private String getStreamUrl(Map jsonResult) {
		List streams = jsonResult['body']
		List directStreams = streams.findAll { it['is_direct'] == true && ['mp3','aac','ogg','html','flash','hls'].contains(it['media_type']) }
		final String bestStreamUrl = findBestStream(directStreams)
		if(bestStreamUrl == null) {
			List proxyStreams = streams.findAll { it['is_direct'] == false && ['mp3','aac','ogg','html','flash','hls'].contains(it['media_type']) }
			return findBestStream(proxyStreams)
		} else {
			return bestStreamUrl
		}
	}
	
	private String RetryGetStreamUrl(Map RetryJsonResult) {
		List streams = RetryJsonResult['body']
		List directStreams = streams.findAll { it['is_direct'] == true && ['mp3','aac','ogg','html','flash','hls'].contains(it['media_type']) }
		final String bestStreamUrl = findBestStream(directStreams)
		if(bestStreamUrl == null) {
			List proxyStreams = streams.findAll { it['is_direct'] == false && ['mp3','aac','ogg','html','flash','hls'].contains(it['media_type']) }
			return findBestStream(proxyStreams)
		} else {
			return bestStreamUrl
		}
	}	
	
	private String findBestStream(List streams) {
		if( streams.size() > 0 ) {
			streams = streams.sort { it -> it['bitrate'].toInteger() }
			Map selectedStream = streams.last()
			final String streamUrl = selectedStream['url']
			boolean isProxy = selectedStream['is_direct'] == false
			if(isProxy && streamUrl.startsWith("https")) {
				return getProxyStreamUrl(streamUrl)
			} else {
				return streamUrl
			}
		} else {
			return null
		}
	}
	
	private String getProxyStreamUrl(String proxyUrl) {
		final String playerJson = openURL(new URL(proxyUrl), USER_AGENT)
		def jsonResult = new JsonSlurper().parseText(playerJson)
		List streams = jsonResult['Streams']
		streams = streams.findAll { it -> it['Type'] == 'Live' && ['MP3','Windows','AAC','Flash','HTML5'].contains(it['MediaType']) && it['Url'].indexOf('adType') == -1 }
		if( streams.size() > 0 ) {
			streams = streams.sort { it -> it['Bandwidth'].toInteger() }
			Map selectedStream = streams.last()
			final String streamUrl = selectedStream['Url']
			boolean hasPlaylist = Boolean.valueOf(selectedStream['HasPlaylist'])
			if(hasPlaylist && streamUrl.startsWith("https")) {
				return getUrlFromPlaylist(new URL(streamUrl))
			} else {
				return streamUrl
			}
		} else {
			return null
		}
	}
	
	protected getUrlFromPlaylist(URL playlistUrl) {
		assert playlistUrl != null
		final String playlist = playlistUrl.getText()
		if(playlist.toLowerCase().startsWith("<asx")) {
			return null
		} else {
		    List lines = playlist.readLines()
			final String url = lines[0]
			if(url.startsWith ('[playlist]') ) {
				for(String line : lines) {
					if(line.startsWith('File1')) {
						url = line.substring(line.indexOf ('=') + 1).trim()
						break
					}
				}
			}
			if (url.endsWith('.')) {
				url = url.substring(0, url.size()-1)
			}
			if(url.indexOf('.m3u') > -1 || url.indexOf('.pls') > -1 || url.indexOf('.m3u8') > -1 || url.indexOf('.f4m') > -1) {
				return getUrlFromPlaylist(new URL(url))
			}
			return url
		}
	}
	
	static void main(args) {
		TuneIn TuneIn = new TuneIn()
		def url = ""
		if(!args[0].contains("https"))
			url = "https://tunein.com/radio/trending/" + args[0]
		else
			url = args[0]
		TuneIn.extractItems(new URL(url)).getItems()[14].each { it->
			ContentURLContainer result = TuneIn.extractUrl(it, PreferredQuality.HIGH)
			println result
		}
	}
}