#!/usr/bin/env python3
"""
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/bitmap.py
Bitmap Node Strategy Implementation
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.20
Generation Date: 16-Jan-2026
"""

from __future__ import annotations
from collections.abc import AsyncIterator, Iterator
"""
Bitmap Node Strategy Implementation
This module implements the BITMAP strategy for efficient bit manipulation
and boolean operations with compressed storage.
"""
from typing import Any
import array
from .base import ANodeMatrixStrategy
from .contracts import NodeType
from ...defs import NodeMode, NodeTrait
from ...errors import XWNodeUnsupportedCapabilityError


class BitmapStrategy(ANodeMatrixStrategy):
    """
    Bitmap node strategy for efficient bit manipulation and boolean operations.
    Provides space-efficient storage for boolean flags and supports
    fast bitwise operations with co
    # Strategy type classification
    STRATEGY_TYPE = NodeType.MATRIX
mpressed representation.
    """

    def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
        """
        Initialize the Bitmap strategy.
        Time Complexity: O(initial_size)
        Space Complexity: O(initial_size)
        """
        super().__init__(NodeMode.BITMAP, traits, **options)
        self.initial_size = options.get('initial_size', 1024)
        self.auto_resize = options.get('auto_resize', True)
        self.compression_threshold = options.get('compression_threshold', 0.1)  # 10% fill rate
        # Core storage using Python array for efficiency
        self._bits = array.array('B')  # Byte array for bit storage
        self._capacity_bits = 0
        self._size = 0  # Number of set bits
        self._max_index = -1  # Highest index with a bit set
        # Key-value mapping for compatibility
        self._key_to_index: dict[str, int] = {}
        self._index_to_key: dict[int, str] = {}
        self._values: dict[str, Any] = {}  # Associated values
        self._next_index = 0
        # Initialize with initial capacity
        self._resize_to(self.initial_size)

    def get_supported_traits(self) -> NodeTrait:
        """
        Get the traits supported by the bitmap strategy.
        Time Complexity: O(1)
        """
        return (NodeTrait.COMPRESSED | NodeTrait.INDEXED | NodeTrait.STREAMING)

    def _resize_to(self, new_bit_capacity: int) -> None:
        """
        Resize bitmap to new bit capacity.
        Time Complexity: O(new_capacity - old_capacity) when expanding
        """
        new_byte_capacity = (new_bit_capacity + 7) // 8  # Round up to nearest byte
        if new_byte_capacity > len(self._bits):
            # Expand
            self._bits.extend([0] * (new_byte_capacity - len(self._bits)))
        elif new_byte_capacity < len(self._bits):
            # Shrink
            self._bits = self._bits[:new_byte_capacity]
        self._capacity_bits = new_bit_capacity

    def _ensure_capacity(self, bit_index: int) -> None:
        """
        Ensure bitmap has capacity for the given bit index.
        Time Complexity: O(1) amortized, O(capacity) when resizing
        """
        if bit_index >= self._capacity_bits:
            if self.auto_resize:
                new_capacity = max(bit_index + 1, self._capacity_bits * 2)
                self._resize_to(new_capacity)
            else:
                raise IndexError(f"Bit index {bit_index} exceeds capacity {self._capacity_bits}")

    def _get_bit(self, bit_index: int) -> bool:
        """
        Get bit value at index.
        Time Complexity: O(1)
        """
        if bit_index >= self._capacity_bits or bit_index < 0:
            return False
        byte_index = bit_index // 8
        bit_offset = bit_index % 8
        if byte_index >= len(self._bits):
            return False
        return bool(self._bits[byte_index] & (1 << bit_offset))

    def _set_bit(self, bit_index: int, value: bool) -> bool:
        """
        Set bit value at index. Returns True if bit was changed.
        Time Complexity: O(1) amortized
        """
        self._ensure_capacity(bit_index)
        byte_index = bit_index // 8
        bit_offset = bit_index % 8
        old_value = bool(self._bits[byte_index] & (1 << bit_offset))
        if value:
            self._bits[byte_index] |= (1 << bit_offset)
            if not old_value:
                self._size += 1
                self._max_index = max(self._max_index, bit_index)
        else:
            self._bits[byte_index] &= ~(1 << bit_offset)
            if old_value:
                self._size -= 1
                if bit_index == self._max_index:
                    self._update_max_index()
        return old_value != value

    def _update_max_index(self) -> None:
        """Update the maximum set bit index."""
        self._max_index = -1
        for i in range(self._capacity_bits - 1, -1, -1):
            if self._get_bit(i):
                self._max_index = i
                break
    # ============================================================================
    # CORE OPERATIONS (Key-based interface for compatibility)
    # ============================================================================

    def put(self, key: Any, value: Any = None) -> None:
        """
        Set a bit and associate a value.
        Time Complexity: O(1) amortized
        """
        key_str = str(key)
        # Get or assign bit index for this key
        if key_str in self._key_to_index:
            bit_index = self._key_to_index[key_str]
        else:
            # Try to parse key as integer index
            try:
                bit_index = int(key_str)
                if bit_index < 0:
                    bit_index = self._next_index
                    self._next_index += 1
            except ValueError:
                bit_index = self._next_index
                self._next_index += 1
            self._key_to_index[key_str] = bit_index
            self._index_to_key[bit_index] = key_str
        # Set the bit (treat None as False, everything else as True)
        bit_value = value is not None and value is not False
        self._set_bit(bit_index, bit_value)
        # Store associated value
        if bit_value:
            self._values[key_str] = value if value is not None else True
        else:
            self._values.pop(key_str, None)

    def get(self, key: Any, default: Any = None) -> Any:
        """
        Get value associated with key.
        Time Complexity: O(1)
        """
        key_str = str(key)
        if key_str in self._key_to_index:
            bit_index = self._key_to_index[key_str]
            if self._get_bit(bit_index):
                return self._values.get(key_str, True)
        return default

    def has(self, key: Any) -> bool:
        """
        Check if bit is set for key.
        Time Complexity: O(1)
        """
        key_str = str(key)
        if key_str in self._key_to_index:
            bit_index = self._key_to_index[key_str]
            return self._get_bit(bit_index)
        return False

    def remove(self, key: Any) -> bool:
        """
        Clear bit for key.
        Time Complexity: O(1)
        """
        key_str = str(key)
        if key_str in self._key_to_index:
            bit_index = self._key_to_index[key_str]
            was_set = self._get_bit(bit_index)
            if was_set:
                self._set_bit(bit_index, False)
                self._values.pop(key_str, None)
                return True
        return False

    def delete(self, key: Any) -> bool:
        """
        Clear bit for key (alias for remove).
        Time Complexity: O(1)
        """
        return self.remove(key)

    def clear(self) -> None:
        """
        Clear all bits.
        Time Complexity: O(capacity/8) - clears byte array
        """
        self._bits = array.array('B', [0] * len(self._bits))
        self._size = 0
        self._max_index = -1
        self._values.clear()
        # Keep key mappings for consistency

    def keys(self) -> Iterator[str]:
        """
        Get all keys with set bits.
        Time Complexity: O(m) where m is total keys tracked
        """
        for key_str, bit_index in self._key_to_index.items():
            if self._get_bit(bit_index):
                yield key_str

    def values(self) -> Iterator[Any]:
        """
        Get all values for set bits.
        Time Complexity: O(1) to create, O(n) to iterate
        """
        return iter(self._values.values())

    def items(self) -> Iterator[tuple[str, Any]]:
        """
        Get all key-value pairs for set bits.
        Time Complexity: O(m) where m is total keys tracked
        """
        for key_str, bit_index in self._key_to_index.items():
            if self._get_bit(bit_index):
                yield (key_str, self._values.get(key_str, True))

    def __len__(self) -> int:
        """
        Get the number of set bits.
        Time Complexity: O(1)
        """
        return self._size

    def to_native(self) -> dict[str, bool]:
        """
        Convert to native Python dict of boolean values.
        Time Complexity: O(m) where m is total keys
        """
        return dict(self.items())
    # ============================================================================
    # ANodeMatrixStrategy abstract methods (bitmap as 1-row matrix)
    # ============================================================================

    def get_dimensions(self) -> tuple[int, int]:
        """Get matrix dimensions (rows, cols). Bitmap is 1 x capacity_bits."""
        return (1, self._capacity_bits)

    def get_at_position(self, row: int, col: int) -> Any:
        """Get element at matrix position. Only row 0 is valid."""
        if row != 0 or col < 0 or col >= self._capacity_bits:
            return False
        return self._get_bit(col)

    def set_at_position(self, row: int, col: int, value: Any) -> None:
        """Set element at matrix position. Only row 0 is valid."""
        if row == 0 and 0 <= col < self._capacity_bits:
            self._set_bit(col, bool(value))

    def get_row(self, row: int) -> list[Any]:
        """Get entire row. Only row 0 is valid."""
        if row != 0:
            return []
        return [self._get_bit(i) for i in range(self._capacity_bits)]

    def get_column(self, col: int) -> list[Any]:
        """Get entire column."""
        if col < 0 or col >= self._capacity_bits:
            return [False]
        return [self._get_bit(col)]

    def transpose(self) -> ANodeMatrixStrategy:
        """Transpose: 1xN bitmap -> same (identity for 1-row)."""
        return self

    def multiply(self, other: ANodeMatrixStrategy) -> ANodeMatrixStrategy:
        """Matrix multiplication not supported for bitmap."""
        raise XWNodeUnsupportedCapabilityError("Bitmap does not support matrix multiply")

    def add(self, other: ANodeMatrixStrategy) -> ANodeMatrixStrategy:
        """Matrix addition: bitwise OR when other is BitmapStrategy."""
        if isinstance(other, BitmapStrategy):
            return self.bitwise_or(other)
        raise XWNodeUnsupportedCapabilityError("Bitmap.add requires another BitmapStrategy")

    def as_adjacency_matrix(self):
        """Provide Adjacency Matrix behavioral view."""
        raise XWNodeUnsupportedCapabilityError("Bitmap cannot behave as adjacency matrix")

    def as_incidence_matrix(self):
        """Provide Incidence Matrix behavioral view."""
        raise XWNodeUnsupportedCapabilityError("Bitmap cannot behave as incidence matrix")

    def as_sparse_matrix(self):
        """Provide Sparse Matrix behavioral view."""
        raise XWNodeUnsupportedCapabilityError("Bitmap cannot behave as sparse matrix")

    # ============================================================================
    # ASYNC API - Lightweight wrappers (NO lock overhead, v0.0.1.28b)
    # ============================================================================

    async def insert_async(self, key: Any, value: Any) -> None:
        """Lightweight async wrapper for insert (no lock overhead)."""
        return self.insert(key, value)

    async def find_async(self, key: Any) -> Any | None:
        """Lightweight async wrapper for find (no lock overhead)."""
        return self.find(key)

    async def delete_async(self, key: Any) -> bool:
        """Lightweight async wrapper for delete (no lock overhead)."""
        return self.delete(key)

    async def size_async(self) -> int:
        """Lightweight async wrapper for size (no lock overhead)."""
        return self.size()

    async def is_empty_async(self) -> bool:
        """Lightweight async wrapper for is_empty (no lock overhead)."""
        return self.is_empty()

    async def to_native_async(self) -> Any:
        """Lightweight async wrapper for to_native (no lock overhead)."""
        return self.to_native()

    async def keys_async(self) -> AsyncIterator[Any]:
        """Lightweight async wrapper for keys (no lock overhead)."""
        for key in self.keys():
            yield key

    async def values_async(self) -> AsyncIterator[Any]:
        """Lightweight async wrapper for values (no lock overhead)."""
        for value in self.values():
            yield value

    async def items_async(self) -> AsyncIterator[tuple[Any, Any]]:
        """Lightweight async wrapper for items (no lock overhead)."""
        for item in self.items():
            yield item
    @property

    def is_list(self) -> bool:
        """
        This can behave like a list for indexed access.
        Time Complexity: O(1)
        """
        return True
    @property

    def is_dict(self) -> bool:
        """
        This can behave like a dict.
        Time Complexity: O(1)
        """
        return True
    # ============================================================================
    # BITMAP-SPECIFIC OPERATIONS
    # ============================================================================

    def set_bit(self, index: int, value: bool = True) -> bool:
        """
        Set bit at index. Returns previous value.
        Time Complexity: O(1) amortized
        """
        old_value = self._get_bit(index)
        self._set_bit(index, value)
        return old_value

    def get_bit(self, index: int) -> bool:
        """
        Get bit value at index.
        Time Complexity: O(1)
        """
        return self._get_bit(index)

    def flip_bit(self, index: int) -> bool:
        """
        Flip bit at index. Returns new value.
        Time Complexity: O(1) amortized
        """
        current_value = self._get_bit(index)
        new_value = not current_value
        self._set_bit(index, new_value)
        return new_value

    def count_set_bits(self, start: int = 0, end: int | None = None) -> int:
        """
        Count set bits in range [start, end).
        Time Complexity: O(end - start)
        """
        if end is None:
            end = self._capacity_bits
        count = 0
        for i in range(start, min(end, self._capacity_bits)):
            if self._get_bit(i):
                count += 1
        return count

    def find_first_set(self, start: int = 0) -> int | None:
        """
        Find first set bit starting from index.
        Time Complexity: O(capacity) worst case
        """
        for i in range(start, self._capacity_bits):
            if self._get_bit(i):
                return i
        return None

    def find_first_clear(self, start: int = 0) -> int | None:
        """
        Find first clear bit starting from index.
        Time Complexity: O(capacity) worst case
        """
        for i in range(start, self._capacity_bits):
            if not self._get_bit(i):
                return i
        return None

    def bitwise_and(self, other: BitmapStrategy) -> BitmapStrategy:
        """
        Bitwise AND with another bitmap.
        Time Complexity: O(min(capacity1, capacity2))
        """
        result = BitmapStrategy(
            traits=self.traits,
            initial_size=max(self._capacity_bits, other._capacity_bits)
        )
        max_bits = min(self._capacity_bits, other._capacity_bits)
        for i in range(max_bits):
            if self._get_bit(i) and other._get_bit(i):
                result._set_bit(i, True)
        return result

    def bitwise_or(self, other: BitmapStrategy) -> BitmapStrategy:
        """
        Bitwise OR with another bitmap.
        Time Complexity: O(max(capacity1, capacity2))
        """
        result = BitmapStrategy(
            traits=self.traits,
            initial_size=max(self._capacity_bits, other._capacity_bits)
        )
        max_bits = max(self._capacity_bits, other._capacity_bits)
        for i in range(max_bits):
            if self._get_bit(i) or other._get_bit(i):
                result._set_bit(i, True)
        return result

    def bitwise_xor(self, other: BitmapStrategy) -> BitmapStrategy:
        """
        Bitwise XOR with another bitmap.
        Time Complexity: O(max(capacity1, capacity2))
        """
        result = BitmapStrategy(
            traits=self.traits,
            initial_size=max(self._capacity_bits, other._capacity_bits)
        )
        max_bits = max(self._capacity_bits, other._capacity_bits)
        for i in range(max_bits):
            if self._get_bit(i) != other._get_bit(i):
                result._set_bit(i, True)
        return result

    def bitwise_not(self) -> BitmapStrategy:
        """
        Bitwise NOT (invert all bits).
        Time Complexity: O(capacity)
        """
        result = BitmapStrategy(
            traits=self.traits,
            initial_size=self._capacity_bits
        )
        for i in range(self._capacity_bits):
            if not self._get_bit(i):
                result._set_bit(i, True)
        return result

    def compress(self) -> None:
        """Compress bitmap by removing trailing zero bytes."""
        if self._max_index < 0:
            # No bits set, minimize to 1 byte
            self._resize_to(8)
            return
        # Resize to just fit the highest set bit
        new_capacity = ((self._max_index + 8) // 8) * 8
        self._resize_to(new_capacity)

    def get_compression_ratio(self) -> float:
        """Get compression ratio (set bits / total capacity)."""
        if self._capacity_bits == 0:
            return 0.0
        return self._size / self._capacity_bits

    def to_bytes(self) -> bytes:
        """Export bitmap as bytes."""
        return self._bits.tobytes()

    def from_bytes(self, data: bytes) -> None:
        """Import bitmap from bytes."""
        self._bits = array.array('B', data)
        self._capacity_bits = len(self._bits) * 8
        # Recalculate size and max_index
        self._size = 0
        self._max_index = -1
        for i in range(self._capacity_bits):
            if self._get_bit(i):
                self._size += 1
                self._max_index = i

    def get_bit_pattern(self, start: int = 0, length: int = 64) -> str:
        """Get bit pattern as string for debugging."""
        pattern = ""
        end = min(start + length, self._capacity_bits)
        for i in range(start, end):
            pattern += "1" if self._get_bit(i) else "0"
            if (i - start + 1) % 8 == 0 and i < end - 1:
                pattern += " "  # Byte separator
        return pattern
    # ============================================================================
    # PERFORMANCE CHARACTERISTICS
    # ============================================================================
    @property

    def backend_info(self) -> dict[str, Any]:
        """Get backend implementation info."""
        return {
            'strategy': 'BITMAP',
            'backend': 'Python array (byte-based)',
            'capacity_bits': self._capacity_bits,
            'capacity_bytes': len(self._bits),
            'auto_resize': self.auto_resize,
            'complexity': {
                'set_bit': 'O(1)',
                'get_bit': 'O(1)',
                'count_bits': 'O(n)',
                'bitwise_ops': 'O(n)',
                'space': 'O(n/8) bytes'
            }
        }
    @property

    def metrics(self) -> dict[str, Any]:
        """Get performance metrics."""
        compression_ratio = self.get_compression_ratio()
        memory_efficiency = (self._size * 8) / max(1, len(self._bits)) * 100
        return {
            'set_bits': self._size,
            'total_capacity_bits': self._capacity_bits,
            'capacity_bytes': len(self._bits),
            'max_set_index': self._max_index,
            'compression_ratio': f"{compression_ratio:.3f}",
            'memory_efficiency': f"{memory_efficiency:.1f}%",
            'memory_usage': f"{len(self._bits)} bytes (bits) + {len(self._values) * 24} bytes (values)"
        }
