#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

from xml.dom import minidom, Node
import utsusemi.SAS.ana.Reduction.AtomDBTaikan as ADB
import Manyo.Utsusemi as mu
import os

###############################################################################
# 定数値定義
###############################################################################
# 検出器効率計算用パラメータ
ABS_WAVE_LENGTH = 1.8		# 1.8[A](25.25meV) absorption cross section for 2200 m/s neutrons

N_A = 6.02214129e23			# [mol^-1] Avogadro constant
R_GAS_CONST = 8.20574e-2	# [(L*atm)/(K*mol)]molar gas constant

###############################################################################
# 検出器構成情報基底クラス
###############################################################################
class EffPrmInfoCommon:
	"""
		検出器の構成情報を保持するクラス
		属性
		name				: (str)name
		size				: (float)PSDの半径	[cm]
		outerThick			: (float)[]外殻肉厚リスト	[cm]
	"""
	##############################
	def __init__(self , name="", size=0.0, outerThick=[]):
		"""
		初期化処理(コンストラクタ)
		属性の初期化を行う。
		@param	name				(str)name
		@param	size				(float)PSDの半径	[cm]
		@param	outerThick			(float)[]外殻肉厚リスト	[cm]
		"""
		self.name 					= name
		self.size					= size					# PSDの半径		[cm]
		self.outerThick				= outerThick			# 外殻肉厚リスト		[cm]

	##############################
	def Show(self):
		print("name:%s" % (self.name))
		print("size:%g" % (self.size))
		#print "outerThick:%g" % (self.outerThick)
		print("outerThick:", end=' ')
		print(self.outerThick)


###############################################################################
# 検出器構成情報クラス
###############################################################################
class EffPrmInfoRead(EffPrmInfoCommon):
	"""
		検出器の構成情報を保持するクラス
		属性
		EffPrmInfoCommon	: (EffPrmInfoCommon)検出器構成情報基底クラス
		outerDensity		: (float)外殻密度	[g/cm^3]
		gasPressure			: (float)ガス圧	[atm]
		gasTemperture		: (float)ガス温度	[K]
		outerElement		: (dictionary)外殻構成	key = element, value = ratio
		gasElement			: (dictionary)GAS構成	key = element, value = ratio

	"""
	##############################
	def __init__(self , name="", size=0.0, outerThick=[], outerDensity=0.0, gasPressure=0.0, gasTemperture=0.0, outerElement={}, gasElement={}):
		"""
		初期化処理(コンストラクタ)
		属性の初期化を行う。
		@param	name				(str)name
		@param	size				(float)PSDの半径	[cm]
		@param	outerThick			(float)[]外殻肉厚リスト	[cm]
		@param	outerDensity		(float)外殻密度	[g/cm^3]
		@param	gasPressure			(float)ガス圧	[atm]
		@param	gasTemperture		(float)ガス温度	[K]
		@param	outerElement		(dictionary)外殻構成	key = element, value = wt-ratio
		@param	gasElement			(dictionary)GAS構成	key = element, value = ratio
		"""
		EffPrmInfoCommon.__init__(self, name, size, outerThick)

		self.outerDensity		= outerDensity		# 外殻密度 [g/cm^3]
		self.gasPressure		= gasPressure		# ガス圧 [atm]
		self.gasTemperture		= gasTemperture		# ガス温度 [K]

		self.outerElement		= outerElement		# 外殻構成
		self.gasElement			= gasElement		# GAS構成

	##############################
	def Show(self):
		"""
		クラス情報の表示
		"""
		EffPrmInfoCommon.Show(self)
		print("outerDensity:%g" % (self.outerDensity))
		print("gasPressure:%g" % (self.gasPressure))
		print("gasTemperture:%g" % (self.gasTemperture))

		print("outerElement")
		#print self.outerElement
		for k,v in list(self.outerElement.items()):
			print(k,v)
		print("gasElement")
		#print self.gasElement
		for k,v in list(self.gasElement.items()):
			print(k,v)


