"""gen_vectors.py - Test vector generator for rng module.

Generates expected 256-bit LFSR output values using the same Galois LFSR
polynomial as the RTL: x^256 + x^255 + x^253 + x^252 + x^247 + 1
Taps: [255, 253, 252, 247, 0]
"""

import os
import sys

# Add test_framework/lib to path for VectorGenerator base class
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))

from vector_gen import VectorGenerator

# Default seed matches RTL SEED parameter
DEFAULT_SEED = 0xDEADBEEFCAFEBABEFEEDFACEDECAFBAD1234567887654321ABCDEF010FEDCBA9

# 256-bit mask
MASK_256 = (1 << 256) - 1


class _LFSR256:
    """256-bit Galois LFSR matching rng_sync.v implementation.
    
    Polynomial: x^256 + x^255 + x^253 + x^252 + x^247 + 1
    Taps: [255, 253, 252, 247, 0]
    """

    def __init__(self, seed: int):
        self.state = seed & MASK_256

    def next(self) -> int:
        """Advance LFSR one step and return the new state."""
        feedback = self.state & 1  # LSB = state[0]

        # Shift right by 1, feedback into MSB (position 255)
        new_state = (self.state >> 1) | (feedback << 255)

        # XOR feedback into tap positions (shifted)
        if feedback:
            new_state ^= (1 << 254)  # tap 255 -> position 254
            new_state ^= (1 << 252)  # tap 253 -> position 252
            new_state ^= (1 << 251)  # tap 252 -> position 251
            new_state ^= (1 << 246)  # tap 247 -> position 246

        self.state = new_state & MASK_256
        return self.state


class RngVectorGenerator(VectorGenerator):
    """Generates test vectors for the rng_sync module."""

    def __init__(self):
        super().__init__()
        self._lfsr = None

    def _ensure_lfsr(self, params: dict) -> None:
        """Initialize LFSR on first call or when params change seed."""
        seed = params.get('seed', DEFAULT_SEED)
        if self._lfsr is None:
            self._lfsr = _LFSR256(seed)

    def generate_one(self, params: dict) -> dict:
        """Generate one LFSR output value.

        On each call, advances the LFSR from its current state and returns
        the new state. This matches the RTL behavior: valid_i advances the
        LFSR, and the new value appears on data_o.

        Args:
            params: Optional dict with 'seed' key to override default seed.

        Returns:
            dict with 'input' (empty) and 'expected' {'data': lfsr_state_hex}.
        """
        self._ensure_lfsr(params)
        new_state = self._lfsr.next()
        return {
            'input': {},
            'expected': {'data': new_state}
        }

    def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
        """Write input file with one dummy line per vector.

        The RNG has no input signals, but the testbench reads the hex file
        to determine the number of vectors to generate. Each line acts as
        a pulse trigger.

        Args:
            vectors: List of vector dicts from generate_one().
            filepath: Path to write the hex file.
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        with open(filepath, 'w') as f:
            for _ in vectors:
                f.write('0\n')

    def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
        """Write expected output as 64-char hex strings, one per line.

        Args:
            vectors: List of vector dicts from generate_one().
            filepath: Path to write the expected hex file.
        """
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        with open(filepath, 'w') as f:
            for v in vectors:
                data = v['expected']['data']
                # Format as 64-character uppercase hex string
                f.write(f'{data:064X}\n')
