# -*- coding: utf-8 -*-

"""Caching decorator for memoizing function results."""

from functools import wraps
from typing import Any, Callable, Dict, Tuple


# pylint: disable=too-few-public-methods
class _CacheWrapper:
    """Wrapper class to maintain cache for decorated function."""

    def __init__(self, fcn: Callable) -> None:
        self.fcn = fcn
        self.cache: Dict[Tuple[Any, ...], Any] = {}
        wraps(fcn)(self)

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        cache_key = args + tuple(kwargs.items())
        if cache_key not in self.cache:
            self.cache[cache_key] = self.fcn(*args, **kwargs)
        return self.cache[cache_key]


def cache(fcn: Callable) -> Callable:
    """
    It maintains a cache of previous function call results that can be
    used to optimize the performance...

    :param fcn: The function being decorated.
    :type fcn: Callable
    :return: The wrapped function.
    :rtype: Callable
    """
    return _CacheWrapper(fcn)
