import os
import ScriptEnvironment
from Constants     import Constants, product, sortParameterNames
from Parameter     import Parameter
from javax.swing   import JOptionPane
from eu.omp.irap.cassis.gui    import PanelView
from PhysicalModel import PhysicalModel
from eu.omp.irap.cassis.radex import ModeRadexManager
from eu.omp.irap.cassis.cassisd.model.synthetic import RadexConfiguration


class ParameterNameError(Exception):
	"""Raised when the name of the parameter is invalid"""
	pass

def checkParameters(model, parameters):
	if model.lower() == "radex":
		return parameters
	else:
		return dict(x for x in parameters.items() if x[0] not in Constants.COLLISIONER_PARAMETERS)


def checkCollisioner(value):
	if isinstance(value, (list, tuple)):
		return value
	# All other types are incorrect
	else:
		msg = '%s is not a valid type for the collisioner.\n' % value.__class__.__name__
		msg += 'A valid collisioner for a component must be a list of strings.'
		msg += ' (ex: collisioner=["H2"] or collisioner=["p_H2", "o_H2", "H+", "e", "H", "He"]).\n'
		raise TypeError(msg)


def checkCollisionFileExists(collisions_files):
	rc = RadexConfiguration.getInstance()
	mrm = rc.getRadexMoleculesManager()
	bad_files = []
	for collision_file in collisions_files:
		if mrm.getPathCollisionFile(collision_file) is None:
			bad_files.append(collision_file)
	if len(bad_files) != 0:
		msg = "Some collisions files do not exist: %s" % ", ".join(bad_files) if len(bad_files) > 1\
			else "The collision file %s does not exist" % (bad_files[0])
		raise ValueError(msg)

def checkCollisionFile(value):
	# Take into account a single collisionFile as a string
	if isinstance(value, (list, tuple)):
		checkCollisionFileExists(value)
		return value
	else:
		msg = "%s is not a valid type for the collision file.\n" % value.__class__.__name__
		msg += 'Valid collison files for a component must be a list of filenames.'
		msg += ' (ex: collisionFile=["co.dat"] or collisionFile=["hcl@hfs.dat", "h37cl@hfs.dat"])\n'
		raise TypeError(msg)

def check_collisioner_with_collision_file(collisionner, collisionFile):
	print("check collisioner with collisionner file")
	from eu.omp.irap.cassis.gui.model.lteradex import LteRadexControl

	for col_file in collisionFile:
		collisionner_in_file = []
		names = LteRadexControl.getCollisionnerNamesOf(col_file)
		for name in names:
			collisionner_in_file.append(str(name).replace("-","_"))

		for col in collisionner:
			if col not in collisionner_in_file:
				msg = "Collisionner %s not in the file %s containing collisionner %r" % (col,col_file, collisionner_in_file)
				raise ValueError(msg)

def checkReducePhysicalParam(reducePhysicalParam, parameterNames):
	if reducePhysicalParam and reducePhysicalParam.keys() != parameterNames:
		msg = "reducePhysicalParam must contain exactly the same parameters in defined in Component: %r" % parameterNames
		raise ValueError(msg)
	elif reducePhysicalParam is None:
		return dict([(name, 10.0) for name in parameterNames])
	elif len(filter(lambda x: x < 1.0, reducePhysicalParam.values())) > 0:
		msg = "reducePhysicalParam must contain values > 1"
		raise ValueError(msg)
	else:
		return reducePhysicalParam


def checkParameterName(name):
	"""Check the name of a parameter in the following categories:
		- mandatory
		- optional
		- collisioner

	using defined constants. (cf Constants)

	Throws
	------
	ParameterNameError when the parameter name is not valid

	"""
	if name not in Constants.MANDATORY_PARAMETERS and\
	   name not in Constants.OPTIONAL_PARAMETERS and\
	   name not in Constants.COLLISIONER_PARAMETERS:
		msg = "%s is not a valid parameter name.\n" % name
		msg += "Choose between:\n"
		msg += "- mandatory parameters: %s\n" % ", ".join(Constants.MANDATORY_PARAMETERS)
		msg += "- optional parameters: %s\n" % ", ".join(Constants.OPTIONAL_PARAMETERS)
		msg += "- or collisioner parameters: %s\n" % ", ".join(Constants.COLLISIONER_PARAMETERS) 
		raise ParameterNameError(msg)


