# -*- coding: utf-8 -*-
#
# $Date: 2013-03-06 18:51:19 +0900 (水, 06 3 2013) $
# $Rev: 55 $
#
import vector
import VTKPanel
import define

import math
import decimal

import numpy as np

class QPointBase(object):
    def __init__(self, param):
        self.positoin = None
        self.index = None
        self.param = param
        self.__isVisible = False

        self.ResetConfig()
        return

    def SetPosition(self, qx, qy, qz):
        self.position = (qx, qy, qz)
        return

    def SetIndex(self, i, j, k):
        self.index = (i, j, k)
        return

    def GetIndexString(self):
        return "% d,% d,% d" % (self.index[0], self.index[1], self.index[2])

    def GetEtmax(self):
        etmax = None
        for name in self.param.GetAllRegionNames():
            et = self.status[name][define.QPVAL_ET]
            if etmax == None:
                if et != None:
                    etmax = et
                    pass
                pass
            else:
                if et != None:
                    etmax = max(etmax, et)
                    pass
                pass
            pass
        return etmax

    def ResetConfig(self):
        self.status = dict()

        for name in self.param.GetAllRegionNames():
            self.status[name] = {define.QPVAL_ET: None,
                                 define.QPVAL_INSIDE: False,
                                 define.QPVAL_PXNO: None,
                                 define.QPVAL_PSDID: None}
            pass

        self.numInsidePoints = 0
        return

    def IsInside(self):
        return (self.numInsidePoints > 0)

    def GetInfoText(self):
        info = "Q point Index: " + str(self.index) + "\n"
        info += "Position [1/A]:\n"
        info += "  (%g, %g, %g)\n" % (self.position[0], self.position[1], self.position[2])
        info += "\n"
        info += "Detectable regions:\n"

        flag = False

        for name, vals in self.status.items():
            if vals[define.QPVAL_INSIDE]:
                flag = True
                info += "  " + name + "\n"
                info += "    Energy transfer [meV]: %g" % vals[define.QPVAL_ET] + "\n"
                info += "    PSD id: " + str(vals[define.QPVAL_PSDID]) + "\n"
                info += "    Pixel no.: " + str(vals[define.QPVAL_PXNO]) + "\n"
                pass

        if not flag:
            info += "  None"
            pass
        return info

    def CalcConfig(self, regions):
        """
        This class must be overrided by inheritance class
        """
        raise NotImplementedError

    def SetVisible(self, isVisible):
        self.__isVisible = isVisible
        return

    def IsVisible(self):
        return self.__isVisible

class DNAQPoint(QPointBase):
    def __init__(self, param):
        QPointBase.__init__(self, param)
        return

    def CalcConfig(self, regions):
        self.ResetConfig()

        q = self.position

        for region in regions:
            props = self.param.detectorProps[region]

            ratio = props.y0 / props.RA
            ratio2 = ratio * ratio

            check = (math.pi / props.d_miller) ** 2 - ratio2 * q[1] ** 2

            if check < 0.0:
                continue

            kf = math.sqrt(check / (1.0 - ratio2))

            if abs(q[1] / kf) > 1.0:
                continue

            theta_m = math.asin(q[1] / kf)

            if theta_m < props.theta_m_min or theta_m > props.theta_m_max:
               continue

            check = q[0] / (kf * math.cos(theta_m))
            if abs(check) > 1.0:
                continue

            phi0 = math.asin(check)
            if phi0 >= 0:
                phi1 = math.pi - phi0
                pass
            else:
                phi1 = -math.pi - phi0
                pass

            for phi in [phi0, phi1]:
                if phi < props.phi_s or phi > props.phi_e:
                    continue

                ki = kf * math.cos(theta_m) * math.cos(phi) - q[2]

                if ki < self.param.kimin or ki > self.param.kimax:
                    continue

                PSD = int((props.phi_e - phi) // props.rPSD + props.PSD0)

                self.status[region][define.QPVAL_ET] = define.MEV_ENG_FAC * (ki ** 2 - kf ** 2)
                self.status[region][define.QPVAL_PSDID] = PSD
                self.status[region][define.QPVAL_INSIDE] = True
                self.numInsidePoints += 1

                for Px in xrange(0, props.nPx):
                    if props.theta_m[Px + 1] <= theta_m < props.theta_m[Px]:
                        self.status[region][define.QPVAL_PXNO] = Px
                        break;
                    pass
                pass
            pass
        return

class DGTypeQPoint(QPointBase):
    def __init__(self, param):
        QPointBase.__init__(self, param)
        return

    def CalcConfig(self, regions):
        self.ResetConfig()

        ki = self.param.ki
        q = self.position

        for region in regions:
            props = self.param.detectorProps[region]

            kf = math.sqrt(q[0] ** 2 + q[1] ** 2 + (q[2] + ki) ** 2)
            if kf > self.param.kfmax or kf < self.param.kfmin:
                continue

            phi = math.atan2(q[0], q[2] + ki)

            if phi < props.phi_s or phi > props.phi_e:
                continue

            # Calc. local PSD index
            locPSD = int(math.floor(props.mR - props.L2 / props.dPSD * math.tan(phi - props.rR0)))
            if locPSD < 0 or locPSD > props.nPSD - 1:
                continue

            theta = math.atan2(q[1], math.sqrt(q[0] ** 2 + (q[2] + ki) ** 2))
            L2x = math.sqrt(props.L2 ** 2 + ((props.mR - locPSD) * props.dPSD) ** 2)

            Px = int((props.yd0 - L2x * math.tan(theta)) // props.hPx)
            if Px < 0 or Px > props.nPx - 1:
                continue

            self.status[region][define.QPVAL_ET] = define.MEV_ENG_FAC * (ki ** 2 - kf ** 2)
            self.status[region][define.QPVAL_PSDID] = locPSD + props.PSD0
            self.status[region][define.QPVAL_INSIDE] = True
            self.status[region][define.QPVAL_PXNO] = Px
            self.numInsidePoints += 1
            pass
        return

DEFALUT_AMT_FILEPATH = u'./Default.amt'

NUM_WAVE_NUMBERS = 30

def GetRoundingRange(range, digitNum):
    fac = math.pow(10.0, digitNum)
    return [math.floor(range[0] / fac) * fac,
            math.ceil(range[1] / fac) * fac]

def ApplyMat3(M, x):
    return [M[0][0] * x[0] + M[0][1] * x[1] + M[0][2] * x[2],
            M[1][0] * x[0] + M[1][1] * x[1] + M[1][2] * x[2],
            M[2][0] * x[0] + M[2][1] * x[1] + M[2][2] * x[2]]

class QSpaceManager(object):
    def __init__(self, mode, filename = DEFALUT_AMT_FILEPATH):
        self.__mode = mode

        self.__qPointArray = []
        self.__qPointHash = dict()
        self.status = Status()
        self.status.Read(filename)
        self.ltc = Lattice(self.status)

        self.assistantConf = AssistantConfs(mode)

        elemRegions = self.assistantConf.GetRegions()

        if self.__mode == 'DNA':
            self.param = DNADetectorParams(elemRegions)
            pass
        elif self.__mode == 'SIK':
            self.param = DGTypeDetectorParam(elemRegions)
            pass

        self.__detectorRegions = dict()
        self.__thinDetectorRegions = dict()
        self.__intensityRegions = dict()
        for region in self.param.GetAllRegionNames():
            self.__detectorRegions[region] = dict()
            self.__thinDetectorRegions[region] = None
            self.__intensityRegions[region] = None
            pass

        self.UpdateEnergyRange()
        return

    def UpdateEnergyRange(self):
        elemRegions = self.assistantConf.GetRegions()

        if self.__mode == 'DNA':
            eimin = self.status.GetFloat(TAG_ENG_EIMIN)
            eimax = self.status.GetFloat(TAG_ENG_EIMAX)
            self.param.SetEnergyRange(eimin, eimax)
            pass
        elif self.__mode == 'SIK':
            ei = self.status.GetFloat(TAG_ENG_EI)
            etmin = self.status.GetFloat(TAG_ENG_ETMIN)
            etmax = self.status.GetFloat(TAG_ENG_ETMAX)
            if ei == etmax:
                # Following numeric factor avoids singularity point of detector region,
                #  which causes uncertain normal direction of isosurfaces.
                etmax = 0.99999 * ei
                pass
            self.param.SetEnergyRange(ei, etmin, etmax)
            pass
        return

    def GetBoundIndices(self):
        qxmin = self.__bounds[0]
        qxmax = self.__bounds[1]
        qymin = self.__bounds[2]
        qymax = self.__bounds[3]
        qzmin = self.__bounds[4]
        qzmax = self.__bounds[5]

        A = [[self.ltc.ra_rotate[0], self.ltc.rb_rotate[0], self.ltc.rc_rotate[0]],
             [self.ltc.ra_rotate[1], self.ltc.rb_rotate[1], self.ltc.rc_rotate[1]],
             [self.ltc.ra_rotate[2], self.ltc.rb_rotate[2], self.ltc.rc_rotate[2]]]

        M = [[A[1][1] * A[2][2] - A[1][2] * A[2][1],
              A[0][2] * A[2][1] - A[0][1] * A[2][2],
              A[0][1] * A[1][2] - A[0][2] * A[1][1]],
             [A[1][2] * A[2][0] - A[1][0] * A[2][2],
              A[0][0] * A[2][2] - A[0][2] * A[2][0],
              A[0][2] * A[1][0] - A[0][0] * A[1][2]],
             [A[1][0] * A[2][1] - A[1][1] * A[2][0],
              A[0][1] * A[2][0] - A[0][0] * A[2][1],
              A[0][0] * A[1][1] - A[0][1] * A[1][0]]]

        det = A[0][0] * A[1][1] * A[2][2] + \
            A[1][0] * A[2][1] * A[0][2] + \
            A[2][0] * A[0][1] * A[1][2] - \
            A[0][0] * A[2][1] * A[1][2] - \
            A[1][0] * A[0][1] * A[2][2] - \
            A[2][0] * A[1][1] * A[0][2]

        for i in range(3):
            for j in range(3):
                M[i][j] /= det
                pass
            pass

        phi = [ApplyMat3(M, [qxmin, qymin, qzmin]),
               ApplyMat3(M, [qxmin, qymax, qzmin]),
               ApplyMat3(M, [qxmax, qymin, qzmin]),
               ApplyMat3(M, [qxmax, qymax, qzmin]),
               ApplyMat3(M, [qxmin, qymin, qzmax]),
               ApplyMat3(M, [qxmin, qymax, qzmax]),
               ApplyMat3(M, [qxmax, qymin, qzmax]),
               ApplyMat3(M, [qxmax, qymax, qzmax])]

        imin = phi[0][0]; imax = phi[0][0]
        jmin = phi[0][1]; jmax = phi[0][1]
        kmin = phi[0][2]; kmax = phi[0][2]

        for x in phi:
            imin = min(x[0], imin)
            imax = max(x[0], imax)
            jmin = min(x[1], jmin)
            jmax = max(x[1], jmax)
            kmin = min(x[2], kmin)
            kmax = max(x[2], kmax)
            pass

        imin = int(math.ceil(imin))
        jmin = int(math.ceil(jmin))
        kmin = int(math.ceil(kmin))

        imax = int(math.floor(imax))
        jmax = int(math.floor(jmax))
        kmax = int(math.floor(kmax))
        return [imin, imax, jmin, jmax, kmin, kmax]

    def CreateDetectableRegion(self, mode):
        for name in self.param.GetAllRegionNames():
            props = self.param.detectorProps[name]

            nPx = props.nPx
            nPSD = props.nPSD
            rdpP = props.rdpP

            gridSize = (NUM_WAVE_NUMBERS + 1) * (nPx / rdpP + 1) * (nPSD + 1)

            Pxv = np.linspace(0, nPx, nPx / rdpP + 1)[np.newaxis, :, np.newaxis]
            PSDv = np.linspace(0, nPSD, nPSD + 1)[np.newaxis, np.newaxis, :]

            if mode == 'DNA':
                kiv = np.linspace(self.param.kimin,
                                  self.param.kimax,
                                  NUM_WAVE_NUMBERS + 1)[:, np.newaxis, np.newaxis]

                theta_mv = props.theta_m[::rdpP][np.newaxis, :, np.newaxis]

                zero_PSDv = np.zeros(PSDv.shape)
                zero_kiv = np.zeros(kiv.shape)

                phi = props.phi_e - props.rPSD * PSDv

                sin_phi = np.sin(phi)
                cos_phi = np.cos(phi)

                theta_B = np.arccos(props.y0 / props.RA * np.cos(theta_mv))

                kf = math.pi / (props.d_miller * np.sin(theta_B))

                qz0 = kf * np.cos(theta_mv)
                qy0 = kf * np.sin(theta_mv)

                qx = qz0 * sin_phi
                qy = qy0 + zero_PSDv
                qz = qz0 * cos_phi

                points = np.array([qx + zero_kiv, qy + zero_kiv, qz - kiv])
                etvals = (kiv ** 2 - kf ** 2) * define.MEV_ENG_FAC + zero_PSDv
                pxvals = Pxv + np.zeros(kiv.shape) + np.zeros(PSDv.shape)
                pass
            elif mode == 'SIK':
                kfv = np.linspace(self.param.kfmin,
                                  self.param.kfmax,
                                  NUM_WAVE_NUMBERS + 1)[:, np.newaxis, np.newaxis]

                w = (props.mR - PSDv) * props.dPSD
                phi = props.rR0 + np.arctan2(w, props.L2)
                L2x = np.sqrt(props.L2 ** 2 + w ** 2)

                cos_phi = np.cos(phi)
                sin_phi = np.sin(phi)

                theta_m = np.arctan2(props.yd0 - Pxv * props.hPx, L2x)

                cos_theta_m = np.cos(theta_m)
                sin_theta_m = np.sin(theta_m)

                qz0 = kfv * cos_theta_m
                qy0 = kfv * sin_theta_m

                qx = qz0 * sin_phi
                qy = qy0
                qz = qz0 * cos_phi - self.param.ki

                points = np.array([qx, qy, qz])
                etvals = define.MEV_ENG_FAC * (self.param.ki ** 2 - kfv ** 2) \
                       + np.zeros(Pxv.shape) + np.zeros(PSDv.shape)
                pxvals = Pxv + np.zeros(kfv.shape) + np.zeros(PSDv.shape)
                pass
            else:
                assert 0, "Illegal Detector Parameter"

            self.__detectorRegions[name]['bounds'] = (np.amin(points[0]), np.amax(points[0]),
                                                      np.amin(points[1]), np.amax(points[1]),
                                                      np.amin(points[2]), np.amax(points[2]))

            points = points.transpose().reshape(3 * gridSize)
            etvals = etvals.transpose().reshape(gridSize)
            pxvals = pxvals.transpose().reshape(gridSize)

            self.__detectorRegions[name]['points'] = points
            self.__detectorRegions[name]['etvals'] = etvals
            self.__detectorRegions[name]['pxvals'] = pxvals
            self.__detectorRegions[name]['dimensions'] = ((NUM_WAVE_NUMBERS + 1), (nPx / rdpP + 1), (nPSD + 1))
            pass
        return

    def GetDetectorRegions(self):
        return self.__detectorRegions

    def CreateThinDetectableRegion(self, mode, etmin, etmax):
        thinDetectableRegionPoints = dict()

        for name in self.param.GetAllRegionNames():
            props = self.param.detectorProps[name]

            nPx = props.nPx
            nPSD = props.nPSD
            rdpP = props.rdpP

            thinDetectableRegionPoints[name] = dict()
            thinDetectableRegionPoints[name]['ponits'] = []
            thinDetectableRegionPoints[name]['dimensions'] = (2, nPx / rdpP + 1, nPSD + 1)

            if mode == 'DNA':
                for locPSD in xrange(props.nPSD + 1):
                    phi = props.phi_e - props.rPSD * locPSD

                    sin_phi = math.sin(phi)
                    cos_phi = math.cos(phi)

                    for Px in xrange(0, nPx + 1, rdpP):
                        theta_m = props.theta_m[Px]
                        theta_B = math.acos(props.y0 / props.RA * math.cos(theta_m))

                        lam = 2.0 * props.d_miller * math.sin(theta_B)
                        kf = 2.0 * math.pi / lam

                        qz0 = kf * math.cos(theta_m)
                        qy0 = kf * math.sin(theta_m)

                        qx = qz0 * sin_phi
                        qy = qy0
                        qz = qz0 * cos_phi

                        kimin = math.sqrt(etmin / define.MEV_ENG_FAC + kf ** 2)
                        kimax = math.sqrt(etmax / define.MEV_ENG_FAC + kf ** 2)

                        thinDetectableRegionPoints[name]['ponits'].append((qx, qy, qz - kimin))
                        thinDetectableRegionPoints[name]['ponits'].append((qx, qy, qz - kimax))
                        pass
                    pass
                pass
            elif mode == 'SIK':
                ki = self.param.ki

                for locPSD in xrange(0, nPSD + 1):
                    w = (props.mR - locPSD) * props.dPSD
                    phi = props.rR0 + math.atan2(w, props.L2)
                    L2x = math.sqrt(props.L2 ** 2 + w ** 2)

                    cos_phi = math.cos(phi)
                    sin_phi = math.sin(phi)

                    for Px in xrange(0, nPx + 1, rdpP):
                        theta_m = math.atan2(props.yd0 - Px * props.hPx, L2x)

                        cos_theta_m = math.cos(theta_m)
                        sin_theta_m = math.sin(theta_m)

                        kf = math.sqrt(ki ** 2 - etmin / define.MEV_ENG_FAC)

                        qz0 = kf * cos_theta_m
                        qy0 = kf * sin_theta_m

                        qx = qz0 * sin_phi
                        qy = qy0
                        qz = qz0 * cos_phi

                        thinDetectableRegionPoints[name]['ponits'].append((qx, qy, qz - ki))

                        kf = math.sqrt(ki ** 2 - etmax / define.MEV_ENG_FAC)

                        qz0 = kf * cos_theta_m
                        qy0 = kf * sin_theta_m

                        qx = qz0 * sin_phi
                        qy = qy0
                        qz = qz0 * cos_phi

                        thinDetectableRegionPoints[name]['ponits'].append((qx, qy, qz - ki))
                        pass
                    pass
                pass
            else:
                assert 0, "Illegal Detector Parameter"

            pass
        return thinDetectableRegionPoints

    def SetBoundingBox(self, margin, checkedRegions):
        firstLoop = True
        range = [None, None, None, None, None, None]

        if len(checkedRegions) == 0:
            checkedRegions = self.__detectorRegions.keys()
            pass

        for region in checkedRegions:
            bounds = self.__detectorRegions[region]['bounds']

            for i in (0, 2, 4):
                j = i + 1
                if firstLoop:
                    range[i] = bounds[i]
                    range[j] = bounds[j]
                    pass
                else:
                    range[i] = min(range[i], bounds[i])
                    range[j] = max(range[j], bounds[j])
                    pass
                pass
            firstLoop = False
            pass

        qxc = 0.5 * (range[0] + range[1])
        qyc = 0.5 * (range[2] + range[3])
        qzc = 0.5 * (range[4] + range[5])

        qxw = 0.5 * (range[1] - range[0]) * (1.0 + margin[0])
        qyw = 0.5 * (range[3] - range[2]) * (1.0 + margin[1])
        qzw = 0.5 * (range[5] - range[4]) * (1.0 + margin[2])

        xrange = GetRoundingRange([qxc - qxw, qxc + qxw], -1)
        yrange = GetRoundingRange([qyc - qyw, qyc + qyw], -1)
        zrange = GetRoundingRange([qzc - qzw, qzc + qzw], -1)

        self.__bounds = (xrange[0], xrange[1],
                         yrange[0], yrange[1],
                         zrange[0], zrange[1])
        return

    def UpdateQpPositions(self, mode):
        if mode == 'DNA':
            QPointClass = DNAQPoint
            pass
        else:
            QPointClass = DGTypeQPoint
            pass

        checkedRegions = self.param.GetAllRegionNames()
        [ids, ide, jds, jde, kds, kde] = self.GetBoundIndices()

        for i in xrange(len(self.__qPointArray)):
            self.__qPointArray[i].SetVisible(False)
            pass

        for i in xrange(ids, ide + 1):
            for j in xrange(jds, jde + 1):
                for k in xrange(kds, kde + 1):
                    indexArray = (i, j, k)
                    [qx, qy, qz] = self.GetRotateQPosition(indexArray)

                    qp = QPointClass(self.param)
                    qp.SetIndex(indexArray[0], indexArray[1], indexArray[2])

                    key = qp.GetIndexString()

                    if key not in self.__qPointHash:
                        self.__qPointArray.append(qp)
                        self.__qPointHash[key] = qp
                        pass
                    else:
                        qp = self.__qPointHash[key]
                        pass

                    qp.SetPosition(qx, qy, qz)
                    qp.CalcConfig(checkedRegions)

                    if (self.__bounds[0] - define.GEOM_TINY_VALUE <= qx <=
                        self.__bounds[1] + define.GEOM_TINY_VALUE and
                        self.__bounds[2] - define.GEOM_TINY_VALUE <= qy <=
                        self.__bounds[3] + define.GEOM_TINY_VALUE and
                        self.__bounds[4] - define.GEOM_TINY_VALUE <= qz <=
                        self.__bounds[5] + define.GEOM_TINY_VALUE):
                        qp.SetVisible(True)
                        pass
                    else:
                        qp.SetVisible(False)
                        pass
                    pass
                pass
            pass
        return

    def GetAllQPoints(self):
        return self.__qPointArray

    def GetVisibleQPoints(self):
        qPoints = []

        for i in xrange(len(self.__qPointArray)):
            if self.__qPointArray[i].IsVisible():
                qPoints.append(self.__qPointArray[i])
            pass

        return qPoints

    def GetQPoint(self, key):
        return self.__qPointHash[key]

    def GetBounds(self):
        return self.__bounds

    def GetBoundingSize(self):
        x = self.__bounds[1] - self.__bounds[0]
        y = self.__bounds[3] - self.__bounds[2]
        z = self.__bounds[5] - self.__bounds[4]
        return (x, y, z)

    def GetCenter(self):
        x = 0.5 * (self.__bounds[1] + self.__bounds[0])
        y = 0.5 * (self.__bounds[3] + self.__bounds[2])
        z = 0.5 * (self.__bounds[5] + self.__bounds[4])
        return (x, y, z)

    def GetDiagonalBoundingSize(self):
        x = self.__bounds[1] - self.__bounds[0]
        y = self.__bounds[3] - self.__bounds[2]
        z = self.__bounds[5] - self.__bounds[4]
        return math.sqrt(x * x + y * y + z * z)

    def ReadDefinition(self, filename):
        self.status.Read(filename)
        return

    def GetRotateQPosition(self, index):
        ra = self.ltc.ra_rotate
        rb = self.ltc.rb_rotate
        rc = self.ltc.rc_rotate

        return vector.GetVectorByIndex(ra, rb, rc, index[0], index[1], index[2])

    def UpdateBoundingBox(self, regions):
        st = self.status

        margin = [st.GetFloat(TAG_GRAPH_AXISMARGIN_X),
                  st.GetFloat(TAG_GRAPH_AXISMARGIN_Y),
                  st.GetFloat(TAG_GRAPH_AXISMARGIN_Z)]

        self.SetBoundingBox(margin, regions)
        return

    def GetScalarFieldRange(self, scalarKind, isMeasurementData):
        smin = 0.0; smax = 0.0

        if scalarKind == define.ENERGY_TRANSFER:
            targetData = self.__detectorRegions

            for name in targetData:
                if targetData[name] == None:
                    continue

                smin = min(np.amin(targetData[name]['etvals']), smin)
                smax = max(np.amax(targetData[name]['etvals']), smax)
                pass

            range = GetRoundingRange((smin, smax), 0)
            pass
        elif scalarKind == define.DETECT_INTENSITY:
            targetData = self.__intensityRegions

            for name in targetData:
                if targetData[name] == None:
                    continue

                smin = min(np.amin(targetData[name]['raw data']), smin)
                smax = max(np.amax(targetData[name]['raw data']), smax)
                pass

            digitNum = math.ceil(math.log10(smax))
            range = GetRoundingRange((smin, smax), digitNum)
            pass

        return range

    def LoadDataFromFile(self, filename, isHDF):
        TOF_BINDATA = 'TOF'
        ENG_BINDATA = 'Energy'

        energyRange = None

        if isHDF:
            import h5py

            HDF5_file = h5py.File(filename, 'r')

            entry_group = HDF5_file['Entry1']
            data_group = entry_group['Data1']
            elmat_group = data_group['ElementContainerMatrixData']
            elary_group = elmat_group['ElementContainerArray0']
            elcon_group = elary_group['ElementContainer0']

            TOF_dataset = elcon_group.get(TOF_BINDATA, None)
            Eng_dataset = elcon_group.get(ENG_BINDATA, None)

            if Eng_dataset != None:
                matrix_header = elmat_group['HeaderBase_in_ElementContainerMatrix']
                ei = matrix_header['Ei'][0]
                etmin = Eng_dataset[0]
                etmax = Eng_dataset[-1]

                energyRange = (ei, etmin, etmax)
                pass
            pass
        else:
            import Manyo

            reader = Manyo.ReadSerializationFileBinary(str(filename))
            elmat_group = reader.LoadElementContainerMatrix()
            elary_group = elmat_group.Put(0)
            elcon_group = elary_group.Put(0)

            if elcon_group.PutXKey() == TOF_BINDATA:
                TOF_dataset = elcon_group.PutX()
                Eng_dataset = None
                pass
            elif elcon_group.PutXKey() == ENG_BINDATA:
                matrix_header = elmat_group.PutHeader()

                TOF_dataset = None
                Eng_dataset = elcon_group.PutX()
                ei = matrix_header.PutDouble('Ei')
                etmin = Eng_dataset[0]
                etmax = Eng_dataset[-1]

                energyRange = (ei, etmin, etmax)
                pass
            pass

        if TOF_dataset != None:
            self.__intensityDataType = TOF_BINDATA
            pass
        elif Eng_dataset != None:
            self.__intensityDataType = ENG_BINDATA
            pass
        else:
            assert 0, 'Illegal nexus file'
            pass

        if self.__intensityDataType == TOF_BINDATA:
            if isHDF:
                self.param.L1 = matrix_header['L1'][0] * MILLI
                TOF_array = np.zeros(shape=TOF_dataset.shape,
                                     dtype=TOF_dataset.dtype)

                TOF_dataset.read_direct(TOF_array)
                pass
            else:
                self.param.L1 = matrix_header.PutDouble('L1') * MILLI
                TOF_array = TOF_dataset 
                pass

            # Calc TOF offset
            BIN_minid = None
            BIN_maxid = None

            vi = DIRAC_CONST / NEUTRON_MASS * self.param.ki / ANGSTROM
            TOF0 = self.param.L1 / vi

            L2max = 0.0

            for props in self.param.detectorProps.values():
                w = max(abs(props.mR - props.nPSD), props.mR) * props.dPSD
                L2x = sqrt(props.L2 ** 2 + w ** 2)

                h = max(abs(props.yd0 - props.nPx * props.hPx), abs(props.yd0))
                L2t = sqrt(L2x ** 2 + h ** 2)
                L2max = max(L2max, L2t)
                pass

            for i in xrange(TOF_array.size):
                vf = L2max / (TOF_array[i] * MICRO - TOF0)
                kf = NEUTRON_MASS / DIRAC_CONST * vf * ANGSTROM

                if kf > 0.0 and self.param.kfmin <= kf <= self.param.kfmax:
                    if BIN_minid == None:
                        BIN_minid = max(i - 1, 0)
                        pass
                    BIN_maxid = i
                    pass
                pass

            if BIN_minid == None:
                if isHDF:
                    HDF5_file.close()
                    pass
                return energyRange

            TOF_array = np.require(TOF_array[BIN_minid:BIN_maxid + 1], dtype = np.float32)
            self.param.TOF_array = TOF_array
            pass
        else:
            if isHDF:
                self.param.nexus_ei = matrix_header['Ei'][0]
                pass
            else:
                self.param.nexus_ei = matrix_header.PutDouble('Ei')
                pass
            self.param.nexus_ki = np.sqrt(self.param.nexus_ei / define.MEV_ENG_FAC)

            if isHDF:
                Eng_array = np.zeros(shape=Eng_dataset.shape,
                                     dtype=Eng_dataset.dtype)

                Eng_dataset.read_direct(Eng_array)
                self.param.Eng_array = np.require(Eng_array, dtype = np.float32)
                pass
            else:
                Eng_array = np.array(Eng_dataset)
                self.param.Eng_array = np.require(Eng_array, dtype = np.float32)
                pass

            BIN_minid = 0; BIN_maxid = Eng_array.size - 1
            pass

        self.__intensityData = dict()

        if isHDF:
            numLoadedDetectors = elmat_group['size'][0]
            pass
        else:
            numLoadedDetectors = elmat_group.PutSize()
            pass

        for i in xrange(numLoadedDetectors):
            if isHDF:
                name = 'ElementContainerArray' + str(i)
                elary_group = elmat_group[name]
                nPx = elary_group['size'][0]
                header = elary_group['HeaderBase_in_ElementContainerArray']
                PSD = header['PSDID'][0]
                pass
            else:
                elary_group = elmat_group.Put(i)
                nPx = elary_group.PutSize()
                header = elary_group.PutHeader()
                PSD = header.PutDouble('PSDID')
                pass

            for region, props in self.param.detectorProps.items():
                locPSD = PSD - props.PSD0
                if 0 <= locPSD < props.nPSD:
                    if self.__intensityData.get(region, None) == None:
                        self.__intensityData[region] = np.zeros(shape=(BIN_maxid - BIN_minid, props.nPx, props.nPSD), dtype = np.float32)
                        pass

                    for Px in xrange(nPx):
                        if isHDF:
                            name = 'ElementContainer' + str(Px)
                            elcon_group = elary_group[name]
                            intensity_dataset = elcon_group['Intensity']
                            intensity_array = np.zeros(shape = intensity_dataset.shape,
                                                         dtype = intensity_dataset.dtype)
                            intensity_dataset.read_direct(intensity_array)
                            pass
                        else:
                            elcon_group = elary_group.Put(Px)
                            intensity_array = elcon_group.PutY()
                            pass
                        intensity_array = np.require(intensity_array, dtype = np.float32)
                        self.__intensityData[region][:, Px, locPSD] = intensity_array[BIN_minid:BIN_maxid]
                        pass
                    break
                pass
            pass

        if isHDF:
            HDF5_file.close()
            pass
        return energyRange

    def MeasurementDataMapping(self, mode):
        for regionName in self.param.GetAllRegionNames():
            if regionName not in self.__intensityData:
                continue

            self.__intensityRegions[regionName] = dict()

            props = self.param.detectorProps[regionName]

            nPx = props.nPx
            nPSD = props.nPSD

            Pxv = np.linspace(0, nPx, nPx + 1)[np.newaxis, :, np.newaxis]
            PSDv = np.linspace(0, nPSD, nPSD + 1)[np.newaxis, np.newaxis, :]

            if mode == 'DNA':
                Etv = self.param.Eng_array[:, np.newaxis, np.newaxis]

                theta_mv = props.theta_m[np.newaxis, :, np.newaxis]
                theta_B = np.arccos(props.y0 / props.RA * np.cos(theta_mv))
                kfv = math.pi / (props.d_miller * np.sin(theta_B))
                Ef = kfv * kfv * define.MEV_ENG_FAC
                Ei = Ef + Etv
                kiv = np.sqrt(Ei / define.MEV_ENG_FAC)

                phi = props.phi_e - props.rPSD * PSDv

                cos_phi = np.cos(phi)
                sin_phi = np.sin(phi)

                cos_theta_mv = np.cos(theta_mv)
                sin_theta_mv = np.sin(theta_mv)

                qz0 = kfv * cos_theta_mv
                qy0 = kfv * sin_theta_mv

                qx = qz0 * sin_phi
                qy = qy0 + np.zeros(PSDv.shape)
                qz = qz0 * cos_phi

                points = np.array([qx + np.zeros(kiv.shape), qy + np.zeros(kiv.shape), qz - kiv])
                etvals = Etv + np.zeros(Pxv.shape) + np.zeros(PSDv.shape)
                pxvals = Pxv + np.zeros(Etv.shape) + np.zeros(PSDv.shape)
                pass
            elif mode == 'SIK':
                w = (props.mR - PSDv) * props.dPSD
                phi = props.rR0 + np.arctan2(w, props.L2)
                L2x = np.sqrt(props.L2 ** 2 + w ** 2)

                cos_phi = np.cos(phi)
                sin_phi = np.sin(phi)

                if self.__intensityDataType == 'TOF':
                    TOFv = self.param.TOF_array[:, np.newaxis, np.newaxis]

                    h = props.yd0 - Pxv * props.hPx
                    L2t = np.sqrt(L2x ** 2 + h ** 2)

                    vi = DIRAC_CONST / NEUTRON_MASS * self.param.ki / ANGSTROM
                    vf = L2t / (TOFv * MICRO - self.param.L1 / vi)
                    kf = NEUTRON_MASS / DIRAC_CONST * vf * ANGSTROM

                    theta_m = np.arctan2(h, L2x)

                    cos_theta_m = np.cos(theta_m)
                    sin_theta_m = np.sin(theta_m)

                    qz0 = kf * cos_theta_m
                    qy0 = kf * sin_theta_m

                    qx = qz0 * sin_phi
                    qy = qy0
                    qz = qz0 * cos_phi - self.param.ki

                    points = np.array([qx, qy, qz])
                    etvals = define.MEV_ENG_FAC * (self.param.ki ** 2 - kf ** 2)
                    pxvals = Pxv + np.zeros(TOFv.shape) + np.zeros(PSDv.shape)
                    pass
                else:
                    Efv = self.param.Eng_array[:, np.newaxis, np.newaxis]

                    kf = np.sqrt((self.param.nexus_ei - Efv) / define.MEV_ENG_FAC)

                    theta_m = np.arctan2(props.yd0 - Pxv * props.hPx, L2x)

                    cos_theta_m = np.cos(theta_m)
                    sin_theta_m = np.sin(theta_m)

                    qz0 = kf * cos_theta_m
                    qy0 = kf * sin_theta_m

                    qx = qz0 * sin_phi
                    qy = qy0
                    qz = qz0 * cos_phi - self.param.nexus_ki

                    points = np.array([qx, qy, qz])
                    etvals = Efv + np.zeros(Pxv.shape) + np.zeros(PSDv.shape)
                    pxvals = Pxv + np.zeros(Efv.shape) + np.zeros(PSDv.shape)
                    pass
            else:
                assert 0, "Illegal Detector Parameter"

            if self.__intensityDataType== 'TOF':
                self.__intensityRegions[regionName]['dimensions'] = (self.param.TOF_array.size, nPx + 1, nPSD + 1)
                gridSize = self.param.TOF_array.size * (nPx + 1) * (nPSD + 1)
                pass
            else:
                self.__intensityRegions[regionName]['dimensions'] = (self.param.Eng_array.size, nPx + 1, nPSD + 1)
                gridSize = self.param.Eng_array.size * (nPx + 1) * (nPSD + 1)
                pass

            self.__intensityRegions[regionName]['raw data'] = self.__intensityData[regionName]
            self.__intensityRegions[regionName]['points'] = points.transpose().reshape(3 * gridSize)
            self.__intensityRegions[regionName]['etvals'] = etvals.transpose().reshape(gridSize)
            self.__intensityRegions[regionName]['pxvals'] = pxvals.transpose().reshape(gridSize)
            pass
        return

    def GetIntensityRegions(self):
        return self.__intensityRegions

def ApplyRotMat(vtkRotMat, pos):
    return vtkRotMat.TransformPoint(pos)

class Lattice(object):
    LT_SYS_TRICLINIC = 'Triclinic'
    LT_SYS_MONOCLINIC = 'Monoclinic'
    LT_SYS_ORTHORHOMBIC = 'Orthorhombic'
    LT_SYS_TETRAGONAL = 'Tetragonal'
    LT_SYS_TRIGONAL = 'Trigonal'
    LT_SYS_HEXAGONAL = 'Hexagonal'
    LT_SYS_CUBIC = 'Cubic'

    LT_TYPE_SIMPLE = 'Simple'
    LT_TYPE_BODY_CENTERED = 'Body-centered'
    LT_TYPE_FACE_CENTERED = 'Face-centered'
    LT_TYPE_BASE_CENTERED = 'Base-centered'
    LT_TYPE_RHOMBOHEDRAL = 'Rhombohedral'

    LATTICE_SYSTEMS = {
        LT_SYS_TRICLINIC:(LT_TYPE_SIMPLE,),
        LT_SYS_MONOCLINIC:(LT_TYPE_SIMPLE, LT_TYPE_BASE_CENTERED,),
        LT_SYS_ORTHORHOMBIC:(LT_TYPE_SIMPLE, LT_TYPE_BASE_CENTERED, LT_TYPE_BODY_CENTERED, LT_TYPE_FACE_CENTERED,),
        LT_SYS_TETRAGONAL:(LT_TYPE_SIMPLE, LT_TYPE_BODY_CENTERED,),
        LT_SYS_TRIGONAL:(LT_TYPE_RHOMBOHEDRAL,),
        LT_SYS_HEXAGONAL:(LT_TYPE_SIMPLE,),
        LT_SYS_CUBIC:(LT_TYPE_SIMPLE, LT_TYPE_BODY_CENTERED, LT_TYPE_FACE_CENTERED,)
        }

    LATTICE_KIND_DENOMS = {
        LT_SYS_TRICLINIC: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ]
            },
        LT_SYS_MONOCLINIC: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ],
            LT_TYPE_BASE_CENTERED: [
                [decimal.Decimal(1), decimal.Decimal(2), None],
                [None, decimal.Decimal(2), None],
                [None, None, decimal.Decimal(1)]
                ]
            },
        LT_SYS_ORTHORHOMBIC: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ],
            LT_TYPE_BASE_CENTERED: [
                [decimal.Decimal(2), decimal.Decimal(-2), None],
                [decimal.Decimal(2), decimal.Decimal(2), None],
                [None, None, 1]
                ],
            LT_TYPE_BODY_CENTERED: [
                [decimal.Decimal(-2), decimal.Decimal(2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(-2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(2), decimal.Decimal(-2)]
                ],
            LT_TYPE_FACE_CENTERED: [
                [None, decimal.Decimal(2), decimal.Decimal(2)],
                [decimal.Decimal(2), None, decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(2), None]
                ],
            },
        LT_SYS_TETRAGONAL: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ],
            LT_TYPE_BODY_CENTERED: [
                [decimal.Decimal(-2), decimal.Decimal(2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(-2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(2), decimal.Decimal(-2)]
                ]
            },
        LT_SYS_TRIGONAL: {
            LT_TYPE_RHOMBOHEDRAL: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ]
            },
        LT_SYS_HEXAGONAL: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ]
            },
        LT_SYS_CUBIC: {
            LT_TYPE_SIMPLE: [
                [decimal.Decimal(1), None, None],
                [None, decimal.Decimal(1), None],
                [None, None, decimal.Decimal(1)]
                ],
            LT_TYPE_BODY_CENTERED: [
                [decimal.Decimal(-2), decimal.Decimal(2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(-2), decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(2), decimal.Decimal(-2)]
                ],
            LT_TYPE_FACE_CENTERED: [
                [None, decimal.Decimal(2), decimal.Decimal(2)],
                [decimal.Decimal(2), None, decimal.Decimal(2)],
                [decimal.Decimal(2), decimal.Decimal(2), None]
                ]
            }
        }

    def __init__(self, status):
        ltcSys = status.GetValue(TAG_SAMPLE_LTCSYS)
        ltcKind = status.GetValue(TAG_SAMPLE_LTCKIND)
        al = status.GetFloat(TAG_SAMPLE_A)
        bl = status.GetFloat(TAG_SAMPLE_B)
        cl = status.GetFloat(TAG_SAMPLE_C)
        alpha = status.GetFloat(TAG_SAMPLE_ALPHA)
        beta = status.GetFloat(TAG_SAMPLE_BETA)
        gamma = status.GetFloat(TAG_SAMPLE_GAMMA)
        uh = status.GetFloat(TAG_SAMPLE_UH)
        uk = status.GetFloat(TAG_SAMPLE_UK)
        ul = status.GetFloat(TAG_SAMPLE_UL)
        vh = status.GetFloat(TAG_SAMPLE_VH)
        vk = status.GetFloat(TAG_SAMPLE_VK)
        vl = status.GetFloat(TAG_SAMPLE_VL)

        self.ltcKindDenom = Lattice.LATTICE_KIND_DENOMS[ltcSys][ltcKind]

        a, b, c = self.GetUnitVector(ltcSys,
                                     al, bl, cl,
                                     alpha, beta, gamma)

        v0 = vector.GetDot(a, vector.GetCross(b, c))
        fac = 2.0 * math.pi / v0

        ra = vector.GetCross(b, c, fac)
        rb = vector.GetCross(c, a, fac)
        rc = vector.GetCross(a, b, fac)

        u = vector.GetVectorByIndex(ra, rb, rc, uh, uk, ul)
        v = vector.GetVectorByIndex(ra, rb, rc, vh, vk, vl)

        # Calc qY-axis rotation angle
        psi = -math.atan2(u[0], u[2])

        ua = [0.0,
              u[1],
              - math.sin(psi) * u[0] + math.cos(psi) * u[2]]

        va = [math.cos(psi) * v[0] + math.sin(psi) * v[2],
              v[1],
              - math.sin(psi) * v[0] + math.cos(psi) * v[2]]

        # Calc qX-axis rotation angle
        chi = math.atan2(ua[1], ua[2])

        vb = [va[0],
              math.cos(chi) * va[1] - math.sin(chi) * va[2],
              0.0]

        # Calc qZ-axis rotation angle
        phi = -math.atan2(vb[1], vb[0])

        R = VTKPanel.GetRotationMatrix(psi, chi, phi)

        self.ra_rotate = self.ra_orig = ApplyRotMat(R, ra)
        self.rb_rotate = self.rb_orig = ApplyRotMat(R, rb)
        self.rc_rotate = self.rc_orig = ApplyRotMat(R, rc)

        self.a_rotate = self.a_orig = ApplyRotMat(R, a)
        self.b_rotate = self.b_orig = ApplyRotMat(R, b)
        self.c_rotate = self.c_orig = ApplyRotMat(R, c)

        self.u = ApplyRotMat(R, u)
        self.v = ApplyRotMat(R, v)

        self.rotUh = uh
        self.rotUk = uk
        self.rotUl = ul
        self.rotVh = vh
        self.rotVk = vk
        self.rotVl = vl
        return

    def GetUnitVector(self,
                      ltcSys,
                      al, bl, cl,
                      alpha, beta, gamma):
        alpha = math.radians(alpha)
        beta = math.radians(beta)
        gamma = math.radians(gamma)

        if ltcSys == Lattice.LT_SYS_TRICLINIC:
            a = [al, 0.0, 0.0]
            b = [bl * math.cos(gamma), bl * math.sin(gamma), 0.0]
            c = [cl * math.cos(alpha),
                 cl * math.cos(beta) * math.sin(gamma),
                 cl * np.sqrt(1.0 - math.cos(alpha) ** 2 + (math.cos(beta) * math.sin(gamma)) ** 2)]
            pass
        elif ltcSys == Lattice.LT_SYS_MONOCLINIC:
            a = [al, 0.0, 0.0]
            b = [0.0, bl, 0.0]
            c = [cl * math.cos(beta), 0.0, cl * math.sin(beta)]
            pass
        elif ltcSys == Lattice.LT_SYS_ORTHORHOMBIC:
            a = [al, 0.0, 0.0]
            b = [0.0, bl, 0.0]
            c = [0.0, 0.0, cl]
            pass
        elif ltcSys == Lattice.LT_SYS_TETRAGONAL:
            a = [al, 0.0, 0.0]
            b = [0.0, al, 0.0]
            c = [0.0, 0.0, cl]
            pass
        elif ltcSys == Lattice.LT_SYS_TRIGONAL:
            s = 2.0 * al / np.sqrt(3.0) * math.sin(0.5 * alpha)
            r = np.sqrt(al * al - s * s)

            a = [0.0, s, r]
            b = [0.5 * s * np.sqrt(3.0), -0.5 * s, r]
            c = [-0.5 * s * np.sqrt(3.0), -0.5 * s, r]
            pass
        elif ltcSys == Lattice.LT_SYS_HEXAGONAL:
            a = [0.5 * np.sqrt(3.0) * al, -0.5 * al, 0.0]
            b = [0.0, al, 0.0]
            c = [0.0, 0.0, cl]
            pass
        elif ltcSys == Lattice.LT_SYS_CUBIC:
            a = [al, 0.0, 0.0]
            b = [0.0, al, 0.0]
            c = [0.0, 0.0, al]
            pass
        return a, b, c

    def SetRotation(self, psi, chi, phi):
        R = VTKPanel.GetRotationMatrix(psi, chi, phi)

        self.ra_rotate = ApplyRotMat(R, self.ra_orig)
        self.rb_rotate = ApplyRotMat(R, self.rb_orig)
        self.rc_rotate = ApplyRotMat(R, self.rc_orig)

        self.a_rotate = ApplyRotMat(R, self.a_orig)
        self.b_rotate = ApplyRotMat(R, self.b_orig)
        self.c_rotate = ApplyRotMat(R, self.c_orig)

        fac = 1.0 / (2.0 * math.pi)

        self.rotUh = vector.GetDot(self.a_rotate, self.u) * fac
        self.rotUk = vector.GetDot(self.b_rotate, self.u) * fac
        self.rotUl = vector.GetDot(self.c_rotate, self.u) * fac
        self.rotVh = vector.GetDot(self.a_rotate, self.v) * fac
        self.rotVk = vector.GetDot(self.b_rotate, self.v) * fac
        self.rotVl = vector.GetDot(self.c_rotate, self.v) * fac

        if abs(self.rotUh) < define.GEOM_TINY_VALUE:
            self.rotUh = 0.0
            pass

        if abs(self.rotUk) < define.GEOM_TINY_VALUE:
            self.rotUk = 0.0
            pass

        if abs(self.rotUl) < define.GEOM_TINY_VALUE:
            self.rotUl = 0.0
            pass

        if abs(self.rotVh) < define.GEOM_TINY_VALUE:
            self.rotVh = 0.0
            pass

        if abs(self.rotVk) < define.GEOM_TINY_VALUE:
            self.rotVk = 0.0
            pass

        if abs(self.rotVl) < define.GEOM_TINY_VALUE:
            self.rotVl = 0.0
            pass
        return

