#exonware/xwsystem/src/exonware/xwsystem/patterns/object_pool.py
#exonware/xwsystem/patterns/object_pool.py
"""
Generic object pool implementation for XSystem framework.
This module provides a reusable object pool that can be used across different
components to reduce memory allocation overhead and improve performance.
"""

import threading
from typing import Any, Callable, Optional
# Root cause: Migrating to Python 3.12 built-in generic syntax for consistency
# Priority #3: Maintainability - Modern type annotations improve code clarity
from ..config.logging_setup import get_logger


class ObjectPool:
    """
    Generic object pool for reusing instances to reduce memory allocation overhead.
    This is particularly effective when creating and destroying many objects
    of the same type frequently.
    """

    def __init__(
        self,
        max_size: int = 1000,
        enable_thread_safety: bool = True,
        component_name: str = "generic",
    ):
        """
        Initialize the object pool.
        Args:
            max_size: Maximum number of objects to keep in the pool
            enable_thread_safety: Whether to use thread-safe operations
            component_name: Name for logging purposes
        """
        self._pools: dict[type, list[Any]] = {}
        self._max_size = max_size
        self._lock = threading.RLock() if enable_thread_safety else None
        self._logger = get_logger(f"{component_name}.object_pool")
        self._stats = {"created": 0, "reused": 0, "released": 0, "discarded": 0}

    def get[T](self, obj_type: type[T], *args, **kwargs) -> T:
        """
        Get an object from the pool or create a new one if the pool is empty.
        Args:
            obj_type: The type of object to get/create
            *args: Arguments to pass to the object constructor
            **kwargs: Keyword arguments to pass to the object constructor
        Returns:
            An instance of the requested type
        """
        pool = self._pools.get(obj_type, [])
        if self._lock:
            with self._lock:
                return self._get_from_pool(obj_type, pool, *args, **kwargs)
        else:
            return self._get_from_pool(obj_type, pool, *args, **kwargs)

    def _get_from_pool[T](self, obj_type: type[T], pool: list[Any], *args, **kwargs) -> T:
        """Internal method to get object from pool."""
        if pool:
            obj = pool.pop()
            # Re-initialize the object if it has a reset method
            if hasattr(obj, "reset"):
                obj.reset(*args, **kwargs)
            elif hasattr(obj, "__init__"):
                obj.__init__(*args, **kwargs)
            self._stats["reused"] += 1
            self._logger.debug(f"Reused {obj_type.__name__} from pool")
            return obj
        # Create new object
        obj = obj_type(*args, **kwargs)
        self._stats["created"] += 1
        self._logger.debug(f"Created new {obj_type.__name__}")
        return obj

    def release(self, obj: Any) -> None:
        """
        Return an object to the pool for future reuse.
        Args:
            obj: The object to return to the pool
        """
        obj_type = type(obj)
        # Clear references if object has a cleanup method
        if hasattr(obj, "cleanup"):
            obj.cleanup()
        if self._lock:
            with self._lock:
                self._release_to_pool(obj, obj_type)
        else:
            self._release_to_pool(obj, obj_type)

    def _release_to_pool(self, obj: Any, obj_type: type) -> None:
        """Internal method to release object to pool."""
        if obj_type not in self._pools:
            self._pools[obj_type] = []
        pool = self._pools[obj_type]
        if len(pool) < self._max_size:
            pool.append(obj)
            self._stats["released"] += 1
            self._logger.debug(f"Released {obj_type.__name__} to pool")
        else:
            self._stats["discarded"] += 1
            self._logger.debug(f"Discarded {obj_type.__name__} (pool full)")

    def clear(self, obj_type: Optional[type] = None) -> None:
        """
        Clear objects from the pool.
        Args:
            obj_type: Specific type to clear, or None to clear all
        """
        if self._lock:
            with self._lock:
                self._clear_pool(obj_type)
        else:
            self._clear_pool(obj_type)

    def _clear_pool(self, obj_type: Optional[type] = None) -> None:
        """Internal method to clear pool."""
        if obj_type is None:
            # Clear all pools
            for pool in self._pools.values():
                pool.clear()
            self._logger.info("Cleared all object pools")
        else:
            # Clear specific pool
            if obj_type in self._pools:
                self._pools[obj_type].clear()
                self._logger.info(f"Cleared pool for {obj_type.__name__}")

    def get_stats(self) -> dict[str, Any]:
        """
        Get pool statistics.
        Returns:
            Dictionary containing pool statistics
        """
        if self._lock:
            with self._lock:
                return self._get_pool_stats()
        else:
            return self._get_pool_stats()

    def _get_pool_stats(self) -> dict[str, Any]:
        """Internal method to get pool statistics."""
        pool_sizes = {t.__name__: len(pool) for t, pool in self._pools.items()}
        return {
            "stats": self._stats.copy(),
            "pool_sizes": pool_sizes,
            "max_size": self._max_size,
            "total_pools": len(self._pools),
        }

    def reset_stats(self) -> None:
        """Reset pool statistics."""
        if self._lock:
            with self._lock:
                self._stats = {"created": 0, "reused": 0, "released": 0, "discarded": 0}
        else:
            self._stats = {"created": 0, "reused": 0, "released": 0, "discarded": 0}


class PooledObject:
    """
    Base class for objects that can be pooled.
    Objects that inherit from this class can be automatically managed
    by the ObjectPool with proper cleanup and reset functionality.
    """

    def cleanup(self) -> None:
        """
        Clean up the object before returning to pool.
        Override this method to implement custom cleanup logic.
        """
        pass

    def reset(self, *args, **kwargs) -> None:
        """
        Reset the object to initial state.
        Override this method to implement custom reset logic.
        """
        pass
# Global object pool registry
_pool_registry: dict[str, ObjectPool] = {}
_pool_registry_lock = threading.RLock()


def get_object_pool(
    component_name: str = "generic",
    max_size: int = 1000,
    enable_thread_safety: bool = True,
) -> ObjectPool:
    """
    Get or create an object pool for a specific component.
    Args:
        component_name: Name of the component
        max_size: Maximum pool size
        enable_thread_safety: Whether to use thread-safe operations
    Returns:
        ObjectPool instance for the component
    """
    with _pool_registry_lock:
        if component_name not in _pool_registry:
            _pool_registry[component_name] = ObjectPool(
                max_size=max_size,
                enable_thread_safety=enable_thread_safety,
                component_name=component_name,
            )
        return _pool_registry[component_name]


def clear_object_pool(component_name: str) -> None:
    """
    Clear a specific object pool.
    Args:
        component_name: Name of the component pool to clear
    """
    with _pool_registry_lock:
        if component_name in _pool_registry:
            _pool_registry[component_name].clear()
            del _pool_registry[component_name]


def get_all_pool_stats() -> dict[str, dict[str, Any]]:
    """
    Get statistics for all object pools.
    Returns:
        Dictionary mapping component names to their pool statistics
    """
    with _pool_registry_lock:
        return {name: pool.get_stats() for name, pool in _pool_registry.items()}
