# Inheritance Hierarchy Example with Metaclass Debugging
# Python 3 Reflexive Metaprogramming
# H. Conrad Cunningham

# Developed for CSci 658, Software Language Engineering, Spring 2018

#234567890123456789012345678901234567890123456789012345678901234567890

# 2018-04-03: (V1)
# 2018-04-22: (V1a) Add type, isinstance, issubclass checks
#             -- from inherit1.py

from functools import wraps, partial

# Function-level prefix decorator
def debug(func = None, *, prefix = ''): 
    if func is None:
        return partial(debug, prefix=prefix)
    msg = prefix + func.__qualname__
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(msg)
        return func(*args, **kwargs)
    return wrapper

# Class-level decorator
def debugmethods(cls): 
    for name, val in vars(cls).items():
        if callable(val):
            setattr(cls, name, debug(val)) 
    return cls

# Metaclass-level debug decorator
class debugmeta(type):
    def __new__(cls, clsname, bases, clsdict):
        clsobj = super().__new__(cls, clsname, bases, clsdict)
        clsobj = debugmethods(clsobj)
        return clsobj

class P(metaclass = debugmeta):
    def __init__(self,name=None): 
        self.name = name 
    def process(self):
        return f'Process at parent P level'

class C(P):   # class C inherits from class P
    def process(self):
        result = f'Process at child C level'
        # Call method in parent class
        return f'{result} \n  {super().process()}'
			
class D(P):   # class D inherits from class P
    pass

class G(C):   # class G inherits from class C
    def process(self):
        return f'Process at grandchild G level'

if __name__ == '__main__':
    p1  = P()
    c1  = C()
    d1  = D()
    g1  = G()
    print(f'p1.process()  -> \n{p1.process()}')
    print(f'c1.process()  -> \n{c1.process()}')
    print(f'd1.process()  -> \n{d1.process()}')
    print(f'g1.process()  -> \n{g1.process()}')
    obj = d1
    print(f'obj.process() -> \n{obj.process()}')
    obj = g1
    print(f'obj.process() -> \n{obj.process()}')

    # Added for V1a
    print("type checks within class hierarchy")
    print(f'type(P)                   = {type(P)}')
    print(f'type(C)                   = {type(C)}')
    print(f'type(G)                   = {type(G)}')

    print("issubclass checks within class hierarchy")
    print(f'issubclass(P,object)      = {issubclass(P,object)}')
    print(f'issubclass(C,P)           = {issubclass(C,P)}')
    print(f'issubclass(G,C)           = {issubclass(G,C)}')
    print(f'issubclass(G,P)           = {issubclass(G,P)}')
    print(f'issubclass(G,object)      = {issubclass(G,object)}')
    print(f'issubclass(C,G)           = {issubclass(C,G)}')
    print(f'issubclass(G,D)           = {issubclass(G,D)}')

    print("issubclass checks against type")
    print(f'issubclass(P,type)        = {issubclass(P,type)}')
    print(f'issubclass(C,type)        = {issubclass(C,type)}')
    print(f'issubclass(G,type)        = {issubclass(G,type)}')

    print("isinstance checks against type")
    print(f'isinstance(P,type)        = {isinstance(P,type)}')
    print(f'isinstance(C,type)        = {isinstance(C,type)}')
    print(f'isinstance(G,type)        = {isinstance(G,type)}')

    print("check type's relationships with object and itself")
    print(f'type(type)                = {type(type)}')
    print(f'issubclass(type,object)   = {issubclass(type,object)}')
    print(f'isinstance(type,type)     = {isinstance(type,type)}')

    print("check object's relationships with type and itself")
    print(f'type(object)              = {type(object)}')
    print(f'issubclass(object,type)   = {issubclass(object,type)}')
    print(f'isinstance(object,type)   = {isinstance(object,type)}')