try:
    import elementtree.ElementTree as ET # for Python 2.4
    pass
except ImportError:
    import xml.etree.ElementTree as ET # for Python 2.5 or later.
    pass

XML = ET.XML
ElementTree = ET.ElementTree
Element = ET.Element
iselement = ET.iselement
SubElement = ET.SubElement

class StatusError:
    def __init__(self, message):
        self.message = message
        return

    def __str__(self):
        return repr('Status Error: ' + self.message)

    def GetMessage(self):
        return self.message

# TAG NAMES FOR CONFIGULATIONS
TAG_ROOT = ('MethodAssistant',)

TAG_ROTATE = TAG_ROOT + ('rotate',)
TAG_ROTATE_PSI = TAG_ROTATE + ('psi',)
TAG_ROTATE_CHI = TAG_ROTATE + ('chi',)
TAG_ROTATE_PHI = TAG_ROTATE + ('phi',)
TAG_ROTATE_GUIDEAXIS = TAG_ROTATE + ('guide_axis',)

TAG_GRAPH = TAG_ROOT + ('graph_settings',)
TAG_GRAPH_AXISMARGIN = TAG_GRAPH + ('axis_margin',)
TAG_GRAPH_AXISMARGIN_X = TAG_GRAPH_AXISMARGIN + ('x',)
TAG_GRAPH_AXISMARGIN_Y = TAG_GRAPH_AXISMARGIN + ('y',)
TAG_GRAPH_AXISMARGIN_Z = TAG_GRAPH_AXISMARGIN + ('z',)

