from functools import wraps
import time

'''
Такой декоратор работать не будет
'''
'''
def html_tag(func, name_tag='h1'):
    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        return f'<{name_tag}>{result}</{name_tag}>'
    return inner
'''
    
'''
def decorator_factory(a, b):
    print('Запуск функции создания декоратора')
    def decorator(fn):
        print('Запуск декоратора')
        def wrapper(*args, **kwargs):
            print('Запуск функции wrapper')
            print('Переданные аргументы: ', a, b)
            return fn(*args, **kwargs)
        return wrapper
    return decorator
'''

'''
Можно раскрыть следующей записью

decorator = decorator_factory() # получаем декоратор из decorator_factory
original_func = decorator(original_func) # декорируем

Или есть еще вариант

original_func = decorator_facotry()(original_func)
'''

'''
@decorator_factory(10, 20) # Обратите внимание на оператор вызова
def original_func():
	print('Запуск оригинальной функции')
'''

'''
Рабочий вариант декоратора
'''

def html_tag(name_tag='h1'):
    def decorator(func):
        @wraps(func)
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return f'<{name_tag}>{result}</{name_tag}>'
        return inner
    return decorator



@html_tag(name_tag='table')
@html_tag('td')
@html_tag()
@html_tag('p')
def say_hello_to(name, surname):
    return f'Hello {name} {surname}'


def cached_with_expiry(expiry_time):
    def decorator(original_function):
        cache = {} # словарь для хранения кеша
        def wrapper(*args, **kwargs):
            key = (*args, *kwargs.items())
            if key in cache:
                cached_value, cached_timestamp = cache[key]
                if time.time() - cached_timestamp < expiry_time:
                    return f'[CACHED] - {cached_value}'
                
            result = original_function(*args, **kwargs)
            cache[key] = (result, time.time())
            return result
        return wrapper
    return decorator


@cached_with_expiry(expiry_time=5)
def get_product(x, y):
    return x*y


def multiply_result_by(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return n*result
        return wrapper
    return decorator


def limit_query(n):
    def decorator(func):
        limit = n
        @wraps(func)
        def wrapper(*args, **kwargs):
            nonlocal limit
            if limit == 0:
                print(f'Лимит вызовов закончен, все {n} попытки израсходованы')
                return None
            else:
                limit -= 1
                return func(*args, **kwargs)
        return wrapper
    return decorator


def monkey_patching(arg='Monkey', kwarg='patching'):
    def decorator(func):
        @wraps(func)
        def wrapper(*_args, **_kwargs):
            patched_args = (arg, )*len(_args)
            patched_kwargs = {key:kwarg for key, _ in _kwargs.items()}
            return func(*patched_args, **patched_kwargs)
        return wrapper
    return decorator


@monkey_patching(kwarg='Duper')
def print_args_kwargs(*args, **kwargs):
    for i, value in enumerate(args):
        print(i, value)
    for k, v in sorted(kwargs.items()):
        print(f'{k} = {v}')


def pass_arguments(*args_, **kwargs_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(args_, kwargs_)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@pass_arguments(s='Когда', w='-', r='нибудь!')
def concatenate(**kwargs):
    result = ""
    for arg in kwargs.values():
        result += str(arg)
    return result


def pass_arguments(*args_, **kwargs_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            new_args = args + args_
            new_kwargs = kwargs | kwargs_
            return func(*new_args, **new_kwargs)
        return wrapper
    return decorator


def convert_to(type_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return type_(func(*args, **kwargs))
        return wrapper
    return decorator


@convert_to(str)
def add_values(a, b):
    return a + b


'''
def validate_all_args_str(func):
    def wrapper(*args, **kwargs):
        if len([True for x in args if type(x) == str]) == len(args):
            return func(*args, **kwargs)
        else:
            print('Все аргументы должны быть строками')
    return wrapper  
'''


def validate_all_args(type_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if len([True for x in args if type(x) == type_]) == len(args):
                return func(*args, **kwargs)
            else:
                print(f'Все аргументы должны принадлежать типу {type_}')
        return wrapper
    return decorator


@validate_all_args(set)
def print_args_kwargs(*args, **kwargs):
    for i, value in enumerate(args):
        print(i, value)
    for k, v in sorted(kwargs.items()):
        print(f'{k} = {v}')


def compose(*args_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            for f in args_:
                result = f(result)
            return result
        return wrapper
    return decorator


def double_it(a):
    return a * 2


def increment(a):
    return a + 1
    
@compose(double_it, increment)
def get_sum(*args):
    return sum(args)


def add_attrs(**kwargs_):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        for key, value in kwargs_.items():
            setattr(wrapper, key, value)
        return wrapper
    return decorator


@add_attrs(test=True, ordered=True)
def add(a, b):
    return a + b


def main():

    print(add(10, 5))
    print(add.test)
    print(add.ordered)


    # print(get_sum(5))
    # print(get_sum(20, 10))
    # print(get_sum(5, 15, 25))

    # print_args_kwargs([], [1], [1, 2], b=set(), w=set())
    # print_args_kwargs(1, 2, 3, 4, b=300, w=40, t=50, a=100)

    # result = add_values(10, 20)
    # print(f"Результат: {result}, тип результата {type(result)}")

    # print(add(5, 4, 6, a=1, b=2))

    # print(concatenate(a="Я", b="Выучу", c="Этот", d="Питон", e="!"))

    # print_args_kwargs(1, 2, 3, 4, b=300, w=40, t=50, a=100)

    '''
    print(get_product(23, 5))  # Вычисляем в первый раз
    print(get_product(23, 5))  # Во второй раз срабатывает кеш
    time.sleep(5)
    print(get_product(23, 5))  # Кеш просрочился, поэтому вновь вычисляется значение
    '''

    # original_func()
    # print(say_hello_to('Vasiliy', 'Ytkin'))
    


if __name__ == '__main__':
    main()