import numpy as np
import threading
import time

# -----------------------------
# Config
# -----------------------------
CARRIER_FREQ = 915e6       # LoRa frequency (example)
SAMPLE_RATE = 1e6          # samples/sec
TICK_INTERVAL = 0.05
STRANDS, SLOTS = 8, 4
MOD_INDEX = 0.05           # Modulation depth for lattice -> RF

# -----------------------------
# Simple lattice
# -----------------------------
lattice = np.random.rand(STRANDS, SLOTS)

def lattice_to_carrier(lattice, carrier_freq=CARRIER_FREQ, sample_rate=SAMPLE_RATE):
    """
    Map lattice values to a carrier waveform (FM-style)
    lattice: STRANDS x SLOTS, values 0..1 (normalized)
    """
    # Flatten lattice to 1D
    flat = lattice.flatten()
    t = np.arange(len(flat)) / sample_rate
    # Carrier waveform (FM-modulated by lattice)
    carrier = np.cos(2 * np.pi * (carrier_freq + MOD_INDEX * flat) * t)
    return carrier

# -----------------------------
# Simulate OTA transmission
# -----------------------------
def transmit_lattice(node_lattice):
    # Convert lattice to analog carrier
    waveform = lattice_to_carrier(node_lattice)
    # Here you would use RAK4630 DAC / LoRa send to output the waveform
    # For simulation, just return the waveform
    return waveform

# -----------------------------
# Node update loop (example)
# -----------------------------
def node_tick(node_id):
    global lattice
    tick = 0
    while True:
        # Example: small random update to lattice
        lattice += 0.01 * (np.random.rand(STRANDS, SLOTS) - 0.5)
        lattice = np.clip(lattice, 0, 1)
        
        # Map lattice to RF waveform
        waveform = transmit_lattice(lattice)
        
        # For simulation: measure RMS or avg value
        avg = np.mean(waveform)
        print(f"[Node {node_id} Tick {tick}] Waveform avg: {avg:.3f}")
        
        tick += 1
        time.sleep(TICK_INTERVAL)

# -----------------------------
# Start multiple node threads
# -----------------------------
for i in range(4):
    threading.Thread(target=node_tick, args=(i,), daemon=True).start()

# -----------------------------
# Keep main alive
# -----------------------------
while True:
    time.sleep(1)