TAG_GRAPH_AXISDISPLAY = TAG_GRAPH + ('axis_display',)
TAG_GRAPH_AXISDISPLAY_X = TAG_GRAPH_AXISDISPLAY + ('x',)
TAG_GRAPH_AXISDISPLAY_Y = TAG_GRAPH_AXISDISPLAY + ('y',)
TAG_GRAPH_AXISDISPLAY_Z = TAG_GRAPH_AXISDISPLAY + ('z',)

TAG_OBJ3D = TAG_ROOT + ('object_settings',)
TAG_OBJ3D_ETVAL = TAG_OBJ3D + ('etval',)
TAG_OBJ3D_ETWIDTH = TAG_OBJ3D + ('etwidth',)
TAG_OBJ3D_QPCOLOR = TAG_OBJ3D + ('qp_color',)
TAG_OBJ3D_QPDISPLAY = TAG_OBJ3D + ('qp_display_mode',)
TAG_OBJ3D_SURFSCALAR = TAG_OBJ3D + ('surface_scalar',)
TAG_OBJ3D_PXNO = TAG_OBJ3D + ('pixel_no',)
TAG_OBJ3D_PXRANGE = TAG_OBJ3D + ('pixel_range',)
TAG_OBJ3D_ETRANGE = TAG_OBJ3D + ('et_range',)
TAG_OBJ3D_ETRANGE_MODE = TAG_OBJ3D_ETRANGE + ('mode',)
TAG_OBJ3D_ETRANGE_MIN = TAG_OBJ3D_ETRANGE + ('min',)
TAG_OBJ3D_ETRANGE_MAX = TAG_OBJ3D_ETRANGE + ('max',)
TAG_OBJ3D_INTENSITYRANGE = TAG_OBJ3D + ('intensity_range',)
TAG_OBJ3D_INTENSITYRANGE_MODE = TAG_OBJ3D_INTENSITYRANGE + ('mode',)
TAG_OBJ3D_INTENSITYRANGE_MIN = TAG_OBJ3D_INTENSITYRANGE + ('min',)
TAG_OBJ3D_INTENSITYRANGE_MAX = TAG_OBJ3D_INTENSITYRANGE + ('max',)

