import numpy as np
import time
import socket
import pickle
import threading
import random

# -------------------------------
# HDGL Lattice Parameters
# -------------------------------
NUM_STRANDS = 8
SLOTS_PER_STRAND = 4
TOTAL_SLOTS = NUM_STRANDS * SLOTS_PER_STRAND

PHI = 1.6180339887
SQRT_PHI = np.sqrt(PHI)
N_BASE = np.arange(1, NUM_STRANDS+1)
OMEGA = 1 / (PHI**N_BASE)**7

lattice = np.random.uniform(0.5, 1.0, (NUM_STRANDS, SLOTS_PER_STRAND))
phases = np.random.uniform(0, 2*np.pi, (NUM_STRANDS, SLOTS_PER_STRAND))

# -------------------------------
# RF Output Parameters
# -------------------------------
FS = 1_000_000
BLOCK_SIZE = 4096
t = np.arange(BLOCK_SIZE) / FS
BASE_FREQS = np.linspace(100e3, 400e3, TOTAL_SLOTS)

# Frequency hopping schedule
HOP_INTERVAL = 0.1  # seconds
FREQ_HOP_RANGES = [(100e3, 200e3), (200e3, 300e3), (300e3, 400e3)]

# -------------------------------
# Networking: Multi-Hop Peer Sync
# -------------------------------
UDP_PORT = 5005
BROADCAST_IP = "255.255.255.255"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(("", UDP_PORT))
sock.settimeout(0.001)

peers = []

def broadcast_lattice():
    data = pickle.dumps({'lattice': lattice, 'phases': phases})
    sock.sendto(data, (BROADCAST_IP, UDP_PORT))

def listen_for_peers():
    global lattice, phases
    while True:
        try:
            data, addr = sock.recvfrom(8192)
            packet = pickle.loads(data)
            peer_lattice = packet['lattice']
            peer_phases = packet['phases']
            lattice[:] = (lattice + peer_lattice)/2
            phases[:] = (phases + peer_phases)/2
            if addr[0] not in peers:
                peers.append(addr[0])
        except socket.timeout:
            continue
        except Exception as e:
            print(f"Network error: {e}")

listener_thread = threading.Thread(target=listen_for_peers, daemon=True)
listener_thread.start()

# -------------------------------
# Frequency Hopping
# -------------------------------
def get_hopped_frequencies():
    freqs = []
    for idx in range(TOTAL_SLOTS):
        hop_range = random.choice(FREQ_HOP_RANGES)
        freqs.append(random.uniform(*hop_range))
    return np.array(freqs)

# -------------------------------
# Generate lattice RF block
# -------------------------------
def generate_rf_block(freqs):
    global lattice, phases
    lattice_new = np.copy(lattice)
    for s in range(NUM_STRANDS):
        for i in range(SLOTS_PER_STRAND):
            lattice_new[s,i] += 0.02*OMEGA[s]*(1+0.05*i)
            resonance = np.sum([OMEGA[j]*lattice[j,i] for j in range(NUM_STRANDS) if j!=s])
            lattice_new[s,i] += 0.01*resonance
            lattice_new[s,i] = lattice_new[s,i] if lattice_new[s,i] > SQRT_PHI else lattice_new[s,i]*0.8
            phases[s,i] += 0.05*lattice[s,i]
    lattice[:] = lattice_new

    rf_block = np.zeros(BLOCK_SIZE, dtype=np.complex64)
    for idx in range(TOTAL_SLOTS):
        strand = idx // SLOTS_PER_STRAND
        slot = idx % SLOTS_PER_STRAND
        carrier = np.exp(1j*(2*np.pi*freqs[idx]*t + phases[strand,slot]))
        amp = lattice[strand,slot] / np.max(lattice)
        rf_block += amp * carrier

    rf_block /= np.max(np.abs(rf_block))
    return rf_block

# -------------------------------
# Main Streaming Loop
# -------------------------------
try:
    print("HDGL Multi-Hop RF Lattice streaming. Ctrl+C to stop.")
    last_hop = time.time()
    freqs = get_hopped_frequencies()

    while True:
        if time.time() - last_hop > HOP_INTERVAL:
            freqs = get_hopped_frequencies()
            last_hop = time.time()

        block = generate_rf_block(freqs)
        broadcast_lattice()  # share current lattice
        # sdr.write_samples(block)  # Replace with actual SDR/DAC API
        time.sleep(BLOCK_SIZE/FS)

except KeyboardInterrupt:
    print("Streaming stopped.")
