#
#	THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE
#	THE 'COPYING' FILE FOR DETAILS
#
#	Jokosher Mixdown Profile objects.
#
#-------------------------------------------------------------------------------

import Globals
import os
import pygtk
pygtk.require("2.0")
import gtk

import gettext
_ = gettext.gettext

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

class MixdownProfile:
	"""
	Represents a mixdown profile, used for doing complex mixdown operations.
	"""
	
	name = None # becomes a string when saved
	filename = None # becomes a filename when saved. File found in ~/.jokosher/mixdownprofiles/
	actions = [] # list of MixdownAction objects that make up this profile

	#_____________________________________________________________________

	def load(self, filename):
		"""
		Loads the profile details, creating all the actions and putting them
		in self.actions.
		
		Parameters:
			filename -- filename of the profile to load.
		"""
		self.filename = filename
	
	#_____________________________________________________________________

	def save(self):
		"""
		Saves this profile to self.filename. This method is called whenever
		anything changes (instant apply).
		"""
		if not self.filename:
			return
		
	#_____________________________________________________________________

	def run(self):
		"""
		Executes this mixdown profile.
		"""
		datadict = {}
		for action in self.actions:
			self.action.run(datadict)
			
	#_____________________________________________________________________

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

class MixdownAction:
	"""
	Represents a mixdown action, used as a building block for mixdown profiles.
	This class is available to extensions, which will be able to create new 
	actions to be added to profiles by subclassing it.
	"""
	
	#_____________________________________________________________________
	
	def __init__(self):
		"""
		Creates a new instance of MixdownAction.
		"""
		self.config = {} 	# a dictionary of configuration options that this action needs
							# public because MixdownProfile.save() needs to save it
							# and so we can autogenerate a config GUI for it if we have to 
							# (although this is not recommended)
		self.button = None 	# our config button. This gets set by the Dialog.
	
	#_____________________________________________________________________
	
	def configure(self):
		"""
		Displays an autogenerated configuration window.
		Subclasses should override this to do sensible configuration rather
		than nasty autogenerated configuration.
		"""
		window = gtk.Window()
		vb = gtk.VBox()
		
		window.set_property("window-position", gtk.WIN_POS_CENTER)
		window.set_border_width(12)
		vb.set_spacing(6)
		
		window.add(vb)
		for key, value in self.config.items():
			hb = gtk.HBox()
			hb.set_spacing(6)
			hb.add(gtk.Label(key))
			entry = gtk.Entry()
			entry.set_text(value)
			entry.connect("focus-out-event", self.__configEntryChanged, key)
			hb.add(entry)
			vb.add(hb)
		window.connect("delete_event", self.__finishConfiguration)
		window.show_all()
	
	#_____________________________________________________________________
	
	def __finishConfiguration(self, widget, event):
		"""
		Internal function to update the button on the Mixdown window.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
			event -- reserved for GTK callbacks, don't use it explicitly.
		"""
		widget.destroy()
		self.update_button()

	#_____________________________________________________________________

	def __configEntryChanged(self, widget, event, datakey):
		"""
		Internal function to update the config dictionary.
		
		Parameters:
			widget -- reserved for GTK callbacks, don't use it explicitly.
			event -- reserved for GTK callbacks, don't use it explicitly.
			datakey -- key to store the value under.
		"""
		self.config[datakey] = widget.get_text()

	#_____________________________________________________________________

	def update_button(self):
		"""
		Update the button on the mixdown window.
		"""
		self.button.set_label(self.display_name())
		
	#_____________________________________________________________________

	@staticmethod
	def create_name():
		"""
		A name for this action, to go in the choose-an-action menu.
		
		Returns:
			the string with the name for this action.
		"""
		return _("Export file")
	
	#_____________________________________________________________________

	def run(self, data):
		"""
		Do whatever this action actually does.
		
		Parameters:
			data -- a dictionary populated by earlier Actions in this Profile,
					as described below. It can manipulate the data dictionary
					however it needs to.
		"""
		pass
	
	#_____________________________________________________________________

	def display_name(self):
		"""
		Obtain a name for display in the button in the Profile window.
		This is a function because an "Upload to..." action should call itself.
		
		For example "Upload to mysshserver".
		
		Returns:
			a string with the name to display in the action button.
		"""
		return _("An unspecified MixdownAction")
	
	#_____________________________________________________________________

	def is_configured(self):
		"""
		Whether this Action has been configured correctly.
		
		Returns:
			True if this Action thinks it has been configured correctly.
			False otherwise.
		"""
		return False
	
	#_____________________________________________________________________
		
	def serialise(self):
		"""
		Obtain an XML description of this Action, along with its parameters.
		
		Returns:
			the XML description of this ACtion.
		"""
		paramxml = "<%s>%s</%s>"
		outerxml = "<%s>%s</%s>"
		params = [paramxml % (x, self.config[x], x) for x in self.config.keys()]
		return outerxml % (self.__class__.__name__, '\n'.join(params), 
											 self.__class__.__name__)
		
	#_____________________________________________________________________

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