TAG_ENG = TAG_ROOT + ('energy',)
TAG_ENG_EIMIN = TAG_ENG + ('minimum_incident_energy',)
TAG_ENG_EIMAX = TAG_ENG + ('maximum_incident_energy',)
TAG_ENG_EI = TAG_ENG + ('incident_energy',)
TAG_ENG_ETMIN = TAG_ENG + ('minimum_transition_energy',)
TAG_ENG_ETMAX = TAG_ENG + ('maximum_transition_energy',)

TAG_QEGRAPH = TAG_ROOT + ('QE_graph_settings',)

TAG_QEGRAPH_PLANEDEF = TAG_QEGRAPH + ('plane_definition',)
TAG_QEGRAPH_OH = TAG_QEGRAPH + ('Oh',)
TAG_QEGRAPH_OK = TAG_QEGRAPH + ('Ok',)
TAG_QEGRAPH_OL = TAG_QEGRAPH + ('Ol',)
TAG_QEGRAPH_UH = TAG_QEGRAPH + ('Uh',)
TAG_QEGRAPH_UK = TAG_QEGRAPH + ('Uk',)
TAG_QEGRAPH_UL = TAG_QEGRAPH + ('Ul',)
TAG_QEGRAPH_VH = TAG_QEGRAPH + ('Vh',)
TAG_QEGRAPH_VK = TAG_QEGRAPH + ('Vk',)
TAG_QEGRAPH_VL = TAG_QEGRAPH + ('Vl',)

