from copy import copy
import numpy as np

class CalibrationRange(object):
    """ The CalibrationRange object allows the range of allowable parameters to be specified and
    used in checking and reporting of the results.
    """
    def __init__(self, parameter_name, value, checkfunction=None, units='', model_name='',
                fail_msg='',fail_dict={}, pass_msg='', pass_dict={}, description_msg='', description_dict={}):
        self.parameter_name = parameter_name
        self.value = value
        self.checkfunction = checkfunction
        self.units = units
        self.model_name = model_name
        self.fail_msg = (copy(fail_msg), copy(fail_dict))
        self.pass_msg = (copy(pass_msg), copy(pass_dict))
        self.description_msg = (copy(description_msg), copy(description_dict))

    def check(self,parameters):
        """Method for checking whether parameters satisfy the calibration range."""
        if self.parameter_name in parameters:
            return self.checkfunction(self.value,parameters[self.parameter_name])
        else:
            return None

    def string(self,parameters,report_nonexistance=True):
        """Returns a string statement of the calibration check"""
        if type(parameters) == type(None):
            msgdict = self.description_msg[1]
            if type(self.value) == float or type(self.value) == int:
                msgdict['calib_val'] = self.value
            elif type(self.value) == list or type(self.value) == tuple or type(self.value) == np.ndarray:
                for i in range(len(self.value)):
                    msgdict['calib_val'+str(i)] = self.value[i]
            if 'param_name' not in msgdict:
                msgdict['param_name'] = self.parameter_name
            if 'units' not in msgdict:
                msgdict['units'] = self.units
            if 'model_name' not in msgdict:
                msgdict['model_name'] = self.model_name
            return self.description_msg[0].format(**msgdict)
        else:
            check = self.check(parameters)
            if check == True:
                msgdict = self.pass_msg[1]
                msgdict['param_val'] = parameters[self.parameter_name]
                if type(self.value) == float or type(self.value) == int:
                    msgdict['calib_val'] = self.value
                elif type(self.value) == list or type(self.value) == tuple or type(self.value) == np.ndarray:
                    for i in range(len(self.value)):
                        msgdict['calib_val'+str(i)] = self.value[i]
                if 'param_name' not in msgdict:
                    msgdict['param_name'] = self.parameter_name
                if 'units' not in msgdict:
                    msgdict['units'] = self.units
                if 'model_name' not in msgdict:
                    msgdict['model_name'] = self.model_name
                return self.pass_msg[0].format(**msgdict)
            elif check == False:
                msgdict = self.fail_msg[1]
                msgdict['param_val'] = parameters[self.parameter_name]
                if type(self.value) == float or type(self.value) == int:
                    msgdict['calib_val'] = self.value
                elif type(self.value) == list or type(self.value) == tuple or type(self.value) == np.ndarray:
                    for i in range(len(self.value)):
                        msgdict['calib_val'+str(i)] = self.value[i]
                if 'param_name' not in msgdict:
                    msgdict['param_name'] = self.parameter_name
                if 'units' not in msgdict:
                    msgdict['units'] = self.units
                if 'model_name' not in msgdict:
                    msgdict['model_name'] = self.model_name
                return self.fail_msg[0].format(**msgdict)
            else:
                if report_nonexistance == True:
                    return "A value for {} was not provided.".format(self.parameter_name)
                else:
                    return ''

# ------------- DEFAULT CALIBRATIONRANGE OBJECTS --------------- # FIND CALIBRATION RANGES

def crf_EqualTo(calibval,paramval):
    return calibval == paramval
crmsg_EqualTo_pass = "The {param_name} ({param_val:.1f} {units}) is equal to {calib_val:.1f} {units} as required by the calibration range of the {model_name} model. "
crmsg_EqualTo_fail ="{param_name} ({param_val:.1f} {units}) is outside the calibration range of the {model_name} model ({calib_val:.1f} {units}). "
crmsg_EqualTo_description = "The {model_name} model is calibrated for {param_name} equal to {calib_val:.1f} {units}. "

def crf_MixedFluidWarning(calibval,paramval):
    if type(paramval) == float or type(paramval) == int or len(paramval) == 1:
        return paramval == 0 or paramval == 1
    elif len(paramval) == 2:
        return list(paramval) == [0,1] or list(paramval) == [1,0]
    else:
        return True
crmsg_MixedFluidWarning_pass = "The pure fluid model is being used. "
crmsg_MixedFluidWarning_fail ="The {model_name} model should be used to model mixed fluids with caution. The mixed fluid model does not recreate the experimental observations. "
crmsg_MixedFluidWarning_description = "The {model_name} shoule be used to model mixed fluids with caution. "


def crf_GreaterThan(calibval,paramval):
    return paramval >= calibval
crmsg_GreaterThan_pass = "The {param_name} ({param_val:.1f} {units}) is greater than {calib_val:.1f} {units} as required by the calibration range of the {model_name} model. "
crmsg_GreaterThan_fail = "The {param_name} is outside the calibration range of {model_name} ({param_val:.1f}<{calib_val:.1f} {units}. "
crmsg_GreaterThan_description = "The {model_name} model is calibrated for {param_name} greater than {calib_val:.1f} {units}. "
# Warnings for specific models


def crf_LessThan(calibval,paramval):
    return paramval <= calibval
crmsg_LessThan_pass = "The {param_name} ({param_val:.1f} {units}) is less than {calib_val:.1f} {units} as required by the calibration range of the {model_name} model. "
crmsg_LessThan_fail =  "The {param_name} is outside the calibration range of {model_name} ({param_val:.1f}>{calib_val:.1f} {units}. "
crmsg_LessThan_description = "The {model_name} model is calibrated for {param_name} less than {calib_val:.1f} {units}. "
# Warnings for specific model

def crf_Between(calibval,paramval):
    return paramval >= calibval[0] and paramval <= calibval[1]
crmsg_Between_pass = "The {param_name} ({param_val:.1f} {units}) is between {calib_val0:.1f} and {calib_val1:.1f} {units} as required by the calibration range of the {model_name} model. "
crmsg_Between_fail = "{param_name} ({param_val:.1f} {units}) is outside the calibration range of the {model_name} model ({calib_val0:.1f}-{calib_val1:.1f} {units}). "
crmsg_Between_description = "The {model_name} model is calibrated for {param_name} between {calib_val0:.1f} and {calib_val1:.1f} {units}. "
# Different wording for compositional checks (loosing "the")
crmsg_BC_pass = "{param_name} ({param_val:.1f} {units}) is between {calib_val0:.1f} and {calib_val1:.1f} {units} as required by the calibration range of the {model_name} model. "
crmsg_BC_fail = "{param_name} ({param_val:.1f} {units}) is outside the calibration range ({calib_val0:.1f}-{calib_val1:.1f} {units}). "
