import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import math as m
from scipy.special import ellipk, ellipe

MU0 = 4.0e-7 * m.pi  # Permeabilidad magnética vacío (H/m)
RHO_CU = 1.68e-8     # Resistividad cobre (Ohm·m) a 20°C

def self_inductance_loop(r, a_eff=1e-4):
    if r <= 0: return 0.0
    return MU0*r*(m.log(8.0*r/a_eff) - 2.0)

def mutual_inductance_coaxial(r1, r2, z_dist):
    if r1 < 1e-12 or r2 < 1e-12: return 0.0
    k_sq = 4.0*r1*r2 / ((r1 + r2)**2 + z_dist**2)
    if k_sq > 1.0: return 0.0
    k = np.sqrt(k_sq)
    if (1.0 - k_sq) < 1e-12: 
        return MU0*min(r1, r2)*m.pi
    Kk, Ek = ellipk(k_sq), ellipe(k_sq)
    factor = MU0*np.sqrt(r1*r2)
    return factor*((2.0 - k)*Kk - 2.0*Ek)/k

def compute_coil_geometry(N_total, R_int, R_ext, espesor_plast, H, wire_d):
    radial_thickness = R_ext - (R_int + 2*espesor_plast)
    if radial_thickness <= 0:
        raise ValueError("No hay espacio radial para el bobinado.")
    max_layers = int(np.floor(radial_thickness / wire_d))
    if max_layers < 1:
        raise ValueError("No cabe ni una capa con el grosor de hilo dado.")
    max_turns_per_layer = int(np.floor(H / wire_d))
    if max_turns_per_layer < 1:
        raise ValueError("No cabe ni una vuelta en la altura dada.")
    max_turns_possible = max_layers * max_turns_per_layer
    if max_turns_possible < N_total:
        raise ValueError(f"Se necesitan {N_total} espiras, pero solo caben {max_turns_possible}.")

    espiras = []
    vueltas_rest = N_total
    layer_idx = 0
    while vueltas_rest > 0 and layer_idx < max_layers:
        r_layer = R_int + espesor_plast + layer_idx*wire_d + wire_d/2.0
        n_vueltas = max_turns_per_layer if vueltas_rest >= max_turns_per_layer else vueltas_rest
        for turn in range(n_vueltas):
            z_i = (turn + 0.5)*wire_d
            espiras.append((r_layer, z_i))
        vueltas_rest -= n_vueltas
        layer_idx += 1
    if vueltas_rest > 0:
        raise ValueError(f"Faltan {vueltas_rest} espiras por ubicar.")

    return espiras

def inductance_from_spiralist(espiras, wire_radius):
    """Suma autoinductancias + mutuas (O(N^2))."""
    N = len(espiras)
    L_total = 0.0
    for i in range(N):
        r_i, z_i = espiras[i]
        L_total += self_inductance_loop(r_i, wire_radius)
        for j in range(i + 1, N):
            r_j, z_j = espiras[j]
            M_ij = mutual_inductance_coaxial(r_i, r_j, abs(z_j - z_i))
            L_total += 2.0*M_ij
    return abs(L_total)

# Importamos la función para dibujar la bobina
from tabs.geometry_viewer import plot_coil_in_frame

class TabCoil:
    def __init__(self, notebook, tab_simulator):
        self.notebook = notebook
        self.tab_simulator = tab_simulator
        self.frame = tk.Frame(notebook)
        self.frame.pack(fill="both", expand=True)

        frame_left = tk.Frame(self.frame); frame_left.pack(side="left", fill="y", padx=5, pady=5)
        frame_right = tk.Frame(self.frame, bd=2, relief="groove")
        frame_right.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        self.frame_2d = tk.Frame(frame_right)
        self.frame_2d.pack(fill="both", expand=True)

        tk.Label(frame_left, text="Parámetros de la Bobina:").pack(anchor="w")
        self.var_N = tk.DoubleVar(value=500)
        self.var_e_pvc = tk.DoubleVar(value=2)
        self.var_r_int = tk.DoubleVar(value=4.035)
        self.var_r_ext = tk.DoubleVar(value=10.64)
        self.var_h_c = tk.DoubleVar(value=53.12)
        self.var_d_cu = tk.DoubleVar(value=0.5)
        self.var_L = tk.StringVar(value="")
        self.var_R = tk.StringVar(value="")

        self.frame_params = tk.Frame(frame_left, bd=1, relief="sunken")
        self.frame_params.pack(fill="x", pady=5)

        def add_param_row(lbl_txt, var, row, w=8):
            tk.Label(self.frame_params, text=lbl_txt).grid(row=row, column=0, sticky="w", padx=5, pady=2)
            tk.Entry(self.frame_params, textvariable=var, width=w).grid(row=row, column=1, padx=5, pady=2)

        rowcount = 0
        add_param_row("N (vueltas):", self.var_N, rowcount); rowcount += 1
        add_param_row("e_pvc (mm):",  self.var_e_pvc, rowcount); rowcount += 1
        add_param_row("r_int (mm):",  self.var_r_int, rowcount); rowcount += 1
        add_param_row("r_ext (mm):",  self.var_r_ext, rowcount); rowcount += 1
        add_param_row("h_c (mm):",    self.var_h_c,   rowcount); rowcount += 1
        add_param_row("d_cu (mm):",   self.var_d_cu,  rowcount); rowcount += 1

        tk.Label(self.frame_params, text="Induct. (µH):").grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
        tk.Entry(self.frame_params, textvariable=self.var_L, width=10, state="readonly").grid(row=rowcount, column=1, padx=5, pady=2)
        rowcount += 1

        tk.Label(self.frame_params, text="R (Ohms):").grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
        tk.Entry(self.frame_params, textvariable=self.var_R, width=10, state="readonly").grid(row=rowcount, column=1, padx=5, pady=2)
        rowcount += 1

        tk.Button(frame_left, text="Refrescar Vista 2D", command=self.refrescar_2d).pack(pady=10)
        tk.Button(frame_left, text="Calcular", command=self.calcular_inductancia).pack(pady=5)

        tk.Label(frame_left, text="Logs:").pack(anchor="w")
        self.log_area = ScrolledText(frame_left, wrap="word", height=8, width=35)
        self.log_area.pack(fill="x", padx=5, pady=5)
        self.log_area.configure(state="disabled")
        self.log_area.tag_config("info", foreground="green")
        self.log_area.tag_config("error", foreground="red")

        self.fig = plt.Figure(figsize=(4,3), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.canvas_2d = FigureCanvasTkAgg(self.fig, master=self.frame_2d)
        self.canvas_2d_widget = self.canvas_2d.get_tk_widget()
        self.canvas_2d_widget.pack(fill="both", expand=True)

        self.refrescar_2d()

    def log_message(self, text, mode="info"):
        self.log_area.configure(state="normal")
        self.log_area.insert("end", text + "\n", mode)
        self.log_area.see("end")
        self.log_area.configure(state="disabled")

    def refrescar_2d(self):
        N_val = self.var_N.get()
        e_pvc_val = self.var_e_pvc.get()
        r_int_val = self.var_r_int.get()
        r_ext_val = self.var_r_ext.get()
        h_c_val = self.var_h_c.get()
        d_cu_val = self.var_d_cu.get()
        self.ax.clear()
        plot_coil_in_frame(self.frame_2d, N_val, e_pvc_val, r_int_val, r_ext_val, h_c_val, d_cu_val)

    def calcular_inductancia(self):
        try:
            N_espiras = int(self.var_N.get())
            e_pvc_val = self.var_e_pvc.get()
            r_int_val = self.var_r_int.get()
            r_ext_val = self.var_r_ext.get()
            h_c_val = self.var_h_c.get()
            d_cu_val = self.var_d_cu.get()
            R_in, R_out = r_int_val/1000.0, r_ext_val/1000.0
            plast = e_pvc_val/1000.0
            altura = h_c_val/1000.0
            diam_hilo = d_cu_val/1000.0

            espiras_xyz = compute_coil_geometry(N_espiras, R_in, R_out, plast, altura, diam_hilo)
            wire_radius = diam_hilo/2.0
            L_h = inductance_from_spiralist(espiras_xyz, wire_radius)
            L_uH = L_h*1e6

            wire_length = 0.0
            for (r_i, _) in espiras_xyz:
                wire_length += 2.0*m.pi*r_i
            wire_area = m.pi*(wire_radius**2)
            R_coil = RHO_CU*(wire_length/wire_area)

            self.var_L.set(f"{L_uH:.3f}")
            self.var_R.set(f"{R_coil:.3f}")
            self.log_message(f"Inductancia: {L_uH:.3f} µH, Resistencia: {R_coil:.3f} Ω", "info")
        except ValueError as e:
            self.var_L.set("Error")
            self.var_R.set("Error")
            self.log_message(f"Error en cálculo: {str(e)}", "error")
        except Exception as ex:
            self.var_L.set("Error")
            self.var_R.set("Error")
            self.log_message(f"Error inesperado: {str(ex)}", "error")
