###########################################################
#
# Apple Quicktime Trailers script using iTunes XML files
#   -- based on AQTbrowser by Darkie
#
# @author Mouton <mouton_x@abuserz.com>
# @date 2005-10-10
# @version 1.37
#
###########################################################

maintainer = 'Mouton <mouton_x@abuserz.com>'

from os import getcwd, mkdir, path
from string import replace
from xbmc import Player as xbmcPlayer
from xbmcgui import DialogProgress as xbmcguiDialogProgress, Window as xbmcguiWindow, lock as xbmcguiLock, ControlImage as xbmcguiControlImage, ControlLabel as xbmcguiControlLabel, ControlButton as xbmcguiControlButton, ControlCheckMark as xbmcguiControlCheckMark, unlock as xbmcguiUnlock, ControlImage as xbmcguiControlImage, ControlList as xbmcguiControlList, Dialog as xbmcguiDialog
import QuickTimeBrowser_utils as qtb
try: from xbmcgui import Emulating
except: Emulating = False

########################################
## Change those if needed ##############

# Max. seconds to wait for a response when downloading files
HTTP_TIMEOUT = 10.0

# If you use something else that -1 here, this default will be used to skip the Select Video dialog
# 3=FullScreen, 2=Large, 1=Medium, 0=Small, -1=Show dialog
DEFAULT_SIZE = -1

## Change to True to show debug information on the script console (white button when you are in the Scripts window in XBMC)
DEBUG = False

# Number of times the script will try to download a URL before giving up
DOWNLOAD_ATTEMPTS = 5

# Where downloaded trailers will be saved (if selected)
# Default to Q:\Scripts\QuickTimeBrowser\Downloaded Trailers
DOWNLOAD_DIR = path.join(replace(getcwd(), ';', ''), 'QuickTimeBrowser', 'Downloaded Trailers')

# Fetch Opening this week, Exclusive & Newest trailers on each reload ? (True/False)
PARSE_ON_RESTART = False

# Define the background image to use, if any.
#BACKGROUND = None
BACKGROUND = 'background3.png'

## Config ends here ####################
########################################

# global progress dialog
dialogProgress = xbmcguiDialogProgress()

loadedMovies = []
config = qtb.Config()
firstUpdate = True

#defines
STATE_MOVIES_LIST       = 1
STATE_STUDIOS           = 2
STATE_CATEGORIES        = 3
STATE_FAVORITES         = 4
STATE_STUDIOS_MOVIES    = 5
STATE_CATEGORIES_MOVIES = 6

ACTION_MOVE_LEFT      = 1
ACTION_MOVE_RIGHT     = 2
ACTION_MOVE_UP        = 3
ACTION_MOVE_DOWN      = 4
ACTION_PAGE_UP        = 5
ACTION_PAGE_DOWN      = 6
ACTION_SELECT_ITEM    = 7
ACTION_HIGHLIGHT_ITEM = 8
ACTION_PARENT_DIR     = 9
ACTION_PREVIOUS_MENU  = 10
ACTION_SHOW_INFO      = 11
ACTION_PAUSE          = 12
ACTION_STOP           = 13
ACTION_NEXT_ITEM      = 14
ACTION_PREV_ITEM      = 15

qtb.DEBUG = DEBUG
qtb.HTTP_TIMEOUT = HTTP_TIMEOUT
qtb.DOWNLOAD_ATTEMPTS = DOWNLOAD_ATTEMPTS

TMP_DIR = path.join(replace(getcwd(), ';', ''), 'QuickTimeBrowser', 'Temp')
print('TMP_DIR = '+TMP_DIR)
from shutil import rmtree
rmtree(TMP_DIR,True)
try:
	mkdir(TMP_DIR)
except:
	pass

print('DOWNLOAD_DIR = '+DOWNLOAD_DIR)
if not path.exists(DOWNLOAD_DIR):
	try:
		mkdir(DOWNLOAD_DIR)
	except Exception:
		pass

def printDebug(text,mode=0):
	qtb.printDebug(text,mode)

# dialog for selecting the video size
# small / medium / large / fullscreen
class DialogVideoSize(xbmcguiWindow):
	def __init__(self):
		if Emulating:
			self.dialog = w
		else:
			self.dialog = self
		self.result = -1
		self.scaleX = float(self.getWidth())  / float(720)
		self.scaleY = float(self.getHeight()) / float(480)
		self.buttons = []

	def ask(self,trailers):
		size = len(trailers)
		if size > 27:
			size = 27

		xbmcguiLock()

		if BACKGROUND and not Emulating:
			self.dialog.addControl(xbmcguiControlImage(0,0, int(720*self.scaleX),int(480*self.scaleY), BACKGROUND))

		self.dialog.addControl(xbmcguiControlLabel(int(60*self.scaleX), int(100*self.scaleY), int(50*self.scaleX), int(20*self.scaleY), 'Select Video'))

		# create and add buttons
		YOffset = 72
		for i in range(1,size+1):
			btn_ypos = (i-1) % 9 * 35 + 35
			if i <= 9:
				XOffset = 210
			else:
				if i <= 18:
					XOffset = 365
				else:
					XOffset = 520
			button = xbmcguiControlButton(int(XOffset*self.scaleX), int((YOffset +  btn_ypos)*self.scaleY), int(150*self.scaleX), int(30*self.scaleY),  qtb.getTrailerName(trailers[i-1]))
			self.dialog.addControl(button)
			if i == 1:
				self.setFocus(button)
			else:
				self.buttons[0].controlUp(button)     # 1st button + up = loop to last button
				self.buttons[i-2].controlDown(button) # previous button + down = this button
				button.controlUp(self.buttons[i-2])   # this button + up = previous button
				button.controlDown(self.buttons[0])   # this button + down = loop to 1st button
			self.buttons.append(button)

		xbmcguiUnlock()
		if not Emulating:
			self.doModal()

	def onAction(self, action):
		if action == ACTION_PREVIOUS_MENU:
			self.result = -1
			self.close()

	def onControl(self, control):
		for i in range(0,len(self.buttons)):
			if control == self.buttons[i]:
				self.result = i
				self.done()

	def done(self):
		self.close()

class AQTBrowser(xbmcguiWindow):
	def __init__(self):
		if Emulating: xbmcguiWindow.__init__(self)

		self.scaleX = ( float(self.getWidth())  / float(720) )
		self.scaleY = ( float(self.getHeight()) / float(480) )

		self.currentState = None
		self.selectedDistributor = -1

		xbmcguiLock()

		if BACKGROUND and not Emulating:
			self.addControl(xbmcguiControlImage(0,0, int(720*self.scaleX),int(480*self.scaleY), BACKGROUND))
		self.list = xbmcguiControlList(int(211*self.scaleX), int(100*self.scaleY), int(450*self.scaleX), int(350*self.scaleY))

		# add buttons
		self.btnExclusive = xbmcguiControlButton(int(60*self.scaleX), int(100*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Exclusive")
		self.btnOpening = xbmcguiControlButton(int(60*self.scaleX), int(132*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Opening Week")
		self.btnNewest = xbmcguiControlButton(int(60*self.scaleX), int(164*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Newest")
		self.btnStudios = xbmcguiControlButton(int(60*self.scaleX), int(196*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Studios")
		self.btnGenres = xbmcguiControlButton(int(60*self.scaleX), int(228*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Genres")
		self.btnAll = xbmcguiControlButton(int(60*self.scaleX), int(260*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Show All")

		self.btnNew = xbmcguiControlButton(int(60*self.scaleX), int(324*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "New Trailers")
		self.btnFavorites = xbmcguiControlButton(int(60*self.scaleX), int(356*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Favorites")
		self.btnUpdate = xbmcguiControlButton(int(60*self.scaleX), int(388*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Update")
		self.btnUpdate2 = xbmcguiControlButton(int(60*self.scaleX), int(388*self.scaleY), int(140*self.scaleX), int(30*self.scaleY), "Update All")

		self.checkDownload = xbmcguiControlCheckMark(int(71*self.scaleX), int(439*self.scaleY), int(120*self.scaleX), int(20*self.scaleY), "Download", checkWidth=20, checkHeight=20, alignment=0)
		self.checkSave = xbmcguiControlCheckMark(int(191*self.scaleX), int(439*self.scaleY), int(100*self.scaleX), int(20*self.scaleY), "Save", checkWidth=20, checkHeight=20, alignment=0 )

		self.labelStatus = xbmcguiControlLabel(int(300*self.scaleX), int(439*self.scaleY), int(150*self.scaleX), int(20*self.scaleY), "Saving data files..." )

		self.addControl(self.list)

		self.addControl(self.btnExclusive)
		self.addControl(self.btnOpening)
		self.addControl(self.btnNewest)
		self.addControl(self.btnStudios)
		self.addControl(self.btnGenres)
		self.addControl(self.btnAll)
		self.addControl(self.btnNew)
		self.addControl(self.btnFavorites)
		self.addControl(self.btnUpdate)
		self.addControl(self.checkDownload)
		self.addControl(self.checkSave)

		# Load config
		self.checkDownload.setSelected(config.download)
		self.checkSave.setSelected(config.save)

		self.list.controlLeft(self.btnExclusive)

		self.btnExclusive.controlUp(self.checkDownload)
		self.btnExclusive.controlDown(self.btnOpening)
		self.btnExclusive.controlRight(self.list)

		self.btnOpening.controlUp(self.btnExclusive)
		self.btnOpening.controlDown(self.btnNewest)
		self.btnOpening.controlRight(self.list)

		self.btnNewest.controlUp(self.btnOpening)
		self.btnNewest.controlDown(self.btnStudios)
		self.btnNewest.controlRight(self.list)

		self.btnStudios.controlUp(self.btnNewest)
		self.btnStudios.controlDown(self.btnGenres)
		self.btnStudios.controlRight(self.list)

		self.btnGenres.controlUp(self.btnStudios)
		self.btnGenres.controlDown(self.btnAll)
		self.btnGenres.controlRight(self.list)

		self.btnAll.controlUp(self.btnGenres)
		self.btnAll.controlDown(self.btnNew)
		self.btnAll.controlRight(self.list)

		self.btnNew.controlUp(self.btnAll)
		self.btnNew.controlDown(self.btnFavorites)
		self.btnNew.controlRight(self.list)

		self.btnFavorites.controlUp(self.btnNew)
		self.btnFavorites.controlDown(self.btnUpdate)
		self.btnFavorites.controlRight(self.list)

		self.btnUpdate.controlUp(self.btnFavorites)
		self.btnUpdate.controlDown(self.checkDownload)
		self.btnUpdate.controlRight(self.list)

		self.checkDownload.controlUp(self.btnUpdate)
		self.checkDownload.controlDown(self.btnExclusive)
		self.checkDownload.controlRight(self.checkSave)

		self.checkSave.controlUp(self.btnUpdate)
		self.checkSave.controlDown(self.btnExclusive)
		self.checkSave.controlLeft(self.checkDownload)
		self.checkSave.controlRight(self.list)

		xbmcguiUnlock()

	def onAction(self, action):
		global loadedMovies
		# X on controller or 1 on remote adds or remove favorites
		if action == 18 or action == 59:
			if self.currentState == STATE_MOVIES_LIST or self.currentState == STATE_STUDIOS_MOVIES or self.currentState == STATE_CATEGORIES_MOVIES:
				movie = loadedMovies[self.list.getSelectedPosition()]
				config.favorites.append(movie)
				config.dump()
				xbmcguiDialog().ok('Favorites',movie.title + ' added to Favorites.')
			elif self.currentState == STATE_FAVORITES:
				if self.list.getSelectedPosition() < 0 or len(loadedMovies) == 0:
					return
				movie = loadedMovies[self.list.getSelectedPosition()]
				config.favorites.remove(movie)
				config.dump()
				xbmcguiDialog().ok('Favorites',movie.title + ' removed from your Favorites.')
				loadedMovies = config.favorites
				xbmcguiLock()
				self.list.reset()
				for movie in loadedMovies:
					self.list.addItem(movie.title)
				xbmcguiUnlock()
		if action == ACTION_PREVIOUS_MENU:
			self.close()
		elif action == ACTION_PARENT_DIR:
			if self.currentState == STATE_STUDIOS_MOVIES:
				printDebug('Studios button pressed (back to parent).')
				self.currentState = STATE_STUDIOS
				studios = qtb.getAllStudios()
				xbmcguiLock()
				self.list.reset()
				for studio in studios:
					self.list.addItem(studio.name)
				xbmcguiUnlock()
			if self.currentState == STATE_CATEGORIES_MOVIES:
				printDebug('Genres button pressed (back to parent).')
				self.currentState = STATE_CATEGORIES
				categories = qtb.getAllCategories()
				xbmcguiLock()
				self.list.reset()
				for category in categories:
					self.list.addItem(category.name)
				xbmcguiUnlock()

	def onControl(self, control):
		global firstUpdate, loadedMovies
		loadMovies = False
		old_loadedMovies = loadedMovies
		if control == self.btnFavorites:
			printDebug('Favorites button pressed.')
			loadedMovies = config.favorites
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnFavorites)
				self.currentState = STATE_FAVORITES
		elif control == self.btnUpdate:
			printDebug('Update button pressed.')
			if firstUpdate:
				qtb.parse(loaded=True,update_all=False)
				firstUpdate = False
			else:
				qtb.parse(loaded=True,update_all=True)
				firstUpdate = True
			# Switch Update and Update All buttons
			self.removeControl(self.btnUpdate)
			tmp = self.btnUpdate
			self.btnUpdate = self.btnUpdate2
			self.btnUpdate2 = tmp
			self.addControl(self.btnUpdate)
			self.btnUpdate.controlUp(self.btnFavorites)
			self.btnUpdate.controlDown(self.checkDownload)
			self.btnUpdate.controlRight(self.list)
			self.btnFavorites.controlDown(self.btnUpdate)
			self.checkDownload.controlUp(self.btnUpdate)
			self.setFocus(self.btnUpdate)
		elif control == self.checkSave:
			printDebug('Save checkbox pressed.')
			if self.checkSave.getSelected():
				self.checkDownload.setSelected(True)
			config.download = self.checkDownload.getSelected()
			config.save = self.checkSave.getSelected()
			config.dump()
		elif control == self.checkDownload:
			printDebug('Download checkbox pressed.')
			if not self.checkDownload.getSelected() and self.checkSave.getSelected():
				xbmcguiDialog().ok('Error','For trailers to be saved, they need to be downloaded first.')
				self.checkDownload.setSelected(True)
			config.download = self.checkDownload.getSelected()
			config.save = self.checkSave.getSelected()
			config.dump()
		elif control == self.btnExclusive:
			printDebug('Exclusive button pressed.')
			loadedMovies = qtb.exclusiveMovies
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnExclusive)
				self.currentState = STATE_MOVIES_LIST
		elif control == self.btnOpening:
			printDebug('Opening This Week button pressed.')
			loadedMovies = qtb.openingMovies
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnOpening)
				self.currentState = STATE_MOVIES_LIST
		elif control == self.btnNewest:
			printDebug('Newest button pressed.')
			loadedMovies = qtb.newestMovies
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnNewest)
				self.currentState = STATE_MOVIES_LIST
		elif control == self.btnStudios:
			printDebug('Studios button pressed.')
			self.list.controlLeft(self.btnStudios)
			self.currentState = STATE_STUDIOS
			studios = qtb.getAllStudios()
			xbmcguiLock()
			self.list.reset()
			for studio in studios:
				self.list.addItem(studio.name)
			xbmcguiUnlock()
		elif control == self.btnGenres:
			printDebug('Genres button pressed.')
			self.list.controlLeft(self.btnGenres)
			self.currentState = STATE_CATEGORIES
			categories = qtb.getAllCategories()
			xbmcguiLock()
			self.list.reset()
			for category in categories:
				self.list.addItem(category.name)
			xbmcguiUnlock()
		elif control == self.btnAll:
			printDebug('All button pressed.')
			loadedMovies = qtb.getAllMovies(None,True)
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnAll)
				self.currentState = STATE_MOVIES_LIST
		elif control == self.btnNew:
			printDebug('New Trailers button pressed.')
			loadedMovies = qtb.newMovies
			loadMovies = True
			if len(loadedMovies) > 0:
				self.list.controlLeft(self.btnNew)
				self.currentState = STATE_MOVIES_LIST
		elif control == self.list:
			if self.currentState == STATE_STUDIOS:
				# We selected a Studio, show all movies produced by it
				studios = qtb.getAllStudios()
				printDebug('Studio selected from list: ' + studios[self.list.getSelectedPosition()].name)
				loadedMovies = qtb.getAllMoviesFromStudio(studios[self.list.getSelectedPosition()])
				loadMovies = True
				if len(loadedMovies) > 0:
					self.currentState = STATE_STUDIOS_MOVIES
			elif self.currentState == STATE_CATEGORIES:
				# We selected a Category, show all movies of this genre
				categories = qtb.getAllCategories()
				printDebug('Category selected from list: ' + categories[self.list.getSelectedPosition()].name)
				loadedMovies = qtb.getAllMoviesFromCategory(categories[self.list.getSelectedPosition()])
				loadMovies = True
				if len(loadedMovies) > 0:
					self.currentState = STATE_CATEGORIES_MOVIES
			elif self.currentState == STATE_MOVIES_LIST or self.currentState == STATE_STUDIOS_MOVIES or self.currentState == STATE_CATEGORIES_MOVIES or self.currentState == STATE_FAVORITES:
				printDebug('Number of loaded movies = ' + str(len(loadedMovies)) + ', selected item = ' + str(self.list.getSelectedPosition()))
				self.Play(loadedMovies[self.list.getSelectedPosition()])

		if loadMovies:
			if len(loadedMovies) > 0:
				xbmcguiLock()
				self.list.reset()
				for movie in loadedMovies:
					self.list.addItem(movie.title)
				xbmcguiUnlock()
			else:
				loadedMovies = old_loadedMovies
				xbmcguiDialog().ok('Error','There are no trailers available for this category.')

	def Parse(self):
		loaded = qtb.loadDataFiles(ResetTypes=False)
		if PARSE_ON_RESTART:
			result = qtb.parse(loaded)
		else:
			if loaded:
				qtb.loadMovieVars()
			result = 0
		if len(qtb.newMovies) > 0:
			self.setFocus(self.btnNew)
			self.onControl(self.btnNew)
		else:
			self.setFocus(self.btnExclusive)
			self.onControl(self.btnExclusive)
		return result

	def Play(self, movie):
		if movie == None:
			printDebug('cannot play None object.')
			return None
		printDebug('Playing movie: ' + movie.title.encode('ascii','replace'))

		dialogProgress.create("Please wait...","Loading available trailers for this movie.")
		movie.loadMovieTrailersUrls()
		dialogProgress.close()
		if len(movie.trailers) == 0:
			printDebug('No Trailer has been found for this movie using the URL : ' + movie.xmlUrl)
			xbmcguiDialog().ok('Error!','No Trailer has been found for this movie using the URL :\n' + movie.xmlUrl)
			return

		if DEFAULT_SIZE == -1 or movie.hasCustomTrailer:
			d = DialogVideoSize()
			d.ask(movie.trailers)
			sel = d.result
			del d
		else:
			sel = DEFAULT_SIZE

		if sel >= len(movie.trailers):
			sel = len(movie.trailers) - 1

		if sel != -1:
			link = movie.getMov(sel)
			link = link.replace("h.i320", "h320")
			link = link.replace("h.i480", "h480")
			link = link.replace("h.i640", "h640w")
			link = link.replace("_240.mov", "_m240.mov")
			link = link.replace("_320.mov", "_m320.mov")
			link = link.replace("_480.mov", "_m480.mov")

			if link == None or link == '':
				xbmcguiDialog().ok('Error!','No Trailer has been found for this movie using the URL:\n' + movie.trailers[sel])
			else:
				filename_download = get_filename_from_link(DOWNLOAD_DIR,link)
				filename_tmp = get_filename_from_link(TMP_DIR,link)
				if qtb.FileExists(filename_download):
					link = filename_download
				elif qtb.FileExists(filename_tmp):
					link = filename_tmp
				else:
					if self.checkDownload.getSelected():
						dialogProgress.create("Please wait...","Downloading file...")
						printDebug('Downloading file: ' + link)
						total_size = qtb.url_size(link)
						stream = qtb.download_file(link,True)
						movie_file = open(filename_tmp, 'wb')
						printDebug('Reading downloaded file.')
						chunk_size = 16*1024
						self.current_size = 0
						dialogProgress.update(0)

						self.stop = False
						from threading import Thread
						speedSubThread = Thread(target=self.speed_meter, args=())
						speedSubThread.start()
						while 1:
							chunk = stream.read(chunk_size)
							if len(chunk) == 0: break
							movie_file.write(chunk)
							self.current_size = self.current_size + len(chunk)
							dialogProgress.update(int(self.current_size*100/total_size))
						self.stop = True
						movie_file.close()

						link = filename_tmp
						self.removeControl(self.labelStatus)
						dialogProgress.close()
						if self.checkSave.getSelected():
							dialogProgress.create("Please wait...","Saving file...")
							printDebug('Saving file to: ' + filename_download)
							from shutil import move
							move(filename_tmp,filename_download)
							link = filename_download
							dialogProgress.close()

				try:
					printDebug('Playing: ' + link)
					player = customPlayer()
					player.play(link)
				except Exception,e:
					print('Error caught in xbmc.Player().play('+link+')')
					print('Error: ' + str(e))

	def speed_meter(self):
		from time import sleep
		addSpeedCheckpoint(0)
		if not Emulating:
			self.labelStatus.setLabel('Current speed: 0 kB/s')
			self.addControl(self.labelStatus)
		while not self.stop:
			sleep(1.000)
			addSpeedCheckpoint(self.current_size)
			speed = getCurrentSpeed()
			if Emulating:
				print('Current speed: ' + speed + ' kB/s')
			else:
				self.labelStatus.setLabel('Current speed: ' + speed + ' kB/s')

def addSpeedCheckpoint(size_value):
	from time import time
	time_value = time()
	global speed_values
	if (size_value == 0):
		speed_values = {}
	while len(speed_values) >= 5:
		min_key = 9999999999
		for key,value in speed_values.iteritems():
			if key < min_key:
				min_key = key
		del speed_values[min_key]
	speed_values[time_value] = size_value

def get_filename_from_link(directory,link):
	from string import rfind
	if rfind(link,'/') != -1:
		filename = path.join(directory, link[rfind(link,'/')+1:])
	else:
		filename = path.join(directory, link[rfind(link,'%2F')+3:])
	return filename

def getCurrentSpeed():
	min_key = 9999999999
	max_key = 0
	global speed_values
	for key,value in speed_values.iteritems():
		if key < min_key:
			min_key = key
		if key > max_key:
			max_key = key

	start_time = min_key
	start_size = speed_values[min_key]
	end_time = max_key
	end_size = speed_values[max_key]

	size = end_size - start_size
	time = end_time - start_time
	if time == 0: return '0'
	speed = float(size/1024/time)
	return "%.0f" % speed

class customPlayer(xbmcPlayer):
	def __init__(self):
		return

	def onPlayBackStarted(self):
		from time import sleep
		printDebug('Playback started.')
		while self.isPlayingVideo():
			sleep(1.000)
		self.onPlayBackEnded()

	def onPlayBackStopped(self):
		self.onPlayBackEnded()

	def onPlayBackEnded(self):
		printDebug('Playback ended.')

config.load()
w = AQTBrowser()
if w.Parse() != -1:
	w.doModal()
	rmtree(TMP_DIR,True)
	config.dump()
	# Save data files
	from threading import Thread
	checkSubThread = Thread(target=qtb.saveDataFiles, args=(True, False))
	checkSubThread.start()

del w
