import numpy as np
import threading, time

NODE_COUNT = 16
STRANDS, SLOTS = 8, 4
TICK_INTERVAL = 0.05
NEIGHBOR_COUNT = 4  # Each node connects to nearest 4 neighbors

# -----------------------------
# Phyllotaxis positions
# -----------------------------
def phyllotaxis_positions(n, c=1.0):
    golden_angle = np.pi * (3 - np.sqrt(5))
    positions = []
    for k in range(n):
        r = c * np.sqrt(k)
        theta = k * golden_angle
        x, y = r * np.cos(theta), r * np.sin(theta)
        positions.append((x, y))
    return np.array(positions)

positions = phyllotaxis_positions(NODE_COUNT)
nodes = []

# -----------------------------
# Node class
# -----------------------------
class Node:
    def __init__(self, idx, pos):
        self.idx = idx
        self.pos = pos
        self.lattice = np.zeros((STRANDS, SLOTS))
        self.neighbors = []
        self.lock = threading.Lock()

    def update_neighbors(self, all_nodes):
        # Sort by distance and pick closest NEIGHBOR_COUNT
        distances = np.linalg.norm(all_nodes - self.pos, axis=1)
        nearest = np.argsort(distances)[1:NEIGHBOR_COUNT+1]  # skip self
        self.neighbors = nearest

    def propagate(self):
        # Pull average from neighbors
        with self.lock:
            for n_idx in self.neighbors:
                neighbor = nodes[n_idx]
                with neighbor.lock:
                    self.lattice += 0.25 * (neighbor.lattice - self.lattice)  # simple averaging

# -----------------------------
# Initialize nodes
# -----------------------------
for i, pos in enumerate(positions):
    nodes.append(Node(i, pos))
for node in nodes:
    node.update_neighbors(positions)

# -----------------------------
# Tick loop
# -----------------------------
def tick_loop():
    while True:
        for node in nodes:
            node.propagate()
        # Optional: print global summary
        avg = np.mean([node.lattice.mean() for node in nodes])
        print(f"Global lattice avg: {avg:.3f}")
        time.sleep(TICK_INTERVAL)

threading.Thread(target=tick_loop, daemon=True).start()
while True:
    time.sleep(1)