TAG_QRGRAPH = TAG_ROOT + ('QR_graph_settings',)

TAG_QRGRAPH_PLANEDEF = TAG_QRGRAPH + ('plane_definition',)
TAG_QRGRAPH_OH = TAG_QRGRAPH + ('Oh',)
TAG_QRGRAPH_OK = TAG_QRGRAPH + ('Ok',)
TAG_QRGRAPH_OL = TAG_QRGRAPH + ('Ol',)
TAG_QRGRAPH_UH = TAG_QRGRAPH + ('Uh',)
TAG_QRGRAPH_UK = TAG_QRGRAPH + ('Uk',)
TAG_QRGRAPH_UL = TAG_QRGRAPH + ('Ul',)
TAG_QRGRAPH_VH = TAG_QRGRAPH + ('Vh',)
TAG_QRGRAPH_VK = TAG_QRGRAPH + ('Vk',)
TAG_QRGRAPH_VL = TAG_QRGRAPH + ('Vl',)
TAG_QRGRAPH_ROTATEAXIS = TAG_QRGRAPH + ('rotate_axis',)
TAG_QRGRAPH_NSTEP = TAG_QRGRAPH + ('nstep',)
TAG_QRGRAPH_INTERVAL = TAG_QRGRAPH + ('interval',)
TAG_QRGRAPH_PSI0 = TAG_QRGRAPH + ('psi0',)
TAG_QRGRAPH_CHI0 = TAG_QRGRAPH + ('chi0',)
TAG_QRGRAPH_PHI0 = TAG_QRGRAPH + ('phi0',)
TAG_QRGRAPH_ETMIN = TAG_QRGRAPH + ('minimum_transition_energy',)
TAG_QRGRAPH_ETMAX = TAG_QRGRAPH + ('maximum_transition_energy',)

