"""@package ForwardFinancialFramework.Solvers.MonteCarlo

This is the base class for all Monte Carlo solvers.
Created on 11 July 2012
"""
import os,time,subprocess,sys,time,math,copy
import numpy.linalg

class MonteCarlo:
	"""Base Monte Carlo solver class

	This class is the base class for all of the Monte Carlo solvers
	"""
	name = "monte_carlo_solver"
	##Monte Carlo simulation paths
	paths = None
	##Number of threads of execution to use
	threads = None

	##Fusion optimisation
	reduce_underlyings = None

	##Platform of exectuion
	platform = None

	##Derivative products to value
	derivative = []
	##Derivative product attributes
	derivative_attributes = []
	##Derivative product variables
	derivative_variables = []

	##Underlyings of derivative products
	underlying = []
	##Underlying attributes
	underlying_attributes = []
	##Underlying variables
	underlying_variables = []
	underlying_dependencies = []
    
	def __init__(self,derivative,paths,platform,reduce_underlyings=True):
		"""Constructor

		Parameters
			derivatives - (list of ForwardFinancialFramework.Derivatives) list of derivative products that need to be priced
			paths - (int) number of Monte Carlo simulations to use
			platform - (ForwardFinancialFramework.Platform) platform to perform solving upon
			reduce_underlyings - (bool) use the fusion optimisation?
		"""
		name = "monte_carlo_solver"
		self.platform = platform
		self.paths = paths
		   
		self.solver_metadata = {"paths":self.paths}
		self.derivative = derivative
		self.reduce_underlyings = reduce_underlyings
		self.setup_underlyings(self.reduce_underlyings)
		if(derivative): self.generate_name()
   

	def generate(self,override=True): 
		"""Method for generating solver code.
		
		Parameters
			override - (bool) overwrite pre-existing code
		"""
		pass
	
	def compile(self): 
		"""Method for compiling generated solver code
		"""
		pass

	def execute(self,cleanup=False): 
		"""Method for running solver on specified platform
		"""
		pass
    
    	def generate_identifier(self): 
		"""Method for generating unique identified string
		"""
		return ["//%s Generated by Monte Carlo Solver"%self.output_file_name]
    
	def cleanup(self): 
		"""Method for cleaning up solver
		"""
		pass
    
	def setup_underlyings(self,reduce_underlyings):
		"""utility method for generating list of underlyings from the solver's derivatives

		Parameters
			reduce_underlysin - (bool) apply fusion optimisations?

		"""
		self.underlying = []

		underlying_list = []
		for i,d in enumerate(self.derivative):
		    for j,u in enumerate(d.underlying):
			if(u.__dict__ not in underlying_list and reduce_underlyings):  
	  			self.underlying.append(u) #Extracting unique underlyings from derivatives
	  			underlying_list.append(u.__dict__)
	
			elif(reduce_underlyings):  #Making sure that equal underlyings are merged - TODO make this check stronger
	 			new_u_index = underlying_list.index(u.__dict__)
	  			d.underlying[j] = self.underlying[new_u_index]
	    
			else: self.underlying.append(u)

		self.underlying_dependencies = [] #Creating a dependency list for each underlying, detailing the derivative that depends on it
		for u in self.underlying:
    			self.underlying_dependencies.append([])
    			for d in self.derivative:
				if(u in d.underlying): self.underlying_dependencies[-1].append(self.derivative.index(d))

		#Extracting variables and attributes required during code generation, compilation and execution process
		self.underlying_attributes = []
		self.underlying_variables = []
		for u in self.underlying:
			self.underlying_attributes.append(u.__init__.__code__.co_varnames[1:])
			self.underlying_variables.append(self.attribute_stripper(self.underlying_attributes[-1],u.path_init.__code__.co_names))

		self.derivative_attributes = []
		self.derivative_variables = []
		for d in self.derivative:
			self.derivative_attributes.append(list(d.__init__.__code__.co_varnames[1:]))
			self.derivative_attributes[-1].remove("underlying")
			self.derivative_attributes[-1] = tuple(self.derivative_attributes[-1])
			self.derivative_variables.append(self.attribute_stripper(self.derivative_attributes[-1],d.path_init.__code__.co_names))
    
	def generate_name(self):
		"""Method for generating unique name for solver, based upon underlying and derivative produces
		"""
		self.output_file_name = "mc_solver"  
	     	self.output_file_name += "_%s"%(self.platform.name)
	      	if(self.platform.hostname): self.output_file_name += "_%s"%self.platform.hostname.replace('-','_')

		temp = [] #Generating Filename - based on underlyings,derivatives and platforms used
		for u in self.underlying:
			if u.name not in temp:
				count = 0
				for uu in self.underlying:
					if(uu.name==u.name): count = count + 1

				self.output_file_name += "_%s_%d" % (u.name[0:2],count) #First letter is used to keep names succinct
			temp.append(u.name)

		for d in self.derivative:
			if d.name not in temp:
				count = 0
				for dd in self.derivative:
					if(dd.name==d.name): count = count + 1

				self.output_file_name += "_%s_%d" % (d.name[0:2],count)
			temp.append(d.name)
	   
    
    
   
	def attribute_stripper(self,attributes,variables):
      		"""Helper Method used to remove all items in the first list from the second list, if present
		
		Parameters
			attributes - (list) elements to remove
			variables - (list) list to remove from
		
		returns variables - attributes
		"""
		variables = list(variables)
		for a in attributes:
			if(variables.count(a)): variables.remove(a)
      
		return tuple(variables)
      