###############################################################################
# 計算済み検出器構成情報クラス
###############################################################################
class EffPrmInfoCalc(EffPrmInfoCommon):
	"""
		計算済みの検出器の構成情報を保持するクラス
		属性：
		EffPrmInfoCommon		: (EffPrmInfoCommon)検出器構成情報基底クラス
		outerAbs				: (float)外殻全吸収断面積	[cm^2]
		outerScatt				: (float)外殻全散乱断面積	[cm^2]
		outerNumberDensity		: (float)外殻数密度			[個/cm^3]
		gasAbs					: (float)ガス全吸収断面積	[cm^2]
		gasNumberDensity		: (float)ガス数密度		[個/cm^3]
	"""
	##############################
	def __init__(self , name="", size=0.0, outerThick=[], outerAbs=0.0, outerScatt=0.0, outerNumberDensity=0.0, gasAbs=0.0, gasNumberDensity=0.0):
		"""
		初期化処理(コンストラクタ)
		属性の初期化を行う。
		@param	name				(str)name
		@param	size				(float)PSDの半径	[cm]
		@param	outerAbs			(float)外殻全吸収断面積	[cm^2]
		@param	outerScatt			(float)外殻全散乱断面積	[cm^2]
		@param	outerNumberDensity	(float)外殻数密度			[個/cm^3]
		@param	gasAbs				(float)ガス全吸収断面積	[cm^2]
		@param	gasNumberDensity	(float)ガス数密度		[個/cm^3]
		"""
		# 取得値
		EffPrmInfoCommon.__init__(self, name, size, outerThick)

		self.outerAbs			= outerAbs				# 外殻全吸収断面積	[cm^2]
		self.outerScatt			= outerScatt			# 外殻全散乱断面積	[cm^2]
		self.outerNumberDensity	= outerNumberDensity	# 外殻数密度			[個/cm^3]

		self.gasAbs					= gasAbs				# ガス全吸収断面積	[cm^2]
		self.gasNumberDensity		= gasNumberDensity		# ガス数密度[個/cm^3]

	##############################
	def Show(self):
		"""
		クラス情報の表示
		"""
		EffPrmInfoCommon.Show(self)
		print("outerAbs:%g" % (self.outerAbs))
		print("outerScatt:%g" % (self.outerScatt))
		print("outerNumberDensity:%g" % (self.outerNumberDensity))
		print("gasAbs:%g" % (self.gasAbs))
		print("gasNumberDensity:%g" % (self.gasNumberDensity))


