
class Skills:
    __instance = None

    def __init__(self):
        self._functions = {}
        self._rpc = None # remote procedure call

    @classmethod
    def _instance(cls, *args, **kwargs):
        if not Skills.__instance:
            Skills.__instance = Skills(*args, **kwargs)
        return Skills.__instance
    
    def _add_function(self, name, func):
        self._functions[name] = func

    def _remove_function(self, name):
        self._functions.pop(name, None)

    def _clear_functions(self):
        self._functions = {}

    def _get_function(self, name):
        return self._functions.get(name, None)
    
    def _update_rpc(self, rpc):
        self._rpc = rpc

    def _clear_rpc(self):
        self._rpc = None

    def _run(self, method, *args, **kwargs):
        func = self._get_function(method) or self._get_function('system.functions.' + method)
        if func:
            return func(*args, **kwargs)
        elif self._rpc is not None:
            result = self._rpc(method, *args, **kwargs)
            if isinstance(result, Exception):
                raise result
            else:
                return result
        else:
            raise ValueError(f'function {method} not found')

    def __getattr__(self, name):
        return Skills.Proxy(self, name)

    class Proxy:
        def __init__(self, instance, name):
            self.instance = instance
            self.chain = [name]

        def __getattr__(self, name):
            self.chain.append(name)
            return self

        def __call__(self, *args, **kwargs):
            full_name = '.'.join(self.chain)
            return self.instance._run(full_name, *args, **kwargs)