import tkinter as tk
from tkinter import ttk
from collections import deque
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pywifi
import time

# --------------------------
# Parameters
# --------------------------
N_POINTS = 400          # Number of points in lattice / trajectory
dt = 0.05               # Animation interval (s)
k_soft = 1.0            # Scaling factor for soft envelope

# --------------------------
# Tkinter setup
# --------------------------
root = tk.Tk()
root.title("HDGL Wi-Fi EMF Analog Visualizer")

# --------------------------
# Matplotlib figure
# --------------------------
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111, projection='3d')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

# --------------------------
# Slider frame
# --------------------------
slider_frame = tk.Frame(root)
slider_frame.pack(side=tk.BOTTOM, fill=tk.X)

def make_slider(label, minv, maxv, default, row):
    tk.Label(slider_frame, text=label).grid(row=row, column=0, sticky='w')
    var = tk.DoubleVar(value=default)
    slider = tk.Scale(slider_frame, from_=minv, to=maxv, resolution=0.01,
                      orient=tk.HORIZONTAL, variable=var)
    slider.grid(row=row, column=1, sticky='we')
    return var

morph_var = make_slider("Morph (Polar→Cartesian)", 0, 1, 0, 0)
ampl_var  = make_slider("Amplitude Scale", 0, 2, 1, 1)

# --------------------------
# HDGL Lattice (Phyllotaxis)
# --------------------------
phi = (1 + np.sqrt(5)) / 2
theta = 2 * np.pi / phi
radii = np.sqrt(np.arange(N_POINTS))
angles = np.arange(N_POINTS) * theta
zs = np.linspace(-1, 1, N_POINTS)

def lattice_coords(N, t=0, morph=0):
    r = radii
    x_p = r * np.cos(angles)
    y_p = r * np.sin(angles)
    z_p = zs

    # Morph polar -> Cartesian
    x = x_p * (1-morph) + np.linspace(-1,1,N) * morph
    y = y_p * (1-morph) + np.linspace(-1,1,N) * morph
    z = z_p * (1-morph) + np.linspace(-1,1,N) * morph
    return x, y, z

# --------------------------
# Wi-Fi scanning
# --------------------------
wifi = pywifi.PyWiFi()
iface = wifi.interfaces()[0]

def scan_networks():
    iface.scan()
    time.sleep(0.3)  # allow scan to complete
    results = iface.scan_results()
    networks = {}
    for r in results:
        ssid = r.ssid if r.ssid else f"SSID_{r.bssid}"
        rssi = r.signal
        networks[ssid] = rssi
    return networks

# --------------------------
# Soft envelope filter
# --------------------------
def soft_env(s):
    return np.tanh(k_soft * s)

# --------------------------
# Animation buffers
# --------------------------
network_buffers = {}  # ssid -> deque of (x,y,z)
lines = {}            # ssid -> Line3D
max_networks = 16
colors = plt.cm.get_cmap('hsv', max_networks)

# --------------------------
# Animation update
# --------------------------
def update(frame):
    morph = morph_var.get()
    ampl = ampl_var.get()
    networks = scan_networks()
    x_base, y_base, z_base = lattice_coords(N_POINTS, frame*dt, morph)

    for i, (ssid, rssi) in enumerate(networks.items()):
        # Normalize RSSI for amplitude
        amp_mod = ampl * soft_env(max(-1, min(1, (rssi + 100)/60)))
        idx = int(i * N_POINTS / max_networks)  # map network to lattice index
        x = x_base[idx] * amp_mod
        y = y_base[idx] * amp_mod
        z = z_base[idx] * amp_mod

        if ssid not in network_buffers:
            network_buffers[ssid] = deque(maxlen=N_POINTS)
        network_buffers[ssid].append((x, y, z))

        buf = np.array(network_buffers[ssid])
        x_plot = buf[:,0]
        y_plot = buf[:,1]
        z_plot = buf[:,2]

        if ssid not in lines:
            lines[ssid], = ax.plot(x_plot, y_plot, z_plot, color=colors(i), lw=2)
        else:
            lines[ssid].set_data(x_plot, y_plot)
            lines[ssid].set_3d_properties(z_plot)

    ax.set_xlim(-5,5)
    ax.set_ylim(-5,5)
    ax.set_zlim(-2,2)
    return list(lines.values())

ani = FuncAnimation(fig, update, interval=dt*1000, blit=False)

# --------------------------
# Run Tkinter
# --------------------------
root.mainloop()
