class DRAMCache:
    def __init__(self, size_mb=None):
        """Initialize DRAM cache with unlimited capacity"""
        self.cache = {}
        self.access_order = []
        self.is_unlimited = True

    def read(self, key):
        if key in self.cache:
            self.access_order.remove(key)
            self.access_order.append(key)
            return self.cache[key]
        return None

    def write(self, key, value):
        """Write to cache with unlimited capacity - no eviction needed"""
        if key in self.cache:
            self.access_order.remove(key)
        self.cache[key] = value
        self.access_order.append(key)

class Buffer:
    def __init__(self, size_mb=None):
        """Initialize buffer with unlimited capacity"""
        self.buffer = []
        self.is_unlimited = True

    def add(self, data):
        """Add data to buffer - no size restrictions"""
        self.buffer.append(data)

class DynamicDRAMCache(DRAMCache):
    def __init__(self, initial_size_mb=4096, max_size_mb=None, auto_scale=True):
        """Initialize dynamic DRAM cache with automatic scaling
        
        Args:
            initial_size_mb (int): Initial cache size in MB
            max_size_mb (int, optional): Maximum cache size in MB. None means unlimited
            auto_scale (bool): Whether to automatically scale cache size based on usage
        """
        super().__init__(size_mb=initial_size_mb)
        self.initial_size_mb = initial_size_mb
        self.max_size_mb = max_size_mb
        self.auto_scale = auto_scale
        self.current_size_mb = initial_size_mb
        self.scale_factor = 2.0  # Double size when scaling up
        self.usage_threshold = 0.8  # Scale up when 80% full

    def write(self, key, value):
        """Write to cache with dynamic scaling"""
        value_size_mb = len(str(value)) / (1024 * 1024)  # Rough size estimation
        current_usage = sum(len(str(v)) for v in self.cache.values()) / (1024 * 1024)
        
        # Check if we need to scale up
        if self.auto_scale and current_usage / self.current_size_mb > self.usage_threshold:
            self._scale_up()

        # Only write if we have space or unlimited
        if self.max_size_mb is None or current_usage + value_size_mb <= self.max_size_mb:
            super().write(key, value)
        else:
            # Evict least recently used entries until we have space
            while current_usage + value_size_mb > self.max_size_mb and self.access_order:
                lru_key = self.access_order[0]
                lru_size = len(str(self.cache[lru_key])) / (1024 * 1024)
                del self.cache[lru_key]
                self.access_order.remove(lru_key)
                current_usage -= lru_size
            
            # Now we can write the new value
            super().write(key, value)

    def _scale_up(self):
        """Scale up cache size if allowed"""
        if not self.max_size_mb or self.current_size_mb < self.max_size_mb:
            new_size = min(
                self.current_size_mb * self.scale_factor,
                float('inf') if self.max_size_mb is None else self.max_size_mb
            )
            self.current_size_mb = new_size

    def flush(self):
        """Flush buffer and return all data"""
        flushed = self.buffer[:]
        self.buffer = []
        return flushed
