import os
import sys
import copy


from tklib.tkobject import tkObject, _analyze_varstr
from tklib.tkutils import pconv, pint, pfloat
from tklib.tkinifile import tkIniFile
import tklib.tkre as tkre

#=========================
# Parameter class
#=========================


class tkParams(tkObject):
    def __init__(self, parameter_file = None, app = None, **args):
#        super(tkObject, self).__init__(**args)
        self._argv = sys.argv
        self._path = parameter_file
        self._app  = app
        self._explanation = {}

        self.update(**args)

    def __del__(self):
#        super(tkObject, self).__del__()
        pass

    def __str__(self):
        return self.ClassPath()


    def dict(self):
        return self.__dict__

    def copy(self):
        target = tkParams()
        for key, val in self.dict().items():
            target.__dict__[key] = val
        return target

    def get_param_dict(self):
        return self.__dict__

    def get_path(self, path):
        if path is not None:
            self._path = path

        return self._path

    def keys(self):
        return self.__dict__.keys()
        
    def get(self, key, defval = None):
        return self.__dict__.get(key, defval)

    def set(self, key, val, explanation = None):
        setattr(self, key, val)
        self._explanation[key] = explanation
        return val

    def set_attr(self, key, val, explanation = None):
        return self.set(key, val, explanation)

    def get_explanation(self, key):
        expl = self._explanation.get(key, None)
        if expl is None:
            return None

        if self._app:
           return self._app.p(expl)

        return expl
    
    def get_print_func(self, app, use_warning):
        if app and use_warning:
            print_func = app.print_warning
        elif app:
            print_func = app.print
        else:
            print_func = print
        return print_func

    def printinf(self, app = None, use_warning = False):
        if app is None:
            app = self.app
        print_func = self.get_print_func(app, use_warning)

        print_func("Parameters:")
        for key in self.__dict__.keys():
            expl = self.get_explanation(key)
            if expl:
                print_func(f"   {key}: {self.__dict__[key]} ({expl})")
            else:
                print_func(f"   {key}: {self.__dict__[key]}")

    def print_parameters(self, heading = "", sort_by_keys = True, exclude_keys = [], app = None, use_warning = False):
        if app is None:
            app = self._app
        print_func = self.get_print_func(app, use_warning)
#        print("print_func=", print_func)

        dict = self.get_dict()
        keys = [key for key in dict.keys() if key]
        if sort_by_keys:
            keys = sorted(keys, key = str.lower)

        if heading != "":
            print_func(heading)

        for key in keys:
            if tkre.Match('_', key) or dict[key] is None:
                continue

            expl = self.get_explanation(key)
            if expl:
                print_func(f"  {key}: {dict[key]}  ({expl})")
            else:
                print_func(f"  {key}: {dict[key]}")    

    def print_parameters_warning(self, heading = "", sort_by_keys = True, app = None):
        if app is None:
            app = self._app

        app.print_original("print_parameters_warning")
        dict = self.get_dict()
        keys = [key for key in dict.keys()]
        if sort_by_keys:
            keys = sorted(keys, key = str.lower)

        if heading != "":
            app.print_warning(heading)

        for key in keys:
            if tkre.Match('_', key) or dict[key] is None:
                continue

            expl = self.get_explanation(key)
            if expl:
                app.print_warning(f"  {key}: {dict[key]}  ({expl})")
            else:
                app.print_warning(f"  {key}: {dict[key]}")    

    def get_string(self, path = None, section = None, key = None, def_val = None, is_print = False):
        path = self.get_path(path)
        self._path = path

        ini = tkIniFile(IsPrint = IsPrint)
        if ini is None:
            return None

        return ini.get_string(section = section, key = key, def_val = def_val, is_print = is_print)

    def write_string(self, path = None, section = None, key = None, value = None, outfile = None, is_print = False):
        path = self.get_path(path)
        self._path = path

        ini = tkIniFile(IsPrint = IsPrint)
        if ini is None:
            return None

        return write_string(self, section = section, key = key, value = value, is_print = is_print)

    def read_parameters(self, path = None, section = None, AddSection = False, 
                    ignore_keys = [], terminator = None, IsPrint = True, follow_vartype = True, read_inifile = False):
        path = self.get_path(path)
        self._path = path

# Store copy of self to check var types
        vars_org = copy.copy(self)
#        vars_org = copy.deepcopy(self)

        ini = tkIniFile(IsPrint = IsPrint)
        if ini is None:
            return None

        inf = ini.read_all(path, section = section, AddSection = AddSection, ignore_keys = ignore_keys)
        if inf is None:
            return None

# Check list/tuple/dict variables. Get # of elements, and initialize dict variables
        if not read_inifile:
            try:
                del inf['inifile']
            except:
                pass

        keys = inf.keys()
        list_dict = {}
        val_param = None
        for key in keys:
            varname, index = _analyze_varstr(key)
            if index is None:
                continue

            val_param = self.get2(varname, None)
            if type(index) is int:
