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

# -------------------------------
# Configuration
# -------------------------------
NUM_CORES = 2
NUM_STRANDS = 8
SLOTS_PER_STRAND = 4
TOTAL_SLOTS = NUM_STRANDS * SLOTS_PER_STRAND
BLOCK_SIZE = 4096
FS = 1000  # Low frequency for bio-compatibility (Hz)
MAX_AMP = 0.05  # Physiological safe amplitude

# -------------------------------
# Lattice Initialization
# -------------------------------
PHI = 1.6180339887
SQRT_PHI = np.sqrt(PHI)
N_BASE = np.arange(1, NUM_STRANDS+1)
OMEGA_BASE = 1 / (PHI**N_BASE)**7

cores = []
for c in range(NUM_CORES):
    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))
    weights = np.ones((NUM_STRANDS, SLOTS_PER_STRAND))
    cores.append({'lattice': lattice, 'phases': phases, 'weights': weights, 'omega': OMEGA_BASE*(1/(c+1))})

# -------------------------------
# Networking (Geo-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 = {}  # addr -> last seen + timestamp

def broadcast_lattice(gps_time):
    data = pickle.dumps({'cores': cores, 'timestamp': gps_time})
    sock.sendto(data, (BROADCAST_IP, UDP_PORT))

def listen_for_peers():
    global cores
    while True:
        try:
            data, addr = sock.recvfrom(8192)
            packet = pickle.loads(data)
            peer_cores = packet['cores']
            peer_time = packet['timestamp']

            peers[addr[0]] = peer_time

            # Merge lattices with adaptive phase correction
            delay = time.time() - peer_time
            for c, peer_core in enumerate(peer_cores):
                core = cores[c]
                # Phase correction: align constructive interference
                core['phases'][:] = (core['phases'] + peer_core['phases']) / 2 - delay * 0.01
                total_weight = core['weights'] + peer_core['weights']
                core['lattice'][:] = (core['lattice']*core['weights'] + peer_core['lattice']*peer_core['weights'])/total_weight
                core['weights'][:] = np.maximum(core['weights'], peer_core['weights'])
        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()

# -------------------------------
# Peer Cleanup
# -------------------------------
def prune_peers(timeout=5):
    while True:
        now = time.time()
        stale = [addr for addr,last_seen in peers.items() if now-last_seen>timeout]
        for addr in stale:
            del peers[addr]
        time.sleep(1)

prune_thread = threading.Thread(target=prune_peers, daemon=True)
prune_thread.start()

# -------------------------------
# Adaptive Bio-Compatible RF
# -------------------------------
SAFE_FREQS = np.linspace(1, 1000, TOTAL_SLOTS*NUM_CORES)  # 1 Hz - 1 kHz
t = np.arange(BLOCK_SIZE) / FS

def generate_bio_rf_block(freqs, cores):
    rf_block = np.zeros(BLOCK_SIZE, dtype=np.float32)
    for c, core in enumerate(cores):
        lattice = core['lattice']
        phases = core['phases']
        weights = core['weights']
        for s in range(NUM_STRANDS):
            for i in range(SLOTS_PER_STRAND):
                amp = MAX_AMP * (0.5 + 0.5*lattice[s,i])
                phase = phases[s,i]
                freq = freqs[c*TOTAL_SLOTS + s*SLOTS_PER_STRAND + i]
                wave = amp * np.sin(2*np.pi*freq*t + phase)
                rf_block += wave * weights[s,i]
    rf_block /= np.max(np.abs(rf_block))  # normalize envelope
    return rf_block

# -------------------------------
# Main Geo-Sync Adaptive Loop
# -------------------------------
try:
    freqs = SAFE_FREQS
    while True:
        gps_time = time.time()
        block = generate_bio_rf_block(freqs, cores)
        broadcast_lattice(gps_time)
        # Optionally feed to SDR: sdr.write_samples(block)
        time.sleep(BLOCK_SIZE/FS)
except KeyboardInterrupt:
    print("Adaptive bio-safe streaming stopped.")