###############################################################################
# 検出器構成情報ファイル読み込み
###############################################################################
class ReadEfficiencyParam:
	"""
	検出器構成情報ファイル読み込み
		属性：
		detecList :(EffPrmInfoRead)[]検出器の構成情報リスト

	***TDB***
		EffPrmInfoReadの数分読み込んでリストを作成するけど、利用するのはid=1
		(idと検出器構成をきちんと紐付けられるようになってないので)
		idは昇順じゃなくても可
		ratioが合計1になってるかどうかはチェックしない
	"""
	##############################
	def __init__(self, strEffPrmInfo = 'psdinfo', strSize = 'radius', strOuter = 'cylinder', numThick = 1):
		"""
		初期化処理(コンストラクタ)
		属性の初期化を行う。
		@param	strEffPrmInfo	(str)検出器効率パラメータ情報のタグ名
										(psd:psdinfo	monitor:monitorinfo)
		@param	strSize			(str)検出器サイズのタグ名
										(psd:radius		monitor:width)
		@param	strOuter		(str)外殻のタグ名
										(psd:cylinder	monitor:shell)
		@param	numThick		(integer)thickの数
										(psd:1			monitor:2)
		"""
		self.detecList = []

		#読み込み
		self.__ReadFile(strEffPrmInfo, strSize, strOuter, numThick)

	##############################
	def __ReadFile(self, strEffPrmInfo, strSize, strOuter, numThick):
		"""
		パラメータファイルの読み込み
		@param	strEffPrmInfo	(str)検出器効率パラメータ情報のタグ名
										(psd:psdinfo	monitor:monitorinfo)
		@param	strSize			(str)検出器サイズのタグ名
										(psd:radius		monitor:width)
		@param	strOuter		(str)外殻のタグ名
										(psd:cylinder	monitor:shell)
		@param	numThick		(integer)thickの数
										(psd:1			monitor:2)
		"""

		#xmlpath = os.path.join(os.environ["MLF_USR_DIR"],"ana/xml","DetectorInfo.xml")
		#xmlpath = os.path.join(os.environ["UTSUSEMI_USR_DIR"],"ana","xml","DetectorInfo.xml")
                xmlpath = os.path.join(mu.UtsusemiEnvGetInstDir(),"ana","xml","DetectorInfo.xml")
		doc = minidom.parse(xmlpath)

		self.detecList = []
		# タグがstrEffPrmInfoの要素を取得
		for node in doc.getElementsByTagName(strEffPrmInfo):
			# 属性：name, size取得
			name = getAttributeCheckStr(node.getAttribute('name'))
			size = getAttributeCheckFloat(node.getAttribute(strSize))

			#  子要素の初期化
			outerThick = []
			outerDensity = 0.0
			outerElement ={}
			gasPressure = 0.0
			gasTemperture= 0.0
			gasElement ={}

			# 子要素のouterとgasを取得
			for child in node.childNodes:
				if child.nodeType == Node.ELEMENT_NODE:
					if child.tagName == strOuter:
						# 属性：thick, density取得
						if numThick == 1:
							outerThick.append(getAttributeCheckFloat(child.getAttribute('thick')))
						else:
							for num in range(numThick):
								outerThick.append(getAttributeCheckFloat(child.getAttribute('thick'+str(num+1))))

						outerDensity = getAttributeCheckFloat(child.getAttribute('density'))
						# elementの取得
						outerElement = GetElement(child,'outer')
					if child.tagName == 'gas':
						# 属性：pressure, temperature取得
						gasPressure = getAttributeCheckFloat(child.getAttribute('pressure'))
						gasTemperture = getAttributeCheckFloat(child.getAttribute('temperature'))
						# elementの取得
						gasElement = GetElement(child,'gas')

			detec = EffPrmInfoRead(name, size, outerThick, outerDensity, gasPressure, gasTemperture, outerElement, gasElement)
			self.detecList.append(detec)

		return

	##############################
	def GetReadEfficiencyParam(self, name="SUS304-3He"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoRead)検出器の構成情報
		"""
		for readDetecn in self.detecList:
			if readDetecn.name == name:
				return readDetecn

		return None


##############################
def GetElement(node,strType):
	element = {}
	for child in node.childNodes:
		if child.nodeType == Node.ELEMENT_NODE:
			if child.tagName == 'element':
				name = getAttributeCheckStr(child.getAttribute('name'))
				ratio = 0.0
				if strType == 'gas':
					ratio = getAttributeCheckFloat(child.getAttribute('ratio'))
				if strType == 'outer':
					ratio = getAttributeCheckFloat(child.getAttribute('wt-ratio'))

				element[name] = ratio
	return element

##############################
def getAttributeCheckStr(obj):
	try:
		ret = str(obj)
	except ValueError:
		ret = ""

	return ret

##############################
def getAttributeCheckFloat(obj):
	try:
		ret = float(obj)
	except ValueError:
		ret = 0.0

	return ret

###############################################################################
# 検出器効率計算クラス
###############################################################################
class CalcEfficiencyParam:
	"""
		検出器効率の計算を行う
		属性：
		detecList 				:(EffPrmInfoCalc)[]計算済みの検出器の構成情報リスト
		readEffPrm					: ReadEfficiencyParam	検出器構成情報ファイル読み込みインスタンス
		adbi					: AtomDBTaikan	大観用原子情報DBインスタンス
	"""

	##############################
	def __init__(self, strEffPrmInfo = 'psdinfo', strSize = 'radius', strOuter = 'cylinder', numThick = 1):
		"""
		初期化処理(コンストラクタ)
		属性の初期化を行う。
		@param	strEffPrmInfo	(str)検出器効率パラメータ情報のタグ名
										(psd:psdinfo	monitor:monitorinfo)
		@param	strSize			(str)検出器サイズのタグ名
										(psd:radius		monitor:width)
		@param	strOuter		(str)外殻のタグ名
										(psd:cylinder	monitor:shell)
		@param	numThick		(integer)thickの数
										(psd:1			monitor:2)
		"""
		self.detecList = []

		# ReadEfficiencyParam読み込み
		readEffPrm = ReadEfficiencyParam(strEffPrmInfo,strSize,strOuter,numThick)
		self.readEffPrm = readEffPrm

		#AtomDBTaikanの初期化
		adbi = ADB.AtomDBTaikanClass()
		adbi.ReadDBFile()
		self.adbi = adbi

		#計算
		self.__CalcParam()


	##############################
	def GetCalcEfficiencyParam(self, name="SUS304-3He"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoCalc)計算済みの検出器の構成情報
		"""
		for calcDetecn in self.detecList:
			if calcDetecn.name == name:
				return calcDetecn

		return None

	##############################
	def GetReadEfficiencyParam(self, name="SUS304-3He"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoRead)検出器の構成情報
		"""
		return self.readEffPrm.GetReadEfficiencyParam(name)

	##############################
	def __CalcParam(self):
		"""
		パラメータの計算
		"""
		for readDetecn in self.readEffPrm.detecList:
			name				= readDetecn.name				# name
			size				= readDetecn.size				# PSDの半径		[cm]
			outerThick			= readDetecn.outerThick			# 外殻肉厚リスト		[cm]

			# 外殻成分
			outerElement			= list(readDetecn.outerElement.keys())		# 外殻成分元素
			outerElementWtRatio		= list(readDetecn.outerElement.values())		# 外殻成分構成比(重量比)
			outerDensity			= readDetecn.outerDensity				# 外殻密度	[g/cm^3]
			# ガス成分
			gasElement			= list(readDetecn.gasElement.keys())			# ガス成分元素リスト
			gasElementRatio		= list(readDetecn.gasElement.values())		# ガス成分構成比リスト
			if readDetecn.gasTemperture == 0:
				gasNumberDensity = 0.0
			else:
				gasNumberDensity	= (readDetecn.gasPressure * N_A) / (R_GAS_CONST * readDetecn.gasTemperture * 1000)
																# ガス数密度[個/cm^3]

			# AtomDBTaikanから値を取得する[barn]

			outerElementAbs			= []					# 外殻成分吸収断面積リスト[barn]
			outerElementScatt		= []					# 外殻成分散乱断面積リスト[barn]
			outerElementMolWeight	= []					# 外殻成分分子量リスト	[g/mol]

			gasElementAbs		= []							# ガス成分吸収断面積リスト[barn]

			for elem in outerElement:
				# 外殻成分吸収断面積リスト[barn]
				x=[]
				ret = self.adbi.GetValue(elem, ADB.DEF_ATOM_DB_ABSXS_TAG,x)
				fx=float(x[0])
				outerElementAbs.append(fx)
				# 外殻成分散乱断面積リスト[barn]
				x=[]
				self.adbi.GetValue(elem, ADB.DEF_ATOM_DB_SCATTXS_TAG,x)
				fx=float(x[0])
				outerElementScatt.append(fx)
				# 外殻成分分子量リスト	[g/mol]
				x=[]
				self.adbi.GetValue(elem, ADB.DEF_ATOM_DB_MOLWEIGHT_TAG,x)
				fx=float(x[0])
				outerElementMolWeight.append(fx)

			#print outerElementAbs
			#print outerElementScatt
			#print outerElementMolWeight

			for elem in gasElement:
				# ガス成分吸収断面積リスト[barn]
				x=[]
				self.adbi.GetValue(elem, ADB.DEF_ATOM_DB_ABSXS_TAG,x)
				fx=float(x[0])
				gasElementAbs.append(fx)

			#print gasElementAbs

			# 各値を計算する
			# 外殻成分
			outerMolWeight			= CalcElementMolWeight(outerElementMolWeight, outerElementWtRatio)
																	# 外殻全分子量	[g/mol]

			outerElementRatio = [outerMolWeight*outerElementWtRatio[x]/outerElementMolWeight[x] for x in range(len(outerElementMolWeight))]
																	# 重量比→モル比
			#print outerElementWtRatio
			#print outerElementRatio

			outerAbs			= CalcElementAbs(outerElementAbs, outerElementRatio, 'outer')
																	# 外殻全吸収断面積[cm^2]
			outerScatt		= CalcElementScatt(outerElementScatt, outerElementRatio)
																	# 外殻全散乱断面積[cm^2]

			if outerMolWeight == 0:
				outerNumberDensity	= 0.0
			else:
				outerNumberDensity	= (outerDensity * N_A)/outerMolWeight
																	# 外殻数密度		[個/cm^3]
			# ガス成分
			gasAbs			= CalcElementAbs(gasElementAbs, gasElementRatio, 'gas')
																# ガス全吸収断面積[cm^2]
			detec = EffPrmInfoCalc(name, size, outerThick, outerAbs, outerScatt, outerNumberDensity, gasAbs, gasNumberDensity)
			self.detecList.append(detec)
		return

###############################################################################
# PSD検出器効率計算クラス
###############################################################################
class CalcPsdEfficiencyParam(CalcEfficiencyParam):
	"""
		PSD検出器効率の取得と計算を行う
	"""
	##############################
	def __init__(self):
		CalcEfficiencyParam.__init__(self,'psdinfo', 'radius', 'cylinder', 1)

	##############################
	def GetCalcPsdEffParam(self, name="SUS304-3He"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoCalc)計算済みの検出器の構成情報
		"""
		return CalcEfficiencyParam.GetCalcEfficiencyParam(self,name)

	##############################
	def GetReadPsdEffParam(self, name="SUS304-3He"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoRead)検出器の構成情報
		"""
		return CalcEfficiencyParam.GetReadEfficiencyParam(self,name)

###############################################################################
# MONITOR検出器効率計算クラス
###############################################################################
class CalcMonitorEfficiencyParam(CalcEfficiencyParam):
	"""
		MONITOR検出器効率の取得と計算を行う
	"""
	##############################
	def __init__(self):
		CalcEfficiencyParam.__init__(self,'monitorinfo', 'width', 'shell', 2)

	##############################
	def GetCalcMonitorEffParam(self, name="MONITOR-A5083-1"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoCalc)計算済みの検出器の構成情報
		"""
		return CalcEfficiencyParam.GetCalcEfficiencyParam(self,name)

	##############################
	def GetReadMonitorEffParam(self, name="MONITOR-A5083-1"):
		"""
		@param	name	(str)検出器の名前
		@retval			(EffPrmInfoRead)検出器の構成情報
		"""
		return CalcEfficiencyParam.GetReadEfficiencyParam(self,name)