class ExportAsFileType(MixdownAction):
	"""
	Defines a mixdown action that exports the 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)
		# Always start with ogg as the filetype
		self.filetypedict = [x for x in Globals.EXPORT_FORMATS if x['extension'] == 'ogg'][0]
		# offer a default filename in the project folder that 
		# doesn't previously exist
		self.config["filename"] = self.MakeUniqueFilename(os.path.join(os.path.dirname(project.projectfile),"export.ogg"))
		self.config["filetype"] = self.filetypedict['extension']
		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 display_name(self):
		"""
		See MixdownAction.display_name
		"""
		return _("Export to %(file)s as %(desc)s") % {
			"file" : os.path.split(self.config["filename"])[1],
			"desc" : self.filetypedict["description"]}

	#_____________________________________________________________________

	@staticmethod
	def create_name():
		"""
		See MixdownAction.create_name
		"""
		return _("Export file")
	
	#_____________________________________________________________________

	def run(self,data):
		"""
		See MixdownAction.run
		"""
		data["filename"] = self.config["filename"]
		data["filetype"] = self.filetypedict["extension"]
		self.project.Export(self.config["filename"], self.filetypedict["pipeline"])
		
	#_____________________________________________________________________
		
	def configure(self):
		"""
		See MixdownAction.configure
		"""
		buttons = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)
		chooser = gtk.FileChooserDialog(_("Export as"), None, gtk.FILE_CHOOSER_ACTION_SAVE, buttons)
		chooser.set_do_overwrite_confirmation(True)
		chooser.set_default_response(gtk.RESPONSE_OK)
		chooser.set_current_folder(os.path.dirname(self.config["filename"]))
		chooser.set_current_name(os.path.basename(self.config["filename"]))

		saveLabel = gtk.Label(_("Save as file type:"))		
		typeCombo = gtk.combo_box_new_text()
		
		counter = 0
		active_item = 0
		for format in Globals.EXPORT_FORMATS:
			typeCombo.append_text("%s (.%s)" % (format["description"], format["extension"]))
			if format == self.filetypedict:
				active_item = counter
			counter += 1
		typeCombo.set_active(active_item)
		
		extraHBox = gtk.HBox()
		extraHBox.pack_start(saveLabel, False)
		extraHBox.pack_end(typeCombo, False)
		extraHBox.show_all()
		chooser.set_extra_widget(extraHBox)
		
		response = chooser.run()
		if response == gtk.RESPONSE_OK:
			name = chooser.get_filename()
			#if the user has chosen a name that is belongs to 
			#one of the project events then we must override it
			if name in self.project.GetInputFilenames():
				name = self.MakeUniqueFilename(name)
			self.config["filename"] = name
			Globals.settings.general["projectfolder"] = os.path.dirname(self.config["filename"])
			Globals.settings.write()
			#If they haven't already appended the extension for the 
			#chosen file type, add it to the end of the file.
			self.filetypedict = Globals.EXPORT_FORMATS[typeCombo.get_active()]
			if not self.config["filename"].lower().endswith(self.filetypedict["extension"]):
				self.config["filename"] += "." + self.filetypedict["extension"]
			chooser.destroy()
			self.update_button()
		else:
			chooser.destroy()
			
	#_____________________________________________________________________
	
	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 os.path.join(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.config["script"] = os.path.expanduser("~/jokscript.sh")
		
	#_____________________________________________________________________
	
	def display_name(self):
		"""
		See MixdownAction.display_name
		"""
		return _("Run script %s") % (os.path.split(self.config["script"])[1])
	
	#_____________________________________________________________________
	
	@staticmethod
	def create_name():
		"""
		See MixdownAction.create_name
		"""
		return _("Run external script")
	
	#_____________________________________________________________________

	def run(self,data):
		"""
		See MixdownAction.run
		"""
		# Set up the environment for the process by putting all of the
		# vars from the mixdownprofile in it as JOKOSHER_* vars
		subprocess_environment = {}
		for key, value in data.items():
			subprocess_environment["JOKOSHER_%s" % key.upper()] = value
			
		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"], env=subprocess_environment,shell=True).wait()
				# have to get the subprocess's environment after it's finished. How?
			except OSError,e:
				raise _("An error occured with the script %s") % self.config["script"], e
			except:
				raise _("Error in script %s") % self.config["script"]
		
		else:
			raise OSError, _("The script %s does not exist.") % self.config["script"]

		for key, value in subprocess_environment.items(): # this won't work until we have the s.p.'s env
			if key.startswith("JOKOSHER_"):
				varname = key[9:].lower() # strip off JOKOSHER_
				data[varname] = value
				
	#_____________________________________________________________________

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