import pytest
import preciso
import numpy 
import pandas
from pathlib import Path
import h5py
import sys
import os

class Tests:
    '''Class for testing preciso results against a reference

    Parameters
    -----------
    
        test_file : str or pathlib.Path
            The name or path of the HDF5 test file
    
    Attributes
    -----------

        tolerances : dict
            The relative margin of error accepted for each parameter.
        test_file : str
            The name or path of the HDF5 test file
        REF : HDF5 file
            The openened  HDF5 file, gathering the reference results
        simVal : preciso.results.Results
            The Result object generated by running preciso with the input and the parameters specified in the HDF5 test file
    
    '''


    def __init__(self, test_file): 

        #Default tolerances. THESE CAN'T BE 0, COMPARISONS ARE STRICT. If specific tolerances need to be added, make sure to specify those BEFORE the matching patern.
        self.tolerances = {'t[s]' : sys.float_info.epsilon , 'T[K]' : 0.01, 'X_' : 0.01, 'rmean_' : 0.01, 'r*_' : 0.01, 'fv_' : 0.01, 'N_' : 0.01}

        REF = h5py.File(test_file, 'r')

        #Generating temporary input file
        with open('input_temp.input', 'w') as f:
            f.write(REF.attrs['input'])

        #Generating simulation results
        self.simVal = preciso.runSimulation('input_temp.input', REF.attrs['temp'], REF.attrs['write_limit'], REF.attrs['nodes'], REF.attrs['n_samples'], REF.attrs['debug'])

        self.test_file = test_file

        self.REF_FILE = REF


    def __del__(self):
        '''Destructor of class Tests, closes the HDF5 file and removes the temporary input file'''
        os.remove('input_temp.input')
        self.REF_FILE.close()
    
    def mean_error(self, sim, Ref):
        '''Calculates the mean relative error of sim compared to Ref.

            Parameters
            -----------

                sim, Ref: 1 dimensionnal array-likes
                    The datasets to compare.
            
            Returns
            --------
            
                error : float
                    The mean error of sim values compared to Ref
            
        '''
        error = sum([abs(sim[i]-Ref[i])/max(Ref[i],sys.float_info.epsilon)  for i in range(len(Ref))])/len(Ref)
        return error

    def reformat(self, SIM, REF):
        '''Formats the REF frame to fit the SIM time values with linear interpolation between those. Restrict simulation data to non simulation information
        
            Parameters
            -----------

                SIM, REF : pandas DataFrames
                    The datasets to format
            
            Returns
            --------

                SIM : pandas DataFrame
                    The argument dataframe, where simulation information labels (for now the time steps and class count) have been removed
                REF_FORMATED : pandas DataFrame
                    The formated reference dataframe based on REF, with its time values and labels matching those of SIM (after computation)

        '''
        #Removing the simulation data
        labels = []
        for l in SIM.columns:
            if l[:2] != 'dt' and l[:6] != 'Nclass':
                labels.append(l)
        SIM, REF_FORMATED = SIM.loc[:, labels], SIM.loc[:, labels]
        iSim, iRef = 1, 0
        while iSim < len(SIM.index) - 1:
            while REF.loc[iRef+1, 't[s]'] < SIM.loc[iSim, 't[s]']:
                iRef += 1
            REF_FORMATED.iloc[iSim, 1:] = (REF.iloc[iRef, 1:]*(REF.loc[iRef+1, 't[s]']-SIM.loc[iSim, 't[s]']) + REF.iloc[iRef+1, 1:]*(SIM.loc[iSim, 't[s]']-REF.loc[iRef, 't[s]']))/(REF.loc[iRef+1, 't[s]'] - REF.loc[iRef, 't[s]'])
            iSim += 1
        return (SIM, REF_FORMATED)

    def comp_DF(self, SIM, REF):
        '''Compare 2 dataframes of the same format (see function format(self, SIM, REF))

        Parameters
        -----------

            SIM, REF: pandas.DataFrame
                The compared dataframes. REF is the reference for the mean error. SIM and REF must share the same first column for t[s].
        
        Returns
        ----------

            ERR: pandas.DataFrame
                A dataframe with the mean relative error between the DataFrames, for each column.
                For each of REF attributes, ERR has a boolean attribute asserting if it is defined in SIM and if the values match.
        
        '''
        ERR = pandas.DataFrame(data = [len(REF.columns)*[0]], index = ["Relative Error"], columns = REF.columns, dtype = float)
        for c in REF.columns:
            ERR.loc[:, c] = Tests.mean_error(self, SIM.loc[:, c].values, REF.loc[:, c].values)
            #By comparing the first column (t[s], which serves as an index) of the datasets, we ensure that the dataFrames are well formated
        return ERR

    def match_tol(self, label):
        '''Matches a label with the corresponding tolerance value.

            Parameters
            -----------

                label : str
                    The column label to match
            
            Returns
            ---------

                    If a label patern is found in self.tolerances.keys(), returns the corresponding tolerance.
                    Otherwise, returns None.

        '''
        for k in self.tolerances.keys():
            if label[:len(k)] == k:
                return self.tolerances[k]
        return None