##############################
def CalcSumProduct(List1, List2):
	"""
	リストの対応する要素の積を合計する
	@param List1	(float)[]	リスト1
	@param List2	(float)[]	リスト2
	@retval			(float)	全吸収断面積
	"""
	retval = 0.0

	for x in range(len(List1)):
		retval = retval + List1[x] * List2[x]

	return retval

##############################
def CalcElementMolWeight(molWeightList, wtRatioList):
	"""
	全分子量の計算
	@param molWeightList	(float)[]	分子量リスト
	@param wtRatioList		(float)[]	構成比リスト(重量比)
	@retval					(float)	全吸収断面積
	"""
	invmolWeightList = [1/x for x in molWeightList]

	retval = CalcSumProduct(invmolWeightList, wtRatioList)
	retval = 1/retval

	return retval

##############################
def CalcElementAbs(absList, ratioList, strType):
	"""
	全吸収断面積の計算
	@param absList		(float)[]	吸収断面積リスト[barn]
	@param ratioList	(float)[]	構成比リスト
	@param strType		(str)[]	外殻(outer)orガス(gas)の文字列
	@retval				(float)	全吸収断面積	[cm^2]
	"""
	retval = 0.0
	if 'outer' == strType:
		print("DEBUG outer")
		retval = CalcSumProduct(absList, ratioList)/sum(ratioList)
	elif 'gas'  == strType:
		print("DEBUG gas")
		retval = CalcSumProduct(absList, ratioList)

	retval = retval / ABS_WAVE_LENGTH
	# [barn] → [cm^2]
	retval = retval * (1.0e-24)

	return retval

##############################
def CalcElementScatt(scattList, ratioList):
	"""
	全散乱断面積の計算
	@param scattList	(float)[]	散乱断面積リスト[barn]
	@param ratioList	(float)[]	構成比リスト
	@retval				(float)	全散乱断面積	[cm^2]
	"""
	retval = CalcSumProduct(scattList, ratioList)/sum(ratioList)
	# [barn] → [cm^2]
	retval = retval * (1.0e-24)

	return retval

