import ScriptEnvironment
from PhysicalModel import PhysicalModel
from Constants import product
from Component import Component

def is_uniq(collection):
	return len(set(collection)) == 1


def checkInteracting(components, i):
	if components[i].interacting is True and components[i+1].interacting is False:
		msg = "[interacting error] "
		msg += "All the components after a component with interacting = True must have interacting = True."
		msg += "\nPlease check component %d in computeAndWriteChi2Min.\n" % (i + 2)
		raise ValueError(msg)
	return components[i]



def check_molecules_in_interacting_compo_have_same_size(components):
	interactingCompo=[]
	for comp in components:
		if comp.interacting:
			interactingCompo.append(comp)
	for comp in interactingCompo:
		print(comp)
		sizeCompo = comp['molecules'][0]["size"]
		for mol in comp['molecules']:
			if mol["size"] != sizeCompo:
				msg = "[interacting error] "
				msg += "All molecules of any interacting component must have the same size."
				raise ValueError(msg)


def checkIsoUnique(firstComponent, component):
	if hasattr(firstComponent, 'iso'):
		component.iso = firstComponent.iso
	elif not hasattr(firstComponent, 'iso'):
		msg = "[iso error] If isoUnique = True, all components should have an iso parameter."
		raise ValueError(msg)
	return component


class Components(object):
	"""Components creates a list of components from the input components the user wants to use

	Usage:
		components = Components(comps, isoUnique=isoUnique)

	Inputs:
		comps     - a list of Component object
		isoUnique - Take the first iso parameter for all the components (True)
				    or take the union of all the values of the iso parameters (False)
				    [OPTIONAL]
				    [DEFAULT: False]

	Example:
		components = Components([comp_1, comp_2], isoUnique=True)

	"""
	def __init__(self, components, isoUnique=False, method="MCMC"):
		super(Components, self).__init__()
		self.components = components
		self.isoUnique  = isoUnique
		self.method     = method
		self.isRadex    = False

		c = 0
		self.physicalModels = []		
		firstCompoSpecies = self.components[0]

		
		#TODO check_molecules_in_interacting_compo_have_same_size(components)
		for component in components:
			if component.model == "radex":
				self.isRadex = True
			# Check that all the components AFTER an interacting component are interacting
			if c < len(components)-1:
				component = checkInteracting(components, c)

			# Check that the first component has an iso parameter
			if self.isoUnique:
				component = checkIsoUnique(firstCompoSpecies, component)

			if self.method == "RG":
				self.physicalModels.append(component.createPhysicalModel())

			c += 1

		if self.method == "RG":
			# The combinations of the parameters for the Regular grid method
			self.models = self.getValidModels()
		else:
			self.models = []

		self.dof = self.computeDegreeOfFreedom()

	def computeDegreeOfFreedom(self):
		"""The degree of freedom is determined by the number of parameters whose range is extended
		(the minimum value must be different from the maximum value)

		"""
		dof = 0

		# All the parameters except iso
		for component in self.components:
			minima = [getattr(component, name).min for name in component.names if not name.startswith("iso")]
			maxima = [getattr(component, name).max for name in component.names if not name.startswith("iso")]
			extendedRanges = filter(lambda i: minima[i] != maxima[i], range(len(minima)))
			dof += len(extendedRanges)

		# All the iso parameters
		if self.isoUnique:
			if hasattr(self.components[0], "iso") and\
			   self.components[0].iso.min != self.components[0].iso.max:
				dof += 1
		else:
			for component in self.components:
				minima = [getattr(component, name).min for name in component.names if name.startswith("iso")]
				maxima = [getattr(component, name).max for name in component.names if name.startswith("iso")]
				extendedRanges = filter(lambda i: minima[i] != maxima[i], range(len(minima)))
				dof += len(extendedRanges)

		return dof

	def extractParametersValues(self, cidx, row, paramNames):
		"""Extract the values for the given parameters of the selected component

		Usage:
			paramValues = extractParametersValues(cidx, row, 'nmol', 'tex')

		Inputs:
			* cidx       : the index of the component
			* row        : the row amongst the different models of components
			* paramNames : a list of the name of the parameters

		Returns:
			paramValues  : a list containing the values of the parameters

		"""
		model = self.models[row][cidx]
		component = self.components[cidx]

		paramValues = []
		for paramName in paramNames:
			paramValue = getattr(model, paramName)
			# If log mode is True take the log10
			if getattr(component, paramName).log_mode:
				paramValue = paramValue.log10()
			paramValues.append(paramValue)
		return paramValues

	# TODO If the isos are different take into account all the cases
	def getValidModels(self):
		"""Return the models where the isotopic ratios are identical"""
		models = list(product(*self.physicalModels))

		# The distinction between the isos of the different components is done if
		# the following conditions are satisfied:

		# There must be more than one component and iso must be a parameter
		if len(self.components) > 1 and hasattr(self.components[0], 'iso'):

			# If isoUnique, keep only the iso parameter of the first component
			if self.isoUnique:
				validModels = filter(lambda combi: is_uniq(map(lambda y: y.iso, combi)), models)
				return validModels
			else:
				# If not isoUnique, keep all the isos (union)
				return models
		else:
			return models


# vim: set noet fdm=indent:
