#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Utilities using on Chopper spectrometer in MLF
: 4SEASONS, AMATERAS and so on.
"""
from __future__ import print_function
from __future__ import absolute_import
import Manyo as mm
import Manyo.Utsusemi as m
from . import UtilsOnChoppers as UOC
from numpy import array, exp


class MonitorAnalysis:
    '''
    Monitor Dataからの情報の取り込み
    '''
    ################################

    def __init__(self, ECMtx=None, Ei=None, T0shift=0.0, debug=0):
        '''
        Constructor
        @param ECMtx (ElementContainerMatrix) データ。モニターカウンターのデータは
                     ECMtx(0)(0)から。
        @param Ei (Double) ユーザーが意図していた入射エネルギー
        @param T0shift (Double) TOF原点シフト量[micro-sec]
        @param debug (UInt4) debug用表示 OFF:0, ON:1
        '''
        if (ECMtx is None):
            print("You need ElementContainerMatrix of data as parameter")
            return
        SIH = mm.SearchInHeader(ECMtx)
        SIH.SearchArray("TYPE", "MONITOR")
        vec_index = SIH.PutResultIndex(0)
        if vec_index.size() == 0:
            mon_index = -1
        else:
            mon_index = vec_index[0]
        print("Monitor Array index = {}".format(mon_index))
        del SIH

        if mon_index == -1:
            print("This Element Container Matrix has no Monitor data.")
            return

        if (Ei is None):
            Ei = ECMtx(mon_index).PutHeader().PutDouble("SETEI")

        self.est_Ei = Ei
        """
        WhatIsThis = ECMtx(mon_index).PutHeader().PutString("TYPE")
        if (WhatIsThis != "MONITOR"):
            print "You need data from Monitor Counter at index 0 on ECmatrix"
            return
        """
        self.ECAry = ECMtx.Put(mon_index)
        # self.Inst = ECMtx.PutHeader().PutString("INSTRUMENT")
        self.RunNumber = ECMtx.PutHeader().PutInt4("RUNNUMBER")
        self.NumOfMonitor = self.ECAry.PutTableSize()
        self.LenOfMonitor = self.PutLengthOfMonitorFromDb()
        self.T0shift = T0shift

        self.incident_v = None
        self.incident_Ei = None
        self.OriginOfTOF = None
        self.MonitorCount = None
        self.delta_Ei = None
        self.PeakPosi = None
        self.PeakWidth = None

        self.Debug = debug
        # self.SearchPeakPosition()
        # self.CalcMonitorCount()

    ################################

    def PutLengthOfMonitorFromDb(self):
        '''
        Return distances between moderator and monitors
        現在はElementContainerのHeaderから読み取っている。
        First and second value in returned list are used
        for the calculation of incident energy.
        @retval リスト [Double, Double,..] The list of Length between moderator to Monitors
                                          in this instrument. [mm]
        '''
        LenOfMonitor = []
        for i in range(self.NumOfMonitor):
            LenOfMonitor.append(self.ECAry.Put(i).PutHeader().PutDouble("LM"))
            # LenOfMonitor = [8000.0,12000.0]

        return LenOfMonitor

    ################################
    def SearchPeakPosition(self, functype=0):
        '''
        Search Peak positions and these width

        @param functype (UInt4) To specify the fitting function.
        @retval None
        '''

        self.PeakPosi = []
        self.PeakWidth = []
        for i in range(self.NumOfMonitor):
            mon_ec = self.ECAry.Put(i)
            mon_id = mon_ec.PutHeader().PutInt4("MonitorId")
            print("Monitor ID = {}".format(mon_id))
            # This area is used for some corrections on monitor detector if you need.
            # Background, Energy dependence of efficiency, or TOF origin, and so on
            if (functype == 0):
                (peak_posi, peak_width) = self._PeakParamType1(mon_ec)
            else:
                peak_posi = None
                peak_width = None

            self.PeakPosi.append(peak_posi)
            self.PeakWidth.append(peak_width)

    ################################

    def _PeakParamType1(self, EC):
        '''
        Return parameter about fitted peak with Gaussian
        Returned peak_posi parameter is to be calclated for incident energy,
        and returned peak_width is for monitor counts.
        @param EC (ElementContainer) ElementContainer of Monitor Detector
        @retval tuple (double,double)
        '''
        u = UOC.UnitOnChoppers()

        vec_x = EC.PutX()
        vec = EC.PutY()
        init_ii = 0.0
        index = 0
        for i in range(len(vec)):
            if vec[i] > init_ii:
                init_ii = vec[i]
                index = i
        init_cc = vec_x[index]
        """
        init_ii = array(EC.PutY()).max()
        if self.Debug==1: print "init_ii",init_ii

        (ext_v, unit) = u.ToVelocity(self.est_Ei,"meV")
        if self.Debug==1: print "velocity = ",ext_v,",LM=",EC.PutHeader().PutDouble("LM")
        init_cc = EC.PutHeader().PutDouble("LM")/(ext_v/1000.0) # [mm]/[m/s]/1000.0 => [micro s]
        """
        # params = [0.0, init_ii, init_cc, 10.0] # [background, Intensity, Center Posi, gamma] of gaussians
        # [background,slope,Intensity,Center Posi,gamma] of gaussians
        params = [0.0, -1.0, init_ii, init_cc, 10.0]
        if self.Debug == 1:
            print("param for fit = {}".format(params))

        ret = self._PeakFitType1(EC, params)
        if self.Debug == 1:
            print("Peak Param = {}".format(ret))
        peak_posi = ret[2]
        peak_width = ret[3]
        return (peak_posi, peak_width)

    ################################

    def _PeakFitType1(self, EC, params):
        '''
        Do fitting on the peak in tof
        @param EC (ElementContainer) ElementContainer of Monitor Detector
        @retval list of results
        '''
        fit = UOC.FitOnChoppers()

        tof = EC.PutX()
        cnt = EC.PutY()
        xx = []
        max_yy = 0
        max_i = 0
        for i in range(tof.size() - 1):
            xx.append((tof[i] + tof[i + 1]) / 2.0)
            if cnt[i] > max_yy:
                max_yy = cnt[i]
                max_i = i
        if max_yy == 0:
            raise UserWarning("FittingError")

        xx = array(xx[(max_i - 10):(max_i + 11)])
        yy = array(cnt[(max_i - 10):(max_i + 11)])

        bb = fit.Parameter(params[0])
        ll = fit.Parameter(params[1])
        ii = fit.Parameter(params[2])
        cc = fit.Parameter(params[3])
        ss = fit.Parameter(params[4])

        def fit_func_gauss(x):
            return bb() + ll() * x + ii() * exp(-0.50 * ((x - cc()) / ss())**2)

        ret = fit.fit(fit_func_gauss, [bb, ll, ii, cc, ss], yy, xx)
        print("bb = {} \n {}".format(bb(), bb.value_err))

        return [bb(), ii(), cc(), ss()]

    ################################
    def _CalcMonitorCount(self):
        '''
        Calculate Monitor Count
        @retval
        '''
        self.MonitorCount = []
        for i in range(self.NumOfMonitor):
            tof = self.ECAry(i).PutX()
            int = self.ECAry(i).PutY()
            sum = 0.0
            limit_min = (self.PeakPosi[i] - self.PeakWidth[i])
            limit_max = (self.PeakPosi[i] + self.PeakWidth[i])
            for j in range(tof.size()):
                if (tof[j] > limit_min) and (tof[j] < limit_max):
                    sum += int[j]

            self.MonitorCount.append(sum)

    ################################
    def _CalcIncidentInform(self):
        '''
        Calculate some information of incident neutron
        Calculate incident velocity, incident energy and the origin of TOF.
        Each Value is stored as taple ( value(double),unit(string) ).

        '''

        u = UOC.UnitOnChoppers()
        if len(self.PeakPosi) == 2:
            vi = (self.LenOfMonitor[1] - self.LenOfMonitor[0]) / (self.PeakPosi[1] - self.PeakPosi[0]) * 1000.0  # [m/s]
        else:
            vi = self.LenOfMonitor[0] / (self.PeakPosi[0] - self.T0shift) * 1000.0
        self.incident_v = (vi, "m/s")
        self.incident_Ei = u.ToEnergy(vi, self.incident_v[1])
        OriginOfTOF = self.PeakPosi[0] - self.LenOfMonitor[0] / vi * 1000.0
        self.OriginOfTOF = (OriginOfTOF, "microsecond")

        v = self.LenOfMonitor[0] / (self.PeakPosi[0] - self.PeakWidth[0]) * 1000.0
        max_Ei, unit = u.ToEnergy(v, self.incident_v[1])
        v = self.LenOfMonitor[0] / (self.PeakPosi[0] + self.PeakWidth[0]) * 1000.0
        min_Ei, unit = u.ToEnergy(v, self.incident_v[1])
        self.delta_Ei = ((max_Ei - min_Ei), unit)

    ################################
    def PutIncidentEnergy(self):
        '''
        Return incident energy and its unit.

        @retval tuple (Double, "meV")
        '''
        if (self.incident_Ei is None):
            self._CalcIncidentInform()

        return self.incident_Ei

    ################################
    def PutIncidentEnergyWidth(self):
        '''
        Return incident energy and its unit.

        @retval tuple (Double, "meV")
        '''
        if (self.delta_Ei is None):
            self._CalcIncidentInform()

        return self.delta_Ei

    ################################
    def PutIncidentVelocity(self):
        '''
        Return incident velocity of neutrons ant its unit.

        @retval tuple (Double, "m/s")
        '''
        if (self.incident_v is None):
            self._CalcIncidentInform()

        return self.incident_v

    ################################
    def PutOriginOfTOF(self):
        '''
        Return TOF origin ant its unit.

        @retval tuple (Double, "microsecond")
        '''
        if (self.OriginOfTOF is None):
            self._CalcIncidentInform()

        return self.OriginOfTOF

    ################################
    def PutMonitorCount(self, monitorId=0):
        '''
        Return monitor counts
        @param monitorId (Int4) index of monitors
        @retval (double) monitor counts
        '''
        if (self.MonitorCount is None):
            self._CalcMonitorCount()

        return self.MonitorCount[monitorId]

    ################################
    def PutPeakPosi(self, monitorId=0):
        '''
        Return Peak position in TOF
        @param monitorId (Int4) index of monitors
        @retval (double) Peak position in TOF
        '''
        if (self.PeakPosi is None):
            self.SearchPeakPosition()

        return self.PeakPosi[monitorId]

    ################################
    def PutPeakWidth(self, monitorId=0):
        '''
        Return peak width in TOF
        @param monitorId (Int4) index of monitors
        @retval (double) Peak width in TOF
        '''
        if (self.PeakWidth is None):
            self.SearchPeakPosition()

        return self.PeakWidth[monitorId]
