#!/usr/bin/env python3
"""
sidon_address.py — Sidon Address Assignment from Spectral Profile

Maps an 8D spectral profile to a Sidon address from the set
{1, 2, 4, 8, 16, 32, 64, 128}. These are the 8 powers of 2, forming
a Sidon set (B_2 sequence): all pairwise sums a_i + a_j (i ≤ j) are
distinct. This guarantees collision-free addressing.
"""

import hashlib
from typing import List

# ── Sidon Set ────────────────────────────────────────────────────────────
SIDON_ADDRESSES = [1, 2, 4, 8, 16, 32, 64, 128]

_ADDRESS_TO_STRAND = {addr: i for i, addr in enumerate(SIDON_ADDRESSES)}
_STRAND_TO_ADDRESS = {i: addr for i, addr in enumerate(SIDON_ADDRESSES)}

# Verify Sidon property at module load
_SIDON_SUMS = {}
for i, a in enumerate(SIDON_ADDRESSES):
    for j, b in enumerate(SIDON_ADDRESSES):
        if i <= j:
            s = a + b
            if s in _SIDON_SUMS:
                raise RuntimeError(f"Sidon VIOLATED: {a}+{b}={s}")
            _SIDON_SUMS[s] = (a, b)


def verify_sidon_property() -> bool:
    seen = set()
    for i, a in enumerate(SIDON_ADDRESSES):
        for j, b in enumerate(SIDON_ADDRESSES):
            if i <= j:
                s = a + b
                if s in seen:
                    return False
                seen.add(s)
    return True


def spectral_to_sidon_address(spectral_profile: List[float], hash_val: int = 0) -> List[int]:
    """Map 8D spectral profile to ordered Sidon address list.
    
    Uses the spectral profile weighted by a deterministic hash to select
    the primary strand. The hash ensures different equations map to
    different strands even when their spectral profiles are similar.
    
    Args:
        spectral_profile: 8D profile from compute_spectral_profile()
        hash_val: Optional integer hash for diversity (default 0)
    
    Returns:
        Ordered list of 8 Sidon addresses, primary first
    """
    if len(spectral_profile) != 8:
        raise ValueError(f"Expected 8D profile, got {len(spectral_profile)}D")
    
    # Blend profile with hash-derived scores for diversity
    scores = []
    for i in range(8):
        profile_score = spectral_profile[i]
        # Hash contribution: deterministic but different per equation
        hash_score = ((hash_val >> (i * 4)) & 0xF) / 16.0
        # Blend: 70% profile, 30% hash (profile dominates structure)
        blended = 0.7 * profile_score + 0.3 * hash_score
        scores.append((i, blended))
    
    # Sort by score descending
    sorted_strands = sorted(scores, key=lambda x: x[1], reverse=True)
    
    return [SIDON_ADDRESSES[s[0]] for s in sorted_strands]


def hash_to_sidon_address(hash_val: int) -> int:
    return SIDON_ADDRESSES[hash_val % 8]


def address_to_strand(address: int) -> int:
    if address not in _ADDRESS_TO_STRAND:
        raise ValueError(f"Invalid Sidon address: {address}")
    return _ADDRESS_TO_STRAND[address]


def strand_to_address(strand: int) -> int:
    if strand < 0 or strand > 7:
        raise ValueError(f"Invalid strand: {strand}")
    return _STRAND_TO_ADDRESS[strand]


def compute_full_address(equation: str) -> List[int]:
    """Compute full Sidon address list for an equation."""
    from spectral_profile import compute_spectral_profile
    
    profile = compute_spectral_profile(equation)
    h = structural_hash(equation)
    return spectral_to_sidon_address(profile, h)


def structural_hash(equation: str) -> int:
    return int(hashlib.sha256(equation.encode()).hexdigest(), 16)
