from __future__ import print_function
import numpy

import os
import time


import Manyo as mm
import Manyo.Utsusemi as mu
import threading


class D4Mat(object):
    """
    This is python lapping for commands treating D4Matrix on Manyo-lib
    Required Manyo-lib revision is 2250 or later.
    [inamura 160227] : bugfix of wrong path treatment on Windows Environment
    """

    def __init__(self, Ef=0.0):
        """
        Constructor
        """
        self.SlicedECM = None
        self.D4Mat = mu.UtsusemiD4Matrix()
        if Ef > 0.0:
            self.D4Mat.SetEf(Ef)
        self.CommentHead = "D4Mat >>>"
        self.progress = 0
        self.progress_max = 0
        self.AddedFileList = []

    def new(self, A1range, A2range, A3range, A4range, titles, units, paramfilepath):
        """
        makes new matrix

        @param A1range (list) [A1min, A1max, dA1]
        @param A2range (list) [A2min, A2max, dA2]
        @param A3range (list) [A3min, A3max, dA3]
        @param A4range (list) [A4min, A4max, dA4]
        @param titles  (list of string) [ AX1_title, AX2_title, AX3_title, AX4_title ]
        @param units  (list of string) [ AX1_unit, AX2_unit, AX3_unit, AX4_unit ]
        @param datadir (string)
        @param paramfile (string)
        @return None
        """

        if not (isinstance(A1range, list) and len(A1range) == 3):
            print(self.CommentHead + "Invalid parameter about Q1 range. ")
            raise
        if not (isinstance(A2range, list) and len(A2range) == 3):
            print(self.CommentHead + "Invalid parameter about Q2 range. ")
            raise
        if not (isinstance(A3range, list) and len(A3range) == 3):
            print(self.CommentHead + "Invalid parameter about Q3 range. ")
            raise
        if not (isinstance(A4range, list) and len(A4range) == 3):
            print(self.CommentHead + "Invalid parameter about hw range. ")
            raise
        if not (isinstance(titles, list) and len(titles) == 4):
            print(self.CommentHead + "Invalid parameter about titles. ")
            raise
        if not (isinstance(units, list) and len(units) == 4):
            print(self.CommentHead + "Invalid parameter about units. ")
            raise
        if not isinstance(paramfilepath, str):
            print(self.CommentHead + "Invalid parameter about param file. ")
            raise

        a1v = mm.ListToDoubleVector(A1range)
        a2v = mm.ListToDoubleVector(A2range)
        a3v = mm.ListToDoubleVector(A3range)
        a4v = mm.ListToDoubleVector(A4range)
        tlv = mm.MakeStringVector()
        unv = mm.MakeStringVector()
        for title, unit in zip(titles, units):
            tlv.append(str(title))
            unv.append(str(unit))
        # [inamura 160227]-->  # for Windows Environment
        # datadir = os.path.dirname( paramfilepath ) + "/"
        # paramfile = paramfilepath.replace( datadir, "" )
        datadir = os.path.dirname(paramfilepath)
        paramfile = os.path.basename(paramfilepath)
        # <--[inamura 160227]
        self.D4Mat.AllocateNewMat(
            a1v, a2v, a3v, a4v, tlv, unv, datadir, paramfile)

        if not self.D4Mat.isStatusOK():
            print(self.CommnetHead + "Fail to allocate New matrix.. ")
            raise

    def open(self, paramfilepath):
        """
        Opens existed Matrix data

        @param paramfilepath (string) full path to parameter xml file of D4Matrix
        @retval -1  Error happened
        @retval  0  No trouble
        """
        if not os.path.exists(paramfilepath):
            print(self.CommentHead + "Cannot find file " + paramfilepath)
            return -1

        # [inamura 160227]-->  # for Windows Environment
        # datadir = os.path.dirname( paramfilepath ) + "/"
        # filename = paramfilepath.replace( datadir, "" )
        datadir = os.path.dirname(paramfilepath)
        filename = os.path.basename(paramfilepath)
        # <--[inamura 160227]
        print("{} Data dir = {}".format(self.CommentHead, datadir))
        print("{} param_file = {}".format(self.CommentHead, filename))
        self.D4Mat.OpenMat(datadir, filename)

        if not self.D4Mat.isStatusOK():
            print(self.CommentHead + "Fail to open Matrix.. ")
            return -1

        return 0

    def close(self):
        """
        Closes opened Matrix data

        @param None
        @retval None
        """

        self.D4Mat.CloseMat()

    def rebin(self, paramfile1, paramfile2):
        """
        Rebins a matrix to the other matrix

        @param paramfile1    full path to param xml file of original matrix
        @param paramfile2    full path to param xml file of matrix to be rebined
        @retval  -1   Error happened
        @retval   0   No trouble
        """
        if not os.path.exists(paramfile1):
            print(self.CommentHead + "Cannot find file " + paramfile1)
            return -1
        if not os.path.exists(paramfile2):
            print(self.CommentHead + "Cannot find file " + paramfile2)
            return -1

        # [inamura 160227]-->  # for Windows Environment
        # datadir1 = os.path.dirname( paramfile1 ) + "/"
        # filename1 = paramfile1.replace( datadir1, "" )
        # datadir2 = os.path.dirname( paramfile2 ) + "/"
        # filename2 = paramfile2.replace( datadir2, "" )
        datadir1 = os.path.dirname(paramfile1)
        filename1 = os.path.basename(paramfile1)
        datadir2 = os.path.dirname(paramfile2)
        filename2 = os.path.basename(paramfile2)
        # <--[inamura 160227]

        print(self.CommentHead + "Rebin from:")
        print(self.CommentHead + "      Data dir = {}".format(datadir1))
        print(self.CommentHead + "      param_file = {}".format(filename1))
        print(self.CommentHead + "Rebin to:")
        print(self.CommentHead + "      Data dir = {}".format(datadir2))
        print(self.CommentHead + "      param_file = {}".format(filename2))

        self.D4Mat.CloseMat()
        self.D4Mat.AddMatToMat(str(datadir1), str(filename1), str(datadir2), str(filename2))

        if not self.D4Mat.isStatusOK():
            print(self.CommentHead + "Fail to rebin Matrix.. ")
            raise

        return 0

    def isOpened(self):
        """
        Returns True if any matrix data has been opened.
        @param None
        @retval True   matrix data has been opened.
        @retval False  not opened.
        """
        return self.D4Mat.isMatOpened()

    def putOpenedPath(self):
        """
        Returns path to parameter xml file of opened D4Matrix

        @param None
        @retval tupple of string ( directory, xml_file_name )
        """
        ret = self.D4Mat.PutOpenedDataPath()
        if ret.size() < 2:
            return None

        return (ret[0], ret[1])

    def setTitles(self, titles, isSaved=False):
        """
        Sets other titles

        @param titles ( list of string ) [AX1 title,AX2 title,AX3 title,AX4 title]
        @param isSaved  (bool) If True, rewrite parameter xml file
        @return None
        """
        if isinstance(titles, list) and len(titles) == 4:
            pass
        else:
            print(self.CommentHead + " First Argument must be a list of 4 strings. ")
            raise
        if not isinstance(isSaved, bool):
            print(self.CommentHead + " Second Argument must be bool. ")
            raise

        tv = mm.MakeStringVector()
        for title in titles:
            tv.append(str(title))
        self.D4Mat.SetAxTitles(tv, isSaved)

        # for i,title in enumerate(titles):
        #    self.D4Mat.SetAxTitle( i, str(title) )

    def setUnits(self, units, isSaved=False):
        """
        Sets other units

        @param units ( list of string ) [AX1 unit,AX2 unit,AX3 unit,AX4 unit]
        @param isSaved  (bool) If True, rewrite parameter xml file
        @return None
        """
        if isinstance(units, list) and len(units) == 4:
            pass
        else:
            print(self.CommentHead + " First Argument must be a list of 4 strings. ")
            raise
        if not isinstance(isSaved, bool):
            print(self.CommentHead + " Second Argument must be bool. ")
            raise
        tv = mm.MakeStringVector()
        for unit in units:
            tv.append(str(unit))
        self.D4Mat.SetAxUnits(tv, isSaved)

    def addFromDirectry(self, filedir):
        """
        Adds data from files by given directory

        @param filedir (string) path to directory stored text file
        @return List of file path to be loaded
        """
        if not os.path.exists(filedir):
            print(self.CommnetHead + "Cannot find such directory, {}".format(filedir))
            raise

        filelist = os.listdir(filedir)
        filelist.sort()

        num_files = len(filelist)

        self.AddedFileList = []
        self.progress = 0
        self.progress_max = num_files
        print("self.progress_max=", self.progress_max)
        for filename in filelist:
            filepath = os.path.join(filedir, filename)
            msg = self.CommentHead + "filepath = " + filepath
            msg += " :%d/%d" % ((self.progress + 1), num_files)
            print(msg)
            if filepath is not None:
                time.sleep(0.2)
                self.AddedFileList.append(self.addFromFile(filepath))
            self.progress += 1

        self.progress += 1

        print(self.CommentHead + "--------------------------------------------------")
        print(self.CommentHead + " Finished : Load vbin files")
        print(self.CommentHead + " in %s" % (filedir))
        print(self.CommentHead + "--------------------------------------------------")

        return self.AddedFileList

    def addFromFile(self, filename):
        """
        Adds data from a file

        @param filename (string) path to data file
        @return file path to be loaded
        """
        st = time.time()
        try:
            if filename[-5:] == ".vbin":
                self.D4Mat.AddToMatFromBin(str(filename))
            elif filename[-4:] == ".txt":
                self.D4Mat.AddToMatFromText(str(filename))
            else:
                print(self.CommentHead + " File name is invalid. ")
                raise

            if not self.D4Mat.isStatusOK():
                print(self.CommnetHead + "Fail to add data from file.. ")
                raise

        except:
            print(self.CommentHead + "Cannot read file : {}".format(filename))
            filename = None
        print(self.CommentHead + "time of cost = {}".format(time.time() - st))
        return filename

    def subtractText(self, filename):
        """
        Subtracts data of text file from Matrix

        @param filename (string) path to data of text file
        @retval -1  Error happened
        @retval  0  No trouble
        """

        try:
            self.D4Mat.SubtractFromMatByText(str(filename))
            if not self.D4Mat.isStatusOK():
                print(self.CommnetHead + "Fail to subtract text file data from D4Mat.. ")
                return -1
        except:
            print(self.CommentHead + "Fail to open text file..")
            return -1

        return 0

    def sliceMat(self, A1range, A2range, A3range, A4range, type_axes, foldings):
        """
        Slices Matrix

        @param A1range    (list of float) range of AX1
        @param A2range    (list of float) range of AX2
        @param A3range    (list of float) range of AX3
        @param A4range    (list of float) range of AX4
        @param type_axes  (list of string) type of axes 'X','Y' or 'T'
        @param foldings   (list of int )
        @return None
        """

        if not (isinstance(A1range, list) and (len(A1range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q1 range. ")
            raise
        if not (isinstance(A2range, list) and (len(A2range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q2 range. ")
            raise
        if not (isinstance(A3range, list) and (len(A3range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q3 range. ")
            raise
        if not (isinstance(A4range, list) and (len(A4range) == 2)):
            print(self.CommnetHead + "Invalid parameter about hw range. ")
            raise
        if not (isinstance(type_axes, list) and (len(type_axes) == 4)):
            print(self.CommnetHead + "Invalid type_axes. ")
            raise

        a1v = mm.ListToDoubleVector(A1range)
        a2v = mm.ListToDoubleVector(A2range)
        a3v = mm.ListToDoubleVector(A3range)
        a4v = mm.ListToDoubleVector(A4range)

        cnt_Ax = {'X': 0, 'Y': 0, 'T': 0}
        def_axes = mm.MakeStringVector()
        isValid = True
        for Axtype in type_axes:
            if Axtype == 'X':
                cnt_Ax['X'] += 1
            elif Axtype == 'Y':
                cnt_Ax['Y'] += 1
            elif Axtype == 'T':
                cnt_Ax['T'] += 1
            else:
                break
            def_axes.append(Axtype)

        dim = 0
        if (cnt_Ax['X'] == 1) and (cnt_Ax['Y'] == 1) and (cnt_Ax['T'] == 2):
            dim = 2
        elif (cnt_Ax['X'] == 1) and (cnt_Ax['T'] == 3):
            dim = 1
        else:
            # if (cnt_Ax['X']!=1) or (cnt_Ax['Y']!=1) or (cnt_Ax['T']!=2):
            print(self.CommentHead + "Invalid argument type_axes.")
            isValid = False

        fld = mm.ListToDoubleVector(foldings)  # [inamura 160616]
        if isValid:
            if dim == 2:
                self.D4Mat.SliceMat(a1v, a2v, a3v, a4v, def_axes, fld)
            else:
                self.D4Mat.SliceMat1d(a1v, a2v, a3v, a4v, def_axes, fld)
            if not self.D4Mat.isStatusOK():
                print(self.CommnetHead + "Fail to slice data.. ")
                raise
            self.SlicedECM = self.D4Mat.PutSlicedECA()
        else:
            raise

    # [inamura 170227]-->
    def sliceMat3d(self, A1range, A2range, A3range, A4range, type_axes, foldings, name_axes):
        """
        Slices Matrix 3D

        @param A1range    (list of float) range of AX1
        @param A2range    (list of float) range of AX2
        @param A3range    (list of float) range of AX3
        @param A4range    (list of float) range of AX4
        @param type_axes  (list of string) type of axes 'X','Y','Z' or 'T'
        @param foldings   (list of int )
        @return None
        """

        if not (isinstance(A1range, list) and (len(A1range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q1 range. ")
            raise
        if not (isinstance(A2range, list) and (len(A2range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q2 range. ")
            raise
        if not (isinstance(A3range, list) and (len(A3range) == 2)):
            print(self.CommnetHead + "Invalid parameter about Q3 range. ")
            raise
        if not (isinstance(A4range, list) and (len(A4range) == 2)):
            print(self.CommnetHead + "Invalid parameter about hw range. ")
            raise
        if not (isinstance(type_axes, list) and (len(type_axes) == 4)):
            print(self.CommnetHead + "Invalid type_axes. ")
            raise

        a1v = mm.ListToDoubleVector(A1range)
        a2v = mm.ListToDoubleVector(A2range)
        a3v = mm.ListToDoubleVector(A3range)
        a4v = mm.ListToDoubleVector(A4range)

        cnt_Ax = {'X': 0, 'Y': 0, 'Z': 0, 'T': 0}
        def_axes = mm.MakeStringVector()
        isValid = True
        for Axtype in type_axes:
            if Axtype == 'X':
                cnt_Ax['X'] += 1
            elif Axtype == 'Y':
                cnt_Ax['Y'] += 1
            elif Axtype == 'Z':
                cnt_Ax['Z'] += 1
            elif Axtype == 'T':
                cnt_Ax['T'] += 1
            else:
                break
            def_axes.append(Axtype)

        if (cnt_Ax['X'] != 1) or (cnt_Ax['Y'] != 1) or (cnt_Ax['Y'] != 1) or (cnt_Ax['T'] != 1):
            print(self.CommentHead + "Invalid argument type_axes.")
            isValid = False

        fld = mm.ListToDoubleVector(foldings)
        keys = mm.MakeStringVector(3)
        for i in range(3):
            keys[i] = str(name_axes[i])

        if isValid:
            try:
                del self.SlicedECM
            except:
                pass
            self.SlicedECM = mm.ElementContainerMatrix()
            self.D4Mat.SliceMat3D(self.SlicedECM, a1v, a2v,
                                  a3v, a4v, def_axes, fld, keys)
            if not self.D4Mat.isStatusOK():
                print(self.CommnetHead + "Fail to slice data.. ")
                raise
        else:
            raise
        # <--[inamura 170227]

    def loadVisContParams(self, filename=""):
        """
        Pick up information of Axis from VisCont parameter file

        @param filename   path to XML parameter file produced by VisualCont2
        @return  tupple of list ( list_of_ranges_for_axes, list_of_titles_for_axes )
        """
        if not os.path.exists(filename):
            ss = self.CommentHead + (" Cannot find XML file({})".format(filename))
            raise UserWarning("FileIOError::{}".format(ss))

        vcp = mu.UtsusemiSqeCalcXtalParams()
        if vcp.LoadFile(filename):
            pass
        else:
            msg = self.CommentHead + \
                ("Failed load XtalParam file, may be this is not XtalParam ({})".format(
                    filename))
            raise UserWarning(msg)

        axrange_list = []
        title_list = []
        unit_list = []

        ax_type = vcp.PutAxType()
        ax_labels = vcp.PutViewAxesLabels()
        ax_units = vcp.PutViewAxesUnits()
        for i in range(4):
            ax_range = vcp.PutAxRange(i)
            r_axis = []
            for j in range(ax_range.size()):
                r_axis.append(ax_range[j])
            axrange_list.append(r_axis)
            title_list.append(ax_labels[i])
            unit_list.append(ax_units[i])

        return (axrange_list, title_list, unit_list)

    def makeNewMatFromVisContParams(self, VisContParamFile="", D4MatParamFile=""):
        """
        Makes new D4Matrix data file using parameters fo VisContParam

        @param VisContParamFile   path to XML parameter file produced by VisualCont2
        @param D4MatParamFile     path to XML parameter file for D4Matrix
        @return None
        """
        (range_list, title_list) = self.loadVisContParams(VisContParamFile)

        self.new(range_list[0], range_list[1], range_list[2],
                 range_list[3], title_list, D4MatParamFile)
