import logging
import functools
from typing import Callable, Any

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


def cache(maxsize: int = 128) -> Callable:
    """
    A decorator that caches the results of a function.

    Args:
        maxsize: The maximum number of entries to store in the cache.
                   If maxsize is set to None, the cache can grow without bound.
                   Defaults to 128.

    Returns:
        A decorator that wraps the function with a caching mechanism.
    """
    def decorator(func: Callable) -> Callable:
        @functools.lru_cache(maxsize=maxsize)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            try:
                return func(*args, **kwargs)
            except Exception as e:
                logging.error(f"Error in cached function {func.__name__}: {e}")
                raise
        return wrapper
    return decorator


def precompute(func: Callable) -> Callable:
    """
    A decorator that precomputes the results of a function for a fixed set of inputs.

    Args:
        func: The function to precompute.

    Returns:
        A decorator that wraps the function and stores the precomputed results.
    """
    cache_dict = {}

    def wrapper(*args):
        if args not in cache_dict:
            try:
                cache_dict[args] = func(*args)
            except Exception as e:
                logging.error(f"Error in precomputed function {func.__name__}: {e}")
                raise
        return cache_dict[args]

    return wrapper


if __name__ == '__main__':
    # Example usage
    @cache(maxsize=32)
    def expensive_calculation(a: int, b: int) -> int:
        """
        A dummy function that simulates an expensive calculation.
        """
        print(f"Performing expensive calculation for {a} and {b}")  # Simulate work
        return a * b

    @precompute
    def simple_function(x):
        return x * 2

    # Test the cache
    print(expensive_calculation(2, 3))  # First call: performs calculation
    print(expensive_calculation(2, 3))  # Second call: retrieves from cache
    print(expensive_calculation(3, 4))  # First call: performs calculation
    print(simple_function(5))
    print(simple_function(5))