#!/usr/bin/env python3
"""
HDGL Unified Lattice System (Fully Safe Version)
===============================================
- Slot4096: Variable-precision float with safe exponent and power handling
- HDGLLattice: High-density lattice with prismatic recursion
- HDGLInterpreter: Simple instruction execution
"""

import numpy as np
import random
import math
from typing import List, Tuple
from dataclasses import dataclass

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Constants
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PHI = 1.6180339887498948
FIB_TABLE = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
PRIME_TABLE = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]
MAX_SAFE_EXP = 1024  # Safe exponent for float computations
MAX_LOG = 709        # log(max float64)
MIN_LOG = -745       # log(min normal float64)

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Utility: Safe exponentiation
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

def safe_pow(x: float, n: int) -> float:
    """Compute x**n safely with overflow/underflow handling."""
    if x == 0.0:
        return 0.0
    log_val = math.log(abs(x)) * n
    if log_val > MAX_LOG:
        return math.inf if x > 0 else -math.inf
    elif log_val < MIN_LOG:
        return 0.0
    result = math.exp(log_val)
    if x < 0 and n % 2 == 1:
        result = -result
    return result

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Slot4096: Dynamic Precision Float
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

@dataclass
class Slot4096:
    mantissa: float
    exponent: int
    base: float
    bits_mant: int = 48
    bits_exp: int = 24
    
    @classmethod
    def random(cls, bits_mant: int = 48, bits_exp: int = 24) -> 'Slot4096':
        max_exp = min(2**(bits_exp-1)-1, MAX_SAFE_EXP)
        return cls(
            mantissa=random.random(),
            exponent=random.randint(-max_exp, max_exp),
            base=PHI + random.random() * 0.01,
            bits_mant=bits_mant,
            bits_exp=bits_exp
        )
    
    def value(self) -> float:
        """Compute numerical value safely."""
        exp = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, self.exponent))
        try:
            v = self.mantissa * safe_pow(2.0, exp)
            if not np.isfinite(v):
                return 1.0
            return v
        except OverflowError:
            return 1.0
    
    def phi_scale(self, phi: float = PHI):
        """Apply φ-scaling to mantissa safely."""
        self.mantissa *= phi
        if self.mantissa >= 1.0:
            self.mantissa /= 2.0
            self.exponent += 1
        self.exponent = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, self.exponent))
    
    def pack_base_inf(self) -> int:
        """Pack mantissa/exponent safely to integer."""
        safe_mant = min(max(self.mantissa, 0.0), 0.9999999999999999)
        m_int = int(safe_mant * (1 << self.bits_mant))
        e_int = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, self.exponent))
        e_int += (1 << (self.bits_exp - 1))
        return (m_int << self.bits_exp) | (e_int & ((1 << self.bits_exp) - 1))
    
    @classmethod
    def unpack_base_inf(cls, packed: int, bits_mant: int = 48, bits_exp: int = 24) -> 'Slot4096':
        m_int = packed >> bits_exp
        e_int = (packed & ((1 << bits_exp) - 1)) - (1 << (bits_exp - 1))
        e_int = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, e_int))
        return cls(
            mantissa=m_int / (1 << bits_mant),
            exponent=e_int,
            base=PHI,
            bits_mant=bits_mant,
            bits_exp=bits_exp
        )

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# HDGLLattice: High-Density Lattice Engine
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

