import groovy.json.JsonSlurper
import org.serviio.library.metadata.MediaFileType
import org.serviio.library.online.*
/**
 * WebResource extractor plugin for tunein.com.
 * 
 * @author Petr Nejedly
 *
 */
class TuneIn extends WebResourceUrlExtractor {
	
	final VALID_FEED_URL = '^(?:https?://)?(?:www\\.)?tunein\\.com/(radio/|search/\\?query=).+'

	final USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'

	String getExtractorName() {
		return 'Tune In'
	}
	
	boolean extractorMatches(URL feedUrl) {
		return feedUrl ==~ VALID_FEED_URL
	}
	
	int getVersion() {
		10
	}

	WebResourceContainer extractItems(URL resourceUrl, int maxItems) {
		String html = openURL(resourceUrl, USER_AGENT)

		def titleMatcher = html =~ '(?s)<h1.*?>(.+?)<'
		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.search.isEmpty() && !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 == '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)
					String stationTitle = stationNode.title.trim()
					String description = stationNode.description.trim()
					String imageUrl = stationNode.image
					String guideId = stationNode.guideId
					WebResourceItem item = new WebResourceItem(title: stationTitle, description: description, additionalInfo: ['guideId': guideId, 'imageUrl': imageUrl])
					items << item
					itemsAdded++
				}

				return new WebResourceContainer(title: pageTitle, items: items)
			}
		}
		return new WebResourceContainer(title: pageTitle, items: [])
	}
	
	ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {		
		String guideId = item.getAdditionalInfo()['guideId']
		assert guideId != null

		String jsonUrl = "https://opml.radiotime.com/Tune.ashx?id=$guideId&render=json&formats=mp3,aac&type=station&partnerId=RadioTime&version=3.12&itemUrlScheme=secure&reqAttempt=1"
		String playerJson = openURL(new URL(jsonUrl), USER_AGENT)

		def jsonResult = new JsonSlurper().parseText(playerJson)

		// streams, get required quality
		String streamUrl = getStreamUrl(jsonResult)
		if(streamUrl == null || streamUrl == "") {
			log('No suitable streams found')
			return null
		}
 
		return new ContentURLContainer(fileType: MediaFileType.AUDIO, contentUrl: streamUrl, thumbnailUrl: item.getAdditionalInfo()['imageUrl'], live: true)
	}
	
	private String getStreamUrl(Map jsonResult) {
		List streams = jsonResult['body']
		// only get streams that are represented as playlists and are live streams
		List directStreams = streams.findAll { it['is_direct'] == true && ['mp3','aac'].contains(it['media_type']) }
		String bestStreamUrl = findBestStream(directStreams)
		if(bestStreamUrl == null) {
			List proxyStreams = streams.findAll { it['is_direct'] == false && ['mp3','aac'].contains(it['media_type']) }
			return findBestStream(proxyStreams)
		} else {
			return bestStreamUrl
		}
	}

	private String findBestStream(List streams) {
		if( streams.size() > 0 ) {
			// ignore quality, audio bitrate is too low to make any difference, deliver the best available
			streams = streams.sort { it -> it['bitrate'].toInteger() }
			Map selectedStream = streams.last()
			String streamUrl = selectedStream['url']
			boolean isProxy = selectedStream['is_direct'] == false
			if(isProxy && streamUrl.startsWith("http")) {
				return getProxyStreamUrl(streamUrl)
			} else {
				return streamUrl
			}
		} else {
			return null
		}
	}

	private String getProxyStreamUrl(String proxyUrl) {
		String playerJson = openURL(new URL(proxyUrl), USER_AGENT)
		def jsonResult = new JsonSlurper().parseText(playerJson)

		List streams = jsonResult['Streams']
		// only get streams that are represented as playlists and are live streams
		streams = streams.findAll { it -> it['Type'] == 'Live' && ['MP3','Windows','AAC','Flash'].contains(it['MediaType']) && it['Url'].indexOf('adType') == -1 }
		if( streams.size() > 0 ) {
			// ignore quality, audio bitrate is too low to make any difference, deliver the best available
			streams = streams.sort { it -> it['Bandwidth'].toInteger() }
			Map selectedStream = streams.last()
			String streamUrl = selectedStream['Url']
			boolean hasPlaylist = Boolean.valueOf (selectedStream['HasPlaylist'])
			if(hasPlaylist && streamUrl.startsWith("http")) {
				return getUrlFromPlaylist(new URL(streamUrl))
			} else {
				return streamUrl
			}
		} else {
			return null
		}
	}
	
	/**
	 * Supports m3u playlists ATM
	 */
	protected getUrlFromPlaylist(URL playlistUrl) {
		assert playlistUrl != null
		
		String playlist = playlistUrl.getText()
		if(playlist.toLowerCase().startsWith("<asx")) {
			// asx playlists not supported
			return null			
		} else {
			// assume m3u playlist
		    List lines = playlist.readLines()
			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) {
				return getUrlFromPlaylist(new URL(url))
			}
			return url
		}
	}
	
	static void main(args) {
		// this is just to test
		TuneIn extractor = new TuneIn()
				
		assert extractor.extractorMatches( new URL("https://tunein.com/radio/London-United-Kingdom-r100780/?qlc=1") )
		assert extractor.extractorMatches( new URL("https://tunein.com/radio/Capital-London-958-s16534/") )
		assert !extractor.extractorMatches( new URL("https://google.com/feeds/api/standardfeeds/top_rated?time=today") )
		
//		WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/London-United-Kingdom-r100780/?qlc=1"), -1) // with embedded streams
		//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/search/05301/"), -1) // with external streams
		//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/Christmas-Holiday-Favorites-s257618/"), -1)
		WebResourceContainer container = extractor.extractItems( new URL("https://tunein.com/radio/Christmas-UK-s298937/"), -1)
		//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/radio/Christmas-Radio-s249952/"), -1)
		//WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/search/?query=kiss"), -1)
//		WebResourceContainer container = extractor.extractItems( new URL("http://tunein.com/search/?query=kiss%20fm%20955"), -1)
		//WebResourceContainer container = extractor.extractItems( new URL("https://tunein.com/search/?query=doowop"), -1)
		
		println container
		
        container.getItems().each {
            ContentURLContainer result = extractor.extractUrl(it, PreferredQuality.MEDIUM)
            println result
        }
	}
}