#                print("*varname=", varname)
                if list_dict.get(index, None) is None:
                    list_dict[varname] = index
                elif list_dict[varname] < index:
                    list_dict[varname] = index
            else:
                self.__dict__[varname] = {}

# Initialize list/tuple variables
        for varname in list_dict.keys():
#            print("**varname=", varname)
            self.__dict__[varname] = [None] * (list_dict[varname] + 1)
#            print(f"{varname}=", self.__dict__[varname])

# Assign variables
        keys = inf.keys()
        for key in keys:
# Check var types from vars_org
            varname, index = _analyze_varstr(key)
#            val_param = vars_org.get2(key, None)
            var = vars_org.__dict__.get(varname, None)
            if var is None:
                val_param = None
            else:
# Scalar
                if index is None:
                    val_param = var
# list/tuple/dict
                else:
#                    print("index=", varname, index, type(index))
                    if type(index) is int:
# list/tuple
                        nvar = len(var)
                        if nvar == 0:
                            val_param = ''
                        elif index < nvar:
                            val_param = var[index]
# if vars_org is shorter than the var given
                        else:
                            val_param = var[0]
# dict
                    else:
                        if var.get(index, None) is None:
                            val_param = ""
                        else:
                            val_param = var[index]

            str = inf.get(key, None)
            if str is None:
                continue

            if terminator is not None:
                aa = str.split(terminator)
                if len(aa) > 1:
                    str = aa[0]
                
            if follow_vartype and val_param is not None:
                vtype = type(val_param)
#                print("val=", key, val_param, vtype, str)
                if vtype is float:
                    val = pfloat(str)
                elif vtype is int:
                    val = pint(str)
                elif vtype is bool:
                    if str == 'False' or str == '0' or str == '':
                        val = False
                    else:
                        val = True
                else:
                    val = str
            else:
                val = pconv(str, str)

            self.set_attribute2(key, val)

        return inf

    def save_parameters_by_keys(self, prmfile, heading = None, section = None, keys = None, exclude_keys = [], save_commandline = False):
        if keys is None:
            keys = list(params.keys())
        if save_commandline:
            keys.remove("commandline")

        ini = tkIniFile(path = prmfile)
        for key in keys:
            if key in exclude_keys:
                continue

            v = self.get(key, None)
            if v is not None:
                ini.write_string(section = section, key = key, value = v, is_print = False)

    def save_parameters(self, path = None, section = 'Preferences', 
                keys = None, exclude_keys = [], otherparams = None, 
                sort_by_keys = True, 
                other_section = 'OtherParameters',
                update_commandline = True, 
                save_commandline = False,
                save_inifile = False, IsPrint = True):
        path = self.get_path(path)
        self._path = path

        if keys is not None:
            return self.save_parameters_by_keys(path, section = section, keys = keys, exclude_keys = exclude_keys, save_commandline = save_commandline)

        params = self.get_param_dict()
        if update_commandline:
            params['commandline'] = 'python ' + ' '.join(self._argv)

        inifile = params.get('inifile')
        if not save_inifile and inifile is not None:
            try:
                del params['inifile']
            except:
                pass

        if keys is None:
            keys = list(params.keys())

        if not save_commandline: # and params.get('update_commandline', None):
            if "commandline" in keys: keys.remove("commandline")

        if sort_by_keys:
            keys = sorted(keys, key = str.lower)

        ini = tkIniFile(path, IsPrint = IsPrint)
        for key in keys:
            if key in exclude_keys:
                continue

            if tkre.Match('_', key):
                continue

            val = params.get(key, None)
            ret = True
            if val is None:
                continue

            if type(val) is list or type(val) is tuple:
                for i in range(len(val)):
                    ret = ini.write_string(section, f"{key}[{i}]", val[i], is_print = IsPrint)
                    if not ret:
                        break
            elif type(val) is dict:
                for key2 in val.keys():
                    ret = ini.write_string(section, "{}[{}]".format(key, key2), val[key2], is_print = IsPrint)
                    if not ret:
                        break
            else:
                ret = ini.write_string(section, key, val, is_print = IsPrint)

            if not ret:
                break

        if ret and otherparams is not None:
            keys = [key for key in otherparams.keys()]
            if sort_by_keys:
                keys = sorted(keys, key = str.lower)
            for key in keys:
                val = otherparams[key]
                if val is not None:
                    ret = ini.write_string(other_section, key, val, is_print = IsPrint)
                    if not ret:
                        break
        
        if not ret and IsPrint:
            print(f"\nError in tkParams.save_parameters(): Can not write to [{path}]\n")

        params['inifile'] = inifile

        return ret

def main():
    print("")
    print("This is library, not runnable")
    print("")


if __name__ == "__main__":
    main()