class Component(object):
	def __init__(self, model="", interacting=True, geometry="slab", collisionFile=[], collisioner=[], reducePhysicalParam=None, 
			  **parameters):
		"""Create a component for the input parameters.
		
		A component generates physical models from the parameters using the
		cartesian product of the values of the different parameters.

		Usage
		-----
		component     = Component(
		parameter = {'min':minValue, 'max':maxValue, 'nstep':nstep,  'log_mode':logMode},
		interacting   = interacting,
		model         = model,
		geometry      = geometry,
		)


		Basically, Component: 

		1. creates one Parameter object from the dictionary of parameter provided::

			p = Parameter(name=parameterName, 
						  min=parameter["min"], max=parameter["max"], 
						  nstep=parameter["nstep"], log_mode=parameter["log_mode"])

		or if the value of the step is passed instead of number of steps nstep::

			p = Parameter(name=parameterName, 
						  min=parameter["min"], max=parameter["max"], 
						  step=parameter["step"], log_mode=parameter["log_mode"])

		Each of this Parameter object indicates what values of the parameter
		must be used in a model. (cf documentation of Parameter)

		2. performs a cartesian product with all the values indicated by each Parameter object
			and makes a PhysicalModel for each combination of values

		For example for 2 Parameter objects::

			parameter1 has 3 values: a1 a2 a3
			parameter2 has 2 values: b1 b2

		The cartesian product gives 2 * 3 PhysicalModel objects::
			physicalModel1 = PhysicalModel( {parameter1: a1, parameter2: b1} )
							...
			physicalModel6 = PhysicalModel( {parameter1: a3, parameter2: b2} )

		"""
		super(Component, self).__init__()
		self.model         = model.lower()
		self.collisionFile = checkCollisionFile(collisionFile)
		self.collisioner   = checkCollisioner(collisioner)
		check_collisioner_with_collision_file(collisioner, collisionFile)
		self.parameters    = checkParameters(self.model, parameters)
		self.interacting   = interacting
		self.geometry      = geometry
		self.reducePhysicalParam = checkReducePhysicalParam(reducePhysicalParam, self.parameters.keys())
		for name, value in parameters.items():
			checkParameterName(name)
			# Rename the parameter name to "tex" or "tkin" if name == "temp"
			# Create the parameters
			if value.has_key("step"):
				name = Constants.renameTemp(name, self.model)
				p    = Parameter(name, value['min'], value['max'], step=value['step'], 
								 log_mode=value.get('log_mode', False))
			elif value.has_key("nstep"):
				name = Constants.renameTemp(name, self.model)
				p    = Parameter(name, value['min'], value['max'], nstep=value['nstep'], 
								 log_mode=value.get('log_mode', False))
			else:
				raise ValueError("You must provide step or nstep to create a Parameter instance.")

			# Set the model for the parameter
			p.model       = model
			p.interacting = interacting
			p.geometry    = geometry

			# Set the parameter as an attribute
			# Case 1 - Radex: All the parameters are taken into account
			if self.model.lower() == "radex":
				setattr(self, name, p)
				
			# Case 2 - Lte: Only the parameters that are not collisioner
			# parameters are taken into account
			elif self.model.lower() == "lte" and name not in Constants.COLLISIONER_PARAMETERS:
				setattr(self, name, p)

		# Correct the parameters names if necessary (temp -> tex or tkin)
		self.parameters = dict(map(lambda x: (Constants.renameTemp(x[0], self.model), x[1]), self.parameters.items()))

		# Store the names of the parameters in a predefined order
		self.names = [name for name in Constants.ORDERED_PARAMETER_NAMES\
					  if name in self.parameters.keys()]

	def __str__(self):
		return "Component(%s)" % (", ".join(self.names))

	def combineParametersValues(self):
		parameterNames   = sortParameterNames(self.parameters.keys())
		parametersList	 = (getattr(self, x) for x in parameterNames)
		parametersValues = (x.values for x in parametersList)
		return product(*parametersValues)

	def createPhysicalModel(self):
		valueCombinations = self.combineParametersValues()
		parameterNames    = sortParameterNames(self.parameters.keys())
		combinations      = (zip(parameterNames, x) for x in valueCombinations)
		combinations      = [dict(x) for x in combinations]
		physicalModel	  = [PhysicalModel(x) for x in combinations]

		# Collisions for RADEX model
		if self.collisionFile != []:
			[setattr(pSet, 'collisionFile', self.collisionFile) for pSet in physicalModel]

		if self.collisioner != []:
			[setattr(pSet, 'collisioner', self.collisioner) for pSet in physicalModel]

		# Interacting
		if self.interacting != None:
			[setattr(pSet, 'interacting', self.interacting) for pSet in physicalModel]

		# Geometry
		if self.geometry != '':
			[setattr(pSet, 'geometry', self.geometry) for pSet in physicalModel]
		
		return physicalModel

	__repr__ = __str__

# vim: set noet fdm=indent:
