# Account Example with Class-Level Decorator (static/class methods)
# Python 3 Reflexive Metaprogramming
# H. Conrad Cunningham

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

#234567890123456789012345678901234567890123456789012345678901234567890

# 2018-03-29: (V1)
# 2018-04-02: (V1a) Changed balance to get_balance
# 2018-05-02: (V1b) Made consistent with account3.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():
        print(f'name = {name}, val = {val}')
        if callable(val):
            setattr(cls, name, debug(val)) 
    return cls

@debugmethods
class Account:
    def __init__(self):
        self._bal = 0

    def deposit(self,amt):
        self._bal += amt

    def withdraw(self,amt):
        if amt <= self._bal:
            self._bal -= amt
        else:
            print(f'Insufficient funds for withdrawal of {amt}')

    def get_balance(self):
        return self._bal

    def __str__(self):
        return f'Account with balance {self._bal}'

    @classmethod
    def classname(cls):
       return cls.__name__

    @staticmethod
    def warn(msg):
       print(f'Warning: {msg}')
    
if __name__ == '__main__':
    acct = Account()
    print(f'Account: {acct}')
    acct.deposit(100)
    print(f'Depost 10: {acct.get_balance()}')
    acct.withdraw(60)
    print(f'Withdraw 60: {acct.get_balance()}')
    print(f'Account.classname -> {Account.classname()}')
    Account.warn("Error")
    print(f'Account.warn("ERROR") above')
    print(f'callable(Account.classname)? {callable(Account.classname)}')
    print(f'callable(Account.warn)? {callable(Account.warn)}')