class HDGLLattice:
    def __init__(self, num_instances: int = 4096, slots_per_instance: int = 4):
        self.num_instances = num_instances
        self.slots_per_instance = slots_per_instance
        self.total_slots = num_instances * slots_per_instance
        self.slots: List[Slot4096] = []
        
        for i in range(self.total_slots):
            bits_mant = 32 + (i % 8) * 4
            bits_exp = 16 + (i % 8) * 2
            self.slots.append(Slot4096.random(bits_mant, bits_exp))
        
        self.omega = 0.0
        self.time = 0.0
    
    def prismatic_recursion(self, idx: int, val: float) -> float:
        phi_harm = safe_pow(PHI, idx % 16)
        fib_harm = FIB_TABLE[idx % 16]
        dyadic = 1 << (idx % 16)
        prime_harm = PRIME_TABLE[idx % 16]
        omega = 0.5 + 0.5 * np.sin(self.time + idx * 0.01)
        r_dim = safe_pow(val, (idx % 7) + 1)
        r_val = np.sqrt(phi_harm * fib_harm * dyadic * prime_harm * omega) * r_dim
        if not np.isfinite(r_val):
            r_val = 0.0
        return r_val
    
    def step_cpu(self, tick: float = 0.01):
        for i, slot in enumerate(self.slots):
            val = slot.value()
            r = self.prismatic_recursion(i, val)
            slot.mantissa += safe_pow(slot.base, slot.exponent) * tick + 0.05 * r
            if not np.isfinite(slot.mantissa):
                slot.mantissa = 1.0
            if slot.mantissa >= 1.0:
                slot.mantissa /= 2.0
                slot.exponent += 1
            slot.exponent += 1
            slot.exponent = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, slot.exponent))
        self.omega += 0.01 * tick
        self.time += tick
    
    def step_gpu(self, chunk_size: int = 1048576):
        for chunk_start in range(0, self.total_slots, chunk_size):
            chunk_end = min(chunk_start + chunk_size, self.total_slots)
            for i in range(chunk_start, chunk_end):
                slot = self.slots[i]
                val = slot.value()
                r = self.prismatic_recursion(i, val)
                slot.mantissa += safe_pow(slot.base, slot.exponent) * 0.01 + 0.05 * r
                if not np.isfinite(slot.mantissa):
                    slot.mantissa = 1.0
                if slot.mantissa >= 1.0:
                    slot.mantissa /= 2.0
                    slot.exponent += 1
                slot.exponent += 1
                slot.exponent = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, slot.exponent))
        self.time += 0.01
    
    def fold(self):
        new_instances = self.num_instances * 2
        new_total = new_instances * self.slots_per_instance
        for i in range(self.total_slots):
            slot = self.slots[i]
            new_slot = Slot4096(
                mantissa=slot.mantissa + FIB_TABLE[i % 16] * 0.01,
                exponent=slot.exponent,
                base=slot.base + random.random() * 0.001,
                bits_mant=slot.bits_mant,
                bits_exp=slot.bits_exp
            )
            new_slot.exponent = max(-MAX_SAFE_EXP, min(MAX_SAFE_EXP, new_slot.exponent))
            self.slots.append(new_slot)
        self.num_instances = new_instances
        self.total_slots = new_total
    
    def pack(self) -> List[int]:
        return [slot.pack_base_inf() for slot in self.slots]
    
    @classmethod
    def unpack(cls, packed: List[int], slots_per_instance: int = 4) -> 'HDGLLattice':
        total_slots = len(packed)
        num_instances = total_slots // slots_per_instance
        lattice = cls.__new__(cls)
        lattice.num_instances = num_instances
        lattice.slots_per_instance = slots_per_instance
        lattice.total_slots = total_slots
        lattice.slots = [Slot4096.unpack_base_inf(p) for p in packed]
        lattice.omega = 0.0
        lattice.time = 0.0
        return lattice

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# HDGLInterpreter: Instruction Executor
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

class HDGLInterpreter:
    def __init__(self, lattice: HDGLLattice):
        self.lattice = lattice
        self.program = []
        self.ip = 0
    
    def load(self, instructions: List[Tuple[str, ...]]):
        self.program = instructions
        self.ip = 0
    
    def run(self, max_steps: int = 10000):
        steps = 0
        while self.ip < len(self.program) and steps < max_steps:
            inst = self.program[self.ip]
            op = inst[0]
            if op == 'STEP_CPU':
                self.lattice.step_cpu()
            elif op == 'STEP_GPU':
                self.lattice.step_gpu()
            elif op == 'FOLD':
                self.lattice.fold()
            elif op == 'SET' and len(inst) == 3:
                idx, val = int(inst[1]), float(inst[2])
                if 0 <= idx < self.lattice.total_slots:
                    self.lattice.slots[idx].mantissa = val
            elif op == 'PHI_SCALE' and len(inst) == 2:
                idx = int(inst[1])
                if 0 <= idx < self.lattice.total_slots:
                    self.lattice.slots[idx].phi_scale()
            elif op == 'HALT':
                break
            self.ip += 1
            steps += 1
        return steps

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Demo
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

if __name__ == "__main__":
    print("Creating HDGL lattice with 4096 instances...")
    lattice = HDGLLattice(num_instances=4096)
    
    print(f"Total slots: {lattice.total_slots}")
    print(f"First 8 slot values: {[s.value() for s in lattice.slots[:8]]}")
    
    print("\nRunning 10 CPU steps...")
    for _ in range(10):
        lattice.step_cpu()
    print(f"After CPU steps: {[s.value() for s in lattice.slots[:8]]}")
    
    print(f"\nFolding lattice (instances: {lattice.num_instances})...")
    lattice.fold()
    print(f"After fold (instances: {lattice.num_instances})")
    
    print("\nTesting base(∞) pack/unpack...")
    packed = lattice.pack()
    print(f"Packed to {len(packed)} integers")
    unpacked = HDGLLattice.unpack(packed)
    print(f"Unpacked: {unpacked.num_instances} instances")
    
    print("\nRunning interpreter...")
    interp = HDGLInterpreter(lattice)
    interp.load([
        ('STEP_CPU',),
        ('STEP_CPU',),
        ('PHI_SCALE', '0'),
        ('HALT',)
    ])
    steps = interp.run()
    print(f"Executed {steps} instructions")
    
    print("\nHDGL unified system operational.")
