import yaml
import logging
from functools import partial

class Base_Para:

    def ftn_obj(self, param_name, ftn):
        self.__dict__[param_name] =  function_property(ftn)

    def read_configs(self, fd, field = None):
        if  isinstance(fd,str):
            if field is not None:
                with open(fd) as f:
                    configs = yaml.safe_load(f.read())[field]
            else:
                with open(fd) as f:
                    configs = yaml.safe_load(f.read())
        elif isinstance(fd,dict):
            configs=fd[field]
        return configs
    
    def save_configs(self, path, configs):
        with open(path, 'w') as f:
            yaml.dump(configs, f, sort_keys=False)

    def init_obj(self, name, module, *args, **kwargs):
        """
        Finds a function handle with the name given as 'type' in config, and returns the
        instance initialized with corresponding arguments given.

        `object = config.init_obj('name', module, a, b=1)`
        is equivalent to
        `object = module.name(a, b=1)`
        """
        module_name = self[name]['type']
        module_args = dict(self[name]['args'])
        assert all([k not in module_args for k in kwargs]), 'Overwriting kwargs given in config file is not allowed'
        module_args.update(kwargs)
        return getattr(module, module_name)(*args, **module_args)

    def init_ftn(self, name, module, *args, **kwargs):
        """
        Finds a function handle with the name given as 'type' in config, and returns the
        function with given arguments fixed with functools.partial.

        `function = config.init_ftn('name', module, a, b=1)`
        is equivalent to
        `function = lambda *args, **kwargs: module.name(a, *args, b=1, **kwargs)`.
        """
        module_name = self[name]['type']
        module_args = dict(self[name]['args'])
        assert all([k not in module_args for k in kwargs]), 'Overwriting kwargs given in config file is not allowed'
        module_args.update(kwargs)
        return partial(getattr(module, module_name), *args, **module_args)   

    def get_logger(self, name, verbosity=2):
        msg_verbosity = 'verbosity option {} is invalid. Valid options are {}.'.format(verbosity, self.log_levels.keys())
        assert verbosity in self.log_levels, msg_verbosity
        logger = logging.getLogger(name)
        logger.setLevel(self.log_levels[verbosity])
        return logger

    def _check(self, param_name, default_value = 'empty_value', configs = None):
        if not hasattr(self, param_name):
            if configs is not None:
                if param_name in configs:
                    self.__dict__[param_name] = configs[param_name]
                else:
                    self.__dict__[param_name] = default_value
                    configs[param_name] = default_value
            else:
                if isinstance(default_value,str) and default_value == 'empty_value':
                    raise RuntimeError(param_name + 'is an empty value')
                else:
                    self.__dict__[param_name] = default_value

        else:
            if configs is not None:
                configs[param_name] = self.__dict__[param_name]
            
        return self.__dict__[param_name]

    def _config_check(self, param_name, default_value = None, configs = {}):
        if param_name not in configs:
            configs[param_name] = default_value
        return configs[param_name]


    

class function_property(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, owner):
        if obj is None:
            return self
        return self.f(obj)