from pprint import pprint
import re


def _analyze_varstr(str):
    match = re.match(r'^(\S+)(\[|\()(.+)(\]|\))$', str)
    if match is None:
        return str, None

    varname = match.group(1)
    index = match.group(3)
#    print("varname=", varname, index)
    try:
        n = int(index)
        return varname, n
    except:
#        print("index=[{}]".format(index))
        s = index.strip('"')
        s = s.strip("'")
        return varname, s


class tkObject:
    debug = 0
    debug_explicit = 0

#    def __ini__(self, **args):
#        self.update(**args)

    def __del__(self):
        pass


    def __str__(self):
        return "{}.{}".format(__name__, self.__class__.__name__)

    def hasattr(self, attribute):
        return hasattr(self, attribute)

    def callable(self, method):
        return callable(getattr(self, method))

    def exec_method(self, method, *args, **kwarg):
        func = getattr(self, method, None)
        if func and callable(func):
             func(*args, **kwargs)

    def get_class(self):
        return self.__class__

    def get_attributes(self):
        return dir(self.__class__)

    def get_member_functions(self, obj = None):
        if obj is None:
            class_type = self.__class__
        else:
            class_type = obj.__class__
        return [method for method in dir(class_type) if callable(getattr(class_type, method))]

    def get_member_variables(self, obj = None):
        if obj is None:
            class_type = self.__class__
        else:
            class_type = obj.__class__
        return [method for method in dir(class_type) if not callable(getattr(class_type, method))]

    def get_dict(self):
        return self.__dict__

    def get_members(self):
        class_type = self.__class__
        funcs = [method for method in dir(class_type) if callable(getattr(class_type, method))]
        vars = [method for method in dir(class_type) if not callable(getattr(class_type, method))]
        return funcs, vars, getattr(self, "__dict__", None)

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

    def set_attribute(self, key, val):
        self.__dict__[key] = val

    def get2(self, key, defval = None):
        varname, index = _analyze_varstr(key)
        var = self.__dict__.get(varname, defval)
        if var is None:
            return None
        if index is None:
            return var
        return var[index]

    def set_attribute2(self, key, val, assign_dict_element_vars = False):
        varname, index = _analyze_varstr(key)
        if index is None:
            self.__dict__[key] = val
        else:
#            if varname in self.__dict__:
#                if type(index) is int:
#                    self.__dict__[varname] = []
#                    self.__dict__[varname][index] = val
#                else:
#                    self.__dict__[varname] = {}
#                    self.__dict__[varname][index] = val
            self.__dict__[varname][index] = val

        if assign_dict_element_vars:
            self.__dict__[key] = val

    def update(self, check_defined = False, **kwargs):
        errors = []
        for key, val in kwargs.items():
            if check_defined and key not in self.keys():
                errors.append(f"key {key} is not defined")
            else:
                setattr(self, key, val)

        if len(errors) > 0:
            print("Error(s) in {}.{}:".format(__name__, self.__class__.__name__))
            for err in errors:
                print("  {}".format(err))
            print("")
            exit()

        '''        
        if self.debug_explicit == 1:
            errors = []
            for key in kwargs.keys():
                if not key in self.keys():
                    errors.append("key {} is not defined".format(key))

            if len(errors) > 0:
                print("Error(s) in {}.{}:".format(__name__, self.__class__.__name__))
                for err in errors:
                    print("  {}".format(err))
                print("")
                exit()

#        setattr(self, key, args[key])
        print("kwargs=", kwargs)
        self.__dict__.update(kwargs)
        '''

    def print_attributes(self, print_header = 1):
         keys = self.__dict__.keys()
         if print_header:
             print("attributes in {}".format(self.ClassPath()))
         for key in keys:
            print("  {}: {}".format(key, self.__dict__[key]))


    def debugprint(self, level, *args):
        self.dprint(level, *args)

    def dprint(self, level, *args):
        if self.debug >= level:
            print(*args)

    def debugpprint(self, level, *args):
        self.dpprint(level, *args)

    def dpprint(self, level, *args):
        if self.debug >= level:
            pprint(*args)


    def IfYes(self, condition, vartrue, varfalse):
        if condition:
            return vartrue
        else:
            return varfalse


    def errormsg(self, funcname, str):
        print("Error in {}.{}: {}".format(self.ClassPath(), funcname, str))

    def ModuleName(self):
        return __name__

    def ClassName(self):
        return self.__class__.__name__

    def FunctionName(self):
        return sys._getframe().f_code.co_name
#import inspect
#    f = inspect.currentframe()
#    return inspect.getframeinfo(f)[2]

    def ClassPath(self):
        return self.ModuleName() + '.' + self.ClassName()