TAG_SAMPLE = TAG_ROOT + ('sample',)

TAG_SAMPLE_NAME = TAG_SAMPLE + ('name',)
TAG_SAMPLE_LTCSYS = TAG_SAMPLE + ('lattice_system',)
TAG_SAMPLE_LTCKIND = TAG_SAMPLE + ('lattice_kind',)
TAG_SAMPLE_A = TAG_SAMPLE + ('a',)
TAG_SAMPLE_B = TAG_SAMPLE + ('b',)
TAG_SAMPLE_C = TAG_SAMPLE + ('c',)
TAG_SAMPLE_ALPHA = TAG_SAMPLE + ('alpha',)
TAG_SAMPLE_BETA = TAG_SAMPLE + ('beta',)
TAG_SAMPLE_GAMMA = TAG_SAMPLE + ('gamma',)
TAG_SAMPLE_UH = TAG_SAMPLE + ('Uh',)
TAG_SAMPLE_UK = TAG_SAMPLE + ('Uk',)
TAG_SAMPLE_UL = TAG_SAMPLE + ('Ul',)
TAG_SAMPLE_VH = TAG_SAMPLE + ('Vh',)
TAG_SAMPLE_VK = TAG_SAMPLE + ('Vk',)
TAG_SAMPLE_VL = TAG_SAMPLE + ('Vl',)

TAG_SELECTQPINDEX = TAG_ROOT + ('select_Q_point_index',)

class Status(object):
    def __init__(self):
        self._data = dict()
        self._tree = ElementTree()
        return

    def SetValue(self, key, value):
        prev_value = self._data.get(key, None)
        has_key = self._data.has_key(key)

        self._data[key] = value
        return

    def GetValue(self, key):
        return self._data.get(key, None)

    def GetFloat(self, key):
        value = self.GetValue(key)

        if isinstance(value, float):
            return value
        elif isinstance(value, decimal.Decimal):
            return float(value)

        return None

    def ParseXml(self, source):
        self._tree.parse(source)
        return

    def OutputStatus(self, filepath):
        # Copy data to elementtree
        for key, value in self._data.items():
            path = self._GetXMLPath(key)
            elem = self._tree.find(path)

            if iselement(elem):
                elem.text = str(value)
                pass
            pass

        # Output xml data
        self._tree.write(filepath, 'utf-8')
        return

    def ReadValue(self, key, trans = str):
        path = self._GetXMLPath(key)
        elem = self._tree.find(path)

        if iselement(elem):
            try:
                value = trans(elem.text)
                pass
            except:
                raise StatusError('Cannot convert xml data of ' + path + ' to ' + str(trans) + '.')
            else:
                self.SetValue(key, value)
                pass
            pass
        else:
            raise StatusError('Cannot find xml data of ' + path)
        return

    def _GetXMLPath(self, key):
        if len(key) <= 1:
            return '/'

        first = True
        for name in key:
            if first:
                path = '.'
                first = False
                pass
            else:
                path += '/' + name
                pass
            pass
        return path

    def Read(self, filepath):
        self.ParseXml(filepath)

        self.ReadValue(TAG_ROTATE_PSI, decimal.Decimal)
        self.ReadValue(TAG_ROTATE_CHI, decimal.Decimal)
        self.ReadValue(TAG_ROTATE_PHI, decimal.Decimal)
        self.ReadValue(TAG_ROTATE_GUIDEAXIS, str)

        self.ReadValue(TAG_GRAPH_AXISMARGIN_X, decimal.Decimal)
        self.ReadValue(TAG_GRAPH_AXISMARGIN_Y, decimal.Decimal)
        self.ReadValue(TAG_GRAPH_AXISMARGIN_Z, decimal.Decimal)
        
        self.ReadValue(TAG_GRAPH_AXISDISPLAY_X, bool)
        self.ReadValue(TAG_GRAPH_AXISDISPLAY_Y, bool)
        self.ReadValue(TAG_GRAPH_AXISDISPLAY_Z, bool)
        
        self.ReadValue(TAG_OBJ3D_ETVAL, decimal.Decimal)
        self.ReadValue(TAG_OBJ3D_ETWIDTH, decimal.Decimal)

        self.ReadValue(TAG_OBJ3D_QPCOLOR, int)
        self.ReadValue(TAG_OBJ3D_QPDISPLAY, int)
        self.ReadValue(TAG_OBJ3D_SURFSCALAR, str)
        self.ReadValue(TAG_OBJ3D_PXNO, int)
        self.ReadValue(TAG_OBJ3D_PXRANGE, int)
        self.ReadValue(TAG_OBJ3D_ETRANGE_MODE, str)
        self.ReadValue(TAG_OBJ3D_ETRANGE_MIN, decimal.Decimal)
        self.ReadValue(TAG_OBJ3D_ETRANGE_MAX, decimal.Decimal)
        self.ReadValue(TAG_OBJ3D_INTENSITYRANGE_MODE, str)
        self.ReadValue(TAG_OBJ3D_INTENSITYRANGE_MIN, decimal.Decimal)
        self.ReadValue(TAG_OBJ3D_INTENSITYRANGE_MAX, decimal.Decimal)

        self.ReadValue(TAG_ENG_EIMIN, decimal.Decimal) # for DNA
        self.ReadValue(TAG_ENG_EIMAX, decimal.Decimal) # for DNA
        self.ReadValue(TAG_ENG_EI, decimal.Decimal)    # for SIK, AMR
        self.ReadValue(TAG_ENG_ETMIN, decimal.Decimal) # for SIK, AMR
        self.ReadValue(TAG_ENG_ETMAX, decimal.Decimal) # for SIK, AMR

        self.ReadValue(TAG_QEGRAPH_PLANEDEF, int)
        self.ReadValue(TAG_QEGRAPH_OH, int)
        self.ReadValue(TAG_QEGRAPH_OK, int)
        self.ReadValue(TAG_QEGRAPH_OL, int)
        self.ReadValue(TAG_QEGRAPH_UH, int)
        self.ReadValue(TAG_QEGRAPH_UK, int)
        self.ReadValue(TAG_QEGRAPH_UL, int)
        self.ReadValue(TAG_QEGRAPH_VH, int)
        self.ReadValue(TAG_QEGRAPH_VK, int)
        self.ReadValue(TAG_QEGRAPH_VL, int)

        self.ReadValue(TAG_QRGRAPH_PLANEDEF, int)
        self.ReadValue(TAG_QRGRAPH_OH, int)
        self.ReadValue(TAG_QRGRAPH_OK, int)
        self.ReadValue(TAG_QRGRAPH_OL, int)
        self.ReadValue(TAG_QRGRAPH_UH, int)
        self.ReadValue(TAG_QRGRAPH_UK, int)
        self.ReadValue(TAG_QRGRAPH_UL, int)
        self.ReadValue(TAG_QRGRAPH_VH, int)
        self.ReadValue(TAG_QRGRAPH_VK, int)
        self.ReadValue(TAG_QRGRAPH_VL, int)
        self.ReadValue(TAG_QRGRAPH_PSI0, decimal.Decimal)
        self.ReadValue(TAG_QRGRAPH_CHI0, decimal.Decimal)
        self.ReadValue(TAG_QRGRAPH_PHI0, decimal.Decimal)
        self.ReadValue(TAG_QRGRAPH_ROTATEAXIS, int)
        self.ReadValue(TAG_QRGRAPH_NSTEP, int)
        self.ReadValue(TAG_QRGRAPH_INTERVAL, decimal.Decimal)
        self.ReadValue(TAG_QRGRAPH_ETMIN, decimal.Decimal)
        self.ReadValue(TAG_QRGRAPH_ETMAX, decimal.Decimal)

        self.ReadValue(TAG_SAMPLE_NAME, str)
        self.ReadValue(TAG_SAMPLE_LTCSYS)
        self.ReadValue(TAG_SAMPLE_LTCKIND)
        self.ReadValue(TAG_SAMPLE_A, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_B, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_C, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_ALPHA, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_BETA, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_GAMMA, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_UH, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_UK, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_UL, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_VH, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_VK, decimal.Decimal)
        self.ReadValue(TAG_SAMPLE_VL, decimal.Decimal)

        self.SetValue(TAG_SELECTQPINDEX, None)
        return

