#!/usr/bin/env python3
#Imports
from functools import wraps, partial

#functions and classes

class MethodHook:
    """Decorator class to enable Methods hooking"""

    def __init__(self, func):
        self.func = func
        self.pre_hooks = []   # List to store registered pre-hooks
        self.post_hooks = []  # List to store registered post-hooks
        wraps(func)(self)

    def __call__(self, *args, **kwargs):
        # Execute pre-hooks before the original function
        for hook in self.pre_hooks:
            try:
                args, kwargs = hook(*args, **kwargs)
            except Exception as e:
                print(f"{self.func.__name__} Pre-hook {hook.__name__} raised an exception: {e}")

        try:
            result = self.func(*args, **kwargs)

        finally:
            # Execute post-hooks after the original function
            for hook in self.post_hooks:
                try:
                    result = hook(*args, **kwargs, result=result)  # Pass result to hooks
                except Exception as e:
                    print(f"{self.func.__name__} Post-hook {hook.__name__} raised an exception: {e}")

        return result

    def __get__(self, instance, owner):
        if not instance:
            return self
        else:
            return self.make_callable(instance)

    def make_callable(self, instance):
        # Returns a callable that also allows access to hook registration methods
        callable_instance = partial(self.__call__, instance)
        callable_instance.register_pre_hook = self.register_pre_hook
        callable_instance.register_post_hook = self.register_post_hook
        return callable_instance

    def register_pre_hook(self, hook):
        """Register a function to be called before the original function"""
        self.pre_hooks.append(hook)

    def register_post_hook(self, hook):
        """Register a function to be called after the original function"""
        self.post_hooks.append(hook)

class ClassHook:
    """Decorator class to enable Class Modifying"""
    def __init__(self, cls):
        self.cls = cls
        # Initialize deferred class hooks if they don't already exist
        if not hasattr(cls, 'deferred_class_hooks'):
            cls.deferred_class_hooks = []

    def __call__(self, *args, **kwargs):
        instance = self.cls(*args, **kwargs)
        # Attach instance-specific modify method
        instance.modify = self.make_instance_modify(instance)
        # Apply any deferred modifications
        for hook in self.cls.deferred_class_hooks:
            hook(instance)
        return instance

    def __getattr__(self, name):
        """Delegate attribute access to the class."""
        return getattr(self.cls, name)

    def modify(self, modification_func):
        """Queue a modification to be applied to all future instances of the class."""
        self.cls.deferred_class_hooks.append(modification_func)

    def make_instance_modify(self, instance):
        """Create a modify function that is bound to a specific instance."""
        def modify_instance(modification_func):
            """Modify this specific instance."""
            modification_func(instance)
        return modify_instance

