#
#	THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE
#	THE 'COPYING' FILE FOR DETAILS
#
#	This module handles the creation of mixdown actions.
#	
#
#-------------------------------------------------------------------------------

import os
import gtk, gtk.glade
import gobject

import Globals
import ProjectManager

import gettext
_ = gettext.gettext


class MixdownActionException(Exception):
	"""
	An exception that occurs when there is a problem when the RunAction() method
	in a MixdownAction. The 'message' attribute is a string to display to the user.
	"""
	def __init__(self, message):
		Exception.__init__(self, message)

#=========================================================================

class RegisterMixdownActionAPI:
	"""
	This class handles the registering and deregistering of MixdownActions.
	"""
	
	#_____________________________________________________________________
	
	def __init__(self):
		"""
		Creates a new instance of RegisterMixdownActionAPI.
		"""
		self.registeredActions = []
		
	#_____________________________________________________________________
	
	def RegisterMixdownActions(self, mixdownActions):
		"""
		Called when MixdownActions needs to be registered.
		Appends the MixdownActions specified to the registered mixdown action list (self.registeredActions)
	
		Parameters:
			mixdownActions -- reference to a tuple containing MixdownAction objects
		"""
		for action in mixdownActions:
			self.registeredActions.append(action)
		
	#_____________________________________________________________________

	def DeregisterMixdownActions(self, mixdownActions):
		"""
		Called when MixdownActions needs to be deregistered.
		Removes the MixdownActions specified from the registered mixdown action list (self.registeredActions)
	
		Parameters:
			mixdownAction -- reference to a tuple containing MixdownAction objects
		"""
		for action in mixdownActions:
			self.registeredActions.remove(action)
	
	#_____________________________________________________________________
	
	def ReturnAllActions(self):
		"""
		Returns all MixdownActions in the registered mixdown action list (self.registeredActions)
		"""
		return self.registeredActions
	
	#_____________________________________________________________________

#=========================================================================

class MixdownAction(gobject.GObject):
	"""
	Represents a mixdown action, used as a building block for mixdown profiles.
	"""

	"""
	Signals:
		"action-configured" -- MixdownAction has been configured
	"""
	
	__gsignals__ = {
		"action-configured" 	: ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () )
	}

	#_____________________________________________________________________
	
	def __init__(self):
		"""
		Creates a new instance of MixdownAction.
		"""
		gobject.GObject.__init__(self)
		
		# A MixdownAction object has a name, description and icon path.
		self.name = None
		self.description = None
		self.iconPath = None
		
		self.config = {}
		self.isConfigured = None
		
		# to be used by the configuration window in MixdownAction sublcasses.
		self.dialogIcon = gtk.gdk.pixbuf_new_from_file( os.path.join(Globals.IMAGE_PATH, "jokosher.png") )

	
	#_____________________________________________________________________
	
	def ConfigureAction(self):
		"""
		Subclasses should override this method to do configuration.
		This method should handle the configuration of MixdownActions, such as
		modifying details associated with the MixdownAction.
		"""
		pass
		
	#_____________________________________________________________________
	
	def RunAction(self):
		"""
		Do whatever this action actually does.
		"""
		pass
		
	#_____________________________________________________________________
	
#=========================================================================

class ExportAsFileType(MixdownAction):
	"""
	Defines a mixdown action that exports the audio in the user's project to a given file.
	"""
	
	#_____________________________________________________________________
	
	def __init__(self, project):
		"""
		Special init function for this particular MixdownAction, which takes
		a reference to the project as a parameter; this is so we can call back
		into the project to do the export.
		This means that the core code must special-case this action creation,
		and if it's creating one of these then pass it a project.
		
		Parameters:
			project -- the current Jokosher Project.
		"""
		MixdownAction.__init__(self)
		self.name = _("Export File")
		self.description = _("Export a file to the destination of your choosing.")
		# stock gtk icon
		self.iconPath = gtk.STOCK_JUMP_TO
		
		self.project = project # note: this is special to this action, and needs
		                       # to be passed, so this action needs special handling
		                       # in the core.
	
	#_____________________________________________________________________
	
	def ConfigureAction(self):
		"""
		See MixdownAction.ConfigureAction
		"""
		self.configureExportTree = gtk.glade.XML(Globals.GLADE_PATH, "ConfigureExportFileAction")
		signals = {
			"on_cancel_button_clicked" : self.__OnCancel,
			"on_ok_button_clicked" : self.__OnOK,
			"on_local_radio_toggled" : self.__OnLocalRadioToggled,
			"on_server_radio_toggled" : self.__OnServerRadioToggled
		}
			
		self.configureExportTree.signal_autoconnect(signals)
		
		self.configureExportWindow = self.configureExportTree.get_widget("ConfigureExportFileAction")
		self.fileEntry = self.configureExportTree.get_widget("file_entry")
		self.formatCombo = self.configureExportTree.get_widget("format_combo")
		self.localRadio = self.configureExportTree.get_widget("local_radio")
		self.serverRadio = self.configureExportTree.get_widget("server_radio")
		self.detailsVBox = self.configureExportTree.get_widget("details_vbox")
		
		self.formatModel = gtk.ListStore(str, str, str) # description, extension, pipeline
			
		# set some properties
		self.configureExportWindow.set_icon(self.dialogIcon)
		self.formatCombo.set_model(self.formatModel)
		self.formatCombo.clear()
		desc = gtk.CellRendererText()
		self.formatCombo.pack_start(desc)
		self.formatCombo.add_attribute(desc, "text", 0)

		# construct the configuration widgets
		self.__ConstructTable()
		self.__CreateWidgets()
		self.__AttachWidgets()
		self.__BuildLocalHBox()
		self.__PopulateFormatComboBox()
		self.__LoadConfiguration()

		if self.localRadio.get_active():
			self.detailsVBox.pack_start(self.localHBox, False, False)
		else:
			self.detailsVBox.pack_start(self.serverTable, False, False)
		self.detailsVBox.show_all()
		
	#_____________________________________________________________________

	def RunAction(self):
		"""
		See MixdownAction.RunAction
		"""
		filePath = os.path.join( self.config["location"], "%s.%s" % ( self.config["filename"], self.config["filetype"] ) )
		try:
			self.project.Export(filePath, self.config["pipeline"])
		except ProjectManager.ProjectExportException, e:
			msg = "%s"
			if e.errno == ProjectManager.ProjectExportException.MISSING_ELEMENT:
				msg = _("An encoding plugin could not be found: %s.\nPlease make sure all the plugins required for the specified format are installed.")
			elif e.errno == ProjectManager.ProjectExportException.INVALID_ENCODE_BIN:
				msg = _("The encoding bin description is invalid: %s")
			msg %= e.message
			raise MixdownActionException(msg)
		
	#_____________________________________________________________________
	
	def __ConstructTable(self):
		"""
		Called when the server table (self.serverTable) needs to be constructed.
		Creates the server table.
		"""
		self.serverTable = gtk.Table(4, 2)
		self.serverTable.set_row_spacing(0, 6)
		self.serverTable.set_row_spacing(1, 6)
		self.serverTable.set_row_spacing(2, 6)
		self.serverTable.set_row_spacing(3, 6)
		self.serverTable.set_col_spacing(0, 12)
		self.serverTable.set_col_spacing(1, 12)
		
	#_____________________________________________________________________
	
	def __CreateWidgets(self):
		"""
		Called when widgets in the server table (self.serverTable) need to be created.
		Creates the widgets associated with the server table.
		"""
		self.serverType = gtk.Label(_("Server type:"))
		self.serverType.set_alignment(0, 0.5)
		
		self.serverTypeCombo = gtk.ComboBox()
		self.serverTypeCombo.clear()
		textrend = gtk.CellRendererText()
		self.serverTypeCombo.pack_start(textrend)
		self.serverTypeCombo.add_attribute(textrend, "text", 0)
		self.serverTypeModel = gtk.ListStore(str)
		self.serverTypeModel.append(("HTTP",))
		self.serverTypeModel.append(("FTP",))
		self.serverTypeCombo.set_model(self.serverTypeModel)
		
		self.serverLocation = gtk.Label(_("Server location:"))
		self.serverLocation.set_alignment(0, 0.5)
		self.serverLocationEntry = gtk.Entry()
		
		self.username = gtk.Label(_("Username:"))
		self.username.set_alignment(0, 0.5)
		self.usernameEntry = gtk.Entry()
		
		self.password = gtk.Label(_("Password:"))
		self.password.set_alignment(0, 0.5)
		self.passwordEntry = gtk.Entry()
		self.passwordEntry.set_visibility(False)
		
	#_____________________________________________________________________

	def __AttachWidgets(self):
		"""
		Called when the server widgets need to be added to the server table (self.serverTable).
		Attaches the server widgets to the server table.
		"""
		self.serverTable.attach(self.serverType, 0, 1, 0, 1)
		self.serverTable.attach(self.serverTypeCombo, 1, 2, 0, 1)
		self.serverTable.attach(self.serverLocation, 0, 1, 1, 2)
		self.serverTable.attach(self.serverLocationEntry, 1, 2, 1, 2)
		self.serverTable.attach(self.username, 0, 1, 2, 3)
		self.serverTable.attach(self.usernameEntry, 1, 2, 2, 3)
		self.serverTable.attach(self.password, 0, 1, 3, 4)
		self.serverTable.attach(self.passwordEntry, 1, 2, 3, 4)
		
	#_____________________________________________________________________

	def __BuildLocalHBox(self):
		"""
		Called when the location hbox needs to be created.
		Sets up the location hbox in configuration dialog.
		"""
		self.localHBox = gtk.HBox()
		self.localHBox.set_spacing(6)
		self.localEntry = gtk.Entry()
		self.localButton = gtk.Button()
		self.localButton.connect("clicked", self.__ShowDirectoryChooserDialog)
		self.openImage = gtk.Image()
		self.openImage.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
		self.localButton.set_image(self.openImage)
		self.localHBox.pack_start(gtk.Label(_("Location:")), False, False)
		self.localHBox.pack_start(self.localEntry, True, True)
		self.localHBox.pack_start(self.localButton, False, False)
	
	#_____________________________________________________________________
	
	def __PopulateFormatComboBox(self):
		"""
		Called when the format combo model (self.formatModel) needs to be populated.
		Populates the format combo model with Jokosher's currently available export formats.
		"""
		for format in Globals.EXPORT_FORMATS:
			self.formatModel.append((format["description"], format["extension"], format["pipeline"]))
				
	#_____________________________________________________________________
	
	def __LoadConfiguration(self):
		"""
		Called when the configuration for the configuration window needs to be set.
		Updates the widgets to reflect the configuration details that have been loaded.
		"""
		if self.config.has_key("filename"):
			self.fileEntry.set_text(self.config["filename"])
		if self.config.has_key("location"):
			self.localEntry.set_text(self.config["location"])
		if self.config.has_key("filetype"):
			active = -1
			for item in self.formatModel:
				active += 1
				if item[1] == self.config["filetype"]:
					self.formatCombo.set_active(active)
					break

	#_____________________________________________________________________
	
	def __FinishConfiguration(self):
		"""
		Called when configuration needs to be finished.
		This method finishes the action's configuration by modifying
		the configuration dictionary (self.config) to use the new
		configuration details.
		"""
		self.config["filename"] = self.fileEntry.get_text()
		self.config["location"] = self.localEntry.get_text()
		self.config["filetype"] = self.formatModel[self.formatCombo.get_active()][1]
		self.config["pipeline"] = self.formatModel[self.formatCombo.get_active()][2]
		
		fullPath = os.path.join( self.config["location"], "%s.%s" % ( self.config["filename"], self.config["filetype"] ) )
		
		if fullPath in self.project.GetInputFilenames():
			directory, fileName, ext = self.__MakeUniqueFilename(fullPath)
			self.config["filename"] = fileName
			self.config["location"] = directory
			self.config["filetype"] = ext.split(".")[1]
			
		Globals.settings.general["projectfolder"] = os.path.dirname(fullPath)
		Globals.settings.write()

	#_____________________________________________________________________
	
	def __ShowDirectoryChooserDialog(self, widget):
		"""
		Shows a gtk.FileChooserDialog, allowing the user
		to select which directory their audio should be exported to.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
		"""
		chooser = gtk.FileChooserDialog((_('Select Export Location')), None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
		if os.path.exists(Globals.settings.general["projectfolder"]):
			chooser.set_current_folder(Globals.settings.general["projectfolder"])
		else:
			chooser.set_current_folder(os.path.expanduser("~"))
		chooser.set_icon(self.dialogIcon)

		chooser.set_default_response(gtk.RESPONSE_OK)
		chooser.set_transient_for(self.configureExportWindow)
		response = chooser.run()
			
		if response == gtk.RESPONSE_OK:
			filename = chooser.get_filename()
			self.localEntry.set_text(filename)
		chooser.destroy()
		
	#_____________________________________________________________________
	
	def __OnCancel(self, widget):
		"""
		Called when the Cancel Button is clicked.
		Destroys the configuration window.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
		"""
		self.configureExportWindow.destroy()
		
	#_____________________________________________________________________
		
	def __OnOK(self, widget):
		"""
		Called when the OK Button is clicked.
		Finishes the mixdown action's configuration.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
		"""
		# the local directory is the only option which works right now
		if self.localRadio.get_active():
			if self.fileEntry.get_text() and self.localEntry.get_text():
				self.__FinishConfiguration()
		
		self.emit("action-configured")
		self.configureExportWindow.destroy()
	
	#_____________________________________________________________________
	
	def __OnLocalRadioToggled(self, widget):
		"""
		Called when the local directory radio button is activated.
		Shows the location hbox.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
		"""
		for children in self.detailsVBox.get_children():
			self.detailsVBox.remove(children)
		self.detailsVBox.pack_start(self.localHBox, False, False)
		self.detailsVBox.show_all()

	#_____________________________________________________________________

	def __OnServerRadioToggled(self, widget):
		"""
		Called when the server radio button is activated.
		Shows the server table.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
		"""
		for children in self.detailsVBox.get_children():
			self.detailsVBox.remove(children)
		self.detailsVBox.pack_start(self.serverTable, False, False)
		self.detailsVBox.show_all()
		
	#_____________________________________________________________________
	
	def __MakeUniqueFilename(self, filename):
		"""
		From a given filename generates a name which doesn't exist
		by appending increasing numbers to it as necessary.
		
		Return:
			the unique filename
		"""
		dirName, baseName = os.path.split(filename)
		name, ext =  os.path.splitext(baseName)
		current = name
		count = 1
		while os.path.exists(os.path.join(dirName, current + ext)):
			current = "%s-%d" % (name, count)
			count += 1
		return (dirName, current, ext)
		
	#_____________________________________________________________________

#=========================================================================

class RunAScript(MixdownAction):
	"""
	Defines a mixdown action that runs a given script while mixing down.
	"""
	
	#_____________________________________________________________________
	
	def __init__(self):
		"""
		See MixdownAction.__init__
		"""
		MixdownAction.__init__(self)
		self.name = _("Run External Script")
		self.description = _("Run an external script from the destination of your choosing.")
		self.iconPath = os.path.join(Globals.IMAGE_PATH, "effect_miscellaneous.png")

	#_____________________________________________________________________

	def ConfigureAction(self):
		"""
		See MixdownAction.ConfigureAction.
		"""
		chooser = gtk.FileChooserDialog((_("Run External Script")), None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
		if os.path.exists(Globals.settings.general["projectfolder"]):
			chooser.set_current_folder(Globals.settings.general["projectfolder"])
		else:
			chooser.set_current_folder(os.path.expanduser("~"))
		chooser.set_icon(self.dialogIcon)

		chooser.set_default_response(gtk.RESPONSE_OK)
		response = chooser.run()
			
		if response == gtk.RESPONSE_OK:
			filename = chooser.get_filename()
			self.config["script"] = filename
			self.emit("action-configured")
		chooser.destroy()
	
	#_____________________________________________________________________

	def RunAction(self):
		"""
		See MixdownAction.RunAction.
		"""
		import subprocess
		import os.path
		# Interestingly when you use shell=True Popen doesn't raise an OSError if the script doesn't exist
		# because the shell runs happily (you get a shell error on stderr instead).
		# So we need to raise it by hand, or use shell=False and require an interpreter line
		if os.path.exists(self.config["script"]):
			try:
				# needs to happen in a thread!
				subprocess.Popen(self.config["script"] ,shell=True).wait()
			except OSError:
				Globals.debug(_("An error occured with the script %s") % self.config["script"])
			except:
				Globals.debug(_("Error in script %s") % self.config["script"])
		
		else:
			Globals.debug(_("The script %s does not exist.") % self.config["script"])
				
	#_____________________________________________________________________
	
#=========================================================================