class AssistantConfError:
    def __init__(self, message):
        self.message = message
        return

    def __str__(self):
        return repr('AssistantConfError: ' + self.message)

    def GetMessage(self):
        return self.message

ASSISTANT_CONF_FILEPATH = u'./AssistantConfs.xml'

class AssistantConfs(object):
    def __init__(self, inst, filename = ASSISTANT_CONF_FILEPATH):
        import os

        filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), filename)

        self.etree = ElementTree()
        self.etree.parse(filepath)

        self.conf = None

        elems = self.etree.findall('./AssistantConf')

        for elem in elems:
            value = elem.get('inst', None)

            if value == None:
                raise AssistantConfError('Cannot find attribute (inst) for tag (AssistantConf) in file (', +filepath + ').')
            elif value == inst:
                self.conf = elem
                break
            pass

        if self.conf == None:
            raise AssistantConfError('Cannot find tag (AssistantConf) or appropriate attribute (inst) for (' + inst + ') in file (', +filepath + ')')
        return

    def GetRegions(self):
        regions = self.conf.find('Regions')
        if regions == None:
            raise AssistantConfError('Cannot find tag (Regions)')
        return regions

def GetElemValue(elem, tag, trans):
    text = elem.findtext(tag, None)
    if text == None:
        raise AssistantConfError('Cannot find tag (' + tag + ')')

    try:
        value = trans(text)
        return value
    except:
        raise AssistantConfError('Find illegal value(' + text + ') for tag (' + tag + ')')
    return

def GetWaveNumberByEnergy(E):
    return math.sqrt(2.0 * define.NEUTRON_MASS * E * define.MILLI_ELECTRON_VOLT) / define.DIRAC_CONST * define.ANGSTROM

class DetectorParamsBase(object):
    def GetAllRegionNames(self):
        items = self.detectorProps.items()
        items.sort(lambda a, b:cmp(a[1].PSD0, b[1].PSD0))

        names = []
        for item in items:
            names.append(item[0])
            pass
        return names

class DNADetectorParams(DetectorParamsBase):
    def __init__(self, elemRegions):
        self.detectorProps = dict()

        elems = elemRegions.findall('Region')

        for elem in elems:
            label = elem.get('label')
            self.detectorProps[label] = DNARegionParams(elem)
            pass
        return

    def SetEnergyRange(self, eimin, eimax):
        self.kimin = GetWaveNumberByEnergy(eimin)
        self.kimax = GetWaveNumberByEnergy(eimax)
        return

# NEWTON METHOD PARAMETERS
NEWTON_MAX_ITERS = 100
NETWON_CRITERIA = 1.0e-12

class DNARegionParams(object):
    def __init__(self, elemRegion):
        d_miller = GetElemValue(elemRegion, 'd_miller', float)
        Rd = GetElemValue(elemRegion, 'Rd', float)
        RA = GetElemValue(elemRegion, 'RA', float)
        y0 = GetElemValue(elemRegion, 'y0', float)
        rR0 = GetElemValue(elemRegion, 'rR0', float)
        dPSD = GetElemValue(elemRegion, 'dPSD', float)
        nPSD = GetElemValue(elemRegion, 'nPSD', int)
        mR = GetElemValue(elemRegion, 'mR', float)
        PSD0 = GetElemValue(elemRegion, 'PSD0', int)
        LPSD = GetElemValue(elemRegion, 'LPSD', float)
        nPx = GetElemValue(elemRegion, 'nPx', int)
        yd0 = GetElemValue(elemRegion, 'yd0', float)
        rdpP = 2

        Rd = define.MILLI * Rd
        RA = define.MILLI * RA
        y0 = define.MILLI * y0
        rR0 = math.radians(rR0)
        dPSD = define.MILLI * dPSD
        LPSD = define.MILLI * LPSD
        yd0 = define.MILLI * yd0
        rPSD = 2.0 * math.atan2(0.5 * dPSD, Rd)
        mR = mR - 0.5
        hPx = LPSD / nPx

        self.d_miller = d_miller
        self.Rd = Rd
        self.RA = RA
        self.y0 = y0
        self.rPSD = rPSD
        self.nPSD = nPSD
        self.mR = mR
        self.PSD0 = PSD0
        self.hPx = hPx
        self.nPx = nPx
        self.yd0 = yd0
        self.rdpP = rdpP

        assert nPx % rdpP == 0, "rdpP must be one of the measure of nP."

        self.phi_s = rR0 + rPSD * (mR - nPSD)
        self.phi_e = rR0 + rPSD * mR

        # Calculate pixel angles

        self.theta_m = [0.0] * (nPx + 1)
        r = y0 / RA

        for Px in xrange(nPx + 1):
            if Px == 0:
                theta_m = 0.0
                pass
            else:
                theta_m = self.theta_m[Px - 1]
                pass

            yP = yd0 - hPx * Px

            for iter in xrange(NEWTON_MAX_ITERS):
                sin_theta_m = math.sin(theta_m)
                cos_theta_m = math.cos(theta_m)

                theta_B = math.acos(r * cos_theta_m)

                cos_theta_B = r * cos_theta_m
                sin_theta_B = math.sin(theta_B)

                Lsa = RA * (sin_theta_B + r * sin_theta_m)

                y0m = Lsa * sin_theta_m
                z0m = Lsa * cos_theta_m

                theta_X = math.asin((y0 - y0m) / RA)

                yd = y0m + (z0m - Rd) / math.tan(theta_B - theta_X)

                dThetaBByTheta_m = r * sin_theta_m / sin_theta_B

                dLsaByTheta_m = RA * cos_theta_B * (1.0 + dThetaBByTheta_m)

                dY0mByTheta_m = Lsa * cos_theta_m + sin_theta_m * dLsaByTheta_m
                dZ0mByTheta_m = -Lsa * sin_theta_m + cos_theta_m * dLsaByTheta_m

                dThetaXByTheta_m = -dY0mByTheta_m / math.sqrt(RA ** 2 - (y0 - y0m) ** 2)

                dYdByTheta_m = dY0mByTheta_m + dZ0mByTheta_m / math.tan(theta_B - theta_X) - \
                    (z0m - Rd) / math.sin(theta_B - theta_X) ** 2 * \
                    (dThetaBByTheta_m - dThetaXByTheta_m)

                dTheta_m = -(yd - yP) / dYdByTheta_m
                theta_m += dTheta_m

                if (max(abs(dTheta_m), abs(yd - yP)) < NETWON_CRITERIA):
                    break;
                pass
            self.theta_m[Px] = theta_m
            pass

        self.theta_m = np.array(self.theta_m)
        self.theta_m_max = max(self.theta_m[0], self.theta_m[self.nPx])
        self.theta_m_min = min(self.theta_m[0], self.theta_m[self.nPx])
        return

class DGTypeDetectorParam(DetectorParamsBase):
    def __init__(self, elemRegions):
        self.detectorProps = dict()

        elems = elemRegions.findall('Region')

        for elem in elems:
            label = elem.get('label')
            self.detectorProps[label] = DGTypeRegionParams(elem)
            pass
        return

    def SetEnergyRange(self, ei, etmin, etmax):
        self.ki = GetWaveNumberByEnergy(ei)
        self.kfmax = GetWaveNumberByEnergy(ei - etmin)
        self.kfmin = GetWaveNumberByEnergy(ei - etmax)
        return

class DGTypeRegionParams(object):
    def __init__(self, elemRegion):
        rR0 = GetElemValue(elemRegion, 'rR0', float)
        nPSD = GetElemValue(elemRegion, 'nPSD', int)
        PSD0 = GetElemValue(elemRegion, 'PSD0', int)
        mR = GetElemValue(elemRegion, 'mR', float)
        dPSD = GetElemValue(elemRegion, 'dPSD', float)
        yd0 = GetElemValue(elemRegion, 'yd0', float)
        LPSD = GetElemValue(elemRegion, 'LPSD', float)
        nPx = GetElemValue(elemRegion, 'nPx', int)
        L2 = GetElemValue(elemRegion, 'L2', float)
        rdpP = 1

        rR0 = math.radians(rR0)
        mR = mR - 0.5
        dPSD = dPSD * define.MILLI
        LPSD = LPSD * define.MILLI
        yd0 = yd0 * define.MILLI
        L2 = L2 * define.MILLI
        hPx = LPSD / nPx

        self.rR0 = rR0
        self.nPSD = nPSD
        self.PSD0 = PSD0
        self.mR = mR
        self.dPSD = dPSD
        self.hPx = hPx
        self.nPx = nPx
        self.yd0 = yd0
        self.L2 = L2

        self.rdpP = rdpP

        assert nPx % rdpP == 0, "rdpP must be one of the measure of nP."

        self.theta_m_min_0 = math.atan2(yd0 - LPSD, L2)
        self.theta_m_max_0 = math.atan2(yd0, L2)

        self.phi_s = rR0 + math.atan2(dPSD * (mR - nPSD), L2)
        self.phi_e = rR0 + math.atan2(dPSD * mR, L2)
        return
