# tab_power.py

import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import math

class TabPower:
    def __init__(self, notebook, tab_coil, tab_simulator):
        """
        Pestaña que combina:
          - Dimensionamiento (C o V0) por energía objetivo.
          - Visualización/animación del proyectil pasando por n etapas.
        """
        self.notebook = notebook
        self.tab_coil = tab_coil
        self.tab_simulator = tab_simulator
        
        # Frame principal
        self.frame = tk.Frame(notebook)
        self.frame.pack(fill="both", expand=True)

        # Layout superior: panel de inputs y resultados
        self.frame_top = tk.Frame(self.frame)
        self.frame_top.pack(side="top", fill="x")

        # Layout inferior: canvas animación + slider
        self.frame_bottom = tk.Frame(self.frame)
        self.frame_bottom.pack(side="bottom", fill="both", expand=True)

        # -----------------------------
        # PARTE (A): INPUTS Y LOG (arriba)
        # -----------------------------
        self.frame_inputs = tk.Frame(self.frame_top)
        self.frame_inputs.pack(side="left", fill="y", padx=5, pady=5)

        self.frame_results = tk.Frame(self.frame_top, bd=2, relief="groove")
        self.frame_results.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        # 1) Modo dimensionado
        self.mode_var = tk.StringVar(value="Dimensionar C")
        ttk.Label(self.frame_inputs, text="Modo Dimensionado:").pack(anchor="w")
        self.combo_mode = ttk.Combobox(self.frame_inputs, textvariable=self.mode_var,
            values=["Dimensionar C", "Dimensionar V0"], state="readonly")
        self.combo_mode.bind("<<ComboboxSelected>>", self.on_mode_change)
        self.combo_mode.pack(pady=2)

        # 2) Número de etapas
        ttk.Label(self.frame_inputs, text="Número de etapas (n):").pack(anchor="w")
        self.var_n = tk.IntVar(value=3)
        tk.Entry(self.frame_inputs, textvariable=self.var_n, width=8).pack(pady=2)

        # 3) Capacidad
        ttk.Label(self.frame_inputs, text="Capacidad (µF):").pack(anchor="w")
        self.var_C = tk.DoubleVar(value=100.0)
        self.entry_C = tk.Entry(self.frame_inputs, textvariable=self.var_C, width=8)
        self.entry_C.pack(pady=2)

        # 4) Tensión
        ttk.Label(self.frame_inputs, text="Tensión (V):").pack(anchor="w")
        self.var_V0 = tk.DoubleVar(value=200.0)
        self.entry_V0 = tk.Entry(self.frame_inputs, textvariable=self.var_V0, width=8)
        self.entry_V0.pack(pady=2)

        # 5) Tiempo por etapa
        ttk.Label(self.frame_inputs, text="Tiempo/etapa (ms):").pack(anchor="w")
        self.var_time_ms = tk.DoubleVar(value=10.0)
        tk.Entry(self.frame_inputs, textvariable=self.var_time_ms, width=8).pack(pady=2)

        # Botones
        ttk.Button(self.frame_inputs, text="Dimensionar", command=self.dimensionar).pack(pady=5)
        ttk.Button(self.frame_inputs, text="Simular Movimiento", command=self.simular_movimiento).pack(pady=5)

        # LOG de resultados
        tk.Label(self.frame_results, text="Resultados", font=("Arial", 10, "bold")).pack(anchor="nw", padx=5, pady=5)
        self.log_area = ScrolledText(self.frame_results, wrap="word", height=15, width=50)
        self.log_area.pack(fill="both", expand=True, padx=5, pady=5)
        self.log_area.configure(state="disabled")

        # -----------------------------
        # PARTE (B): CANVAS ANIMACIÓN (abajo)
        # -----------------------------
        self.canvas = tk.Canvas(self.frame_bottom, bg="white")
        self.canvas.pack(side="left", fill="both", expand=True)

        # Panel con slider
        self.frame_slider = tk.Frame(self.frame_bottom)
        self.frame_slider.pack(side="right", fill="y", padx=5, pady=5)

        tk.Label(self.frame_slider, text="Tiempo (ms):").pack()
        self.slider_time = tk.Scale(self.frame_slider, from_=0, to=10, resolution=1,
                                    orient=tk.VERTICAL, command=self.on_slider_move)
        self.slider_time.pack(fill="y", expand=True)

        # Variables internas
        self.proyectil_id = None
        self.trayectoria = []  # Lista (t, x)
        self.t_total = 0.0
        self.old_canvas_w = 1
        self.old_canvas_h = 1

        # Inicializar modo
        self.on_mode_change()

        # Responder a resize
        self.canvas.bind("<Configure>", self.on_resize)

    # ============== LOG FUNC =============
    def log_message(self, txt, mode="info"):
        self.log_area.config(state="normal")
        if mode=="error":
            self.log_area.insert("end", "ERROR: " + txt + "\n", "error")
        else:
            self.log_area.insert("end", txt + "\n", mode)
        self.log_area.see("end")
        self.log_area.config(state="disabled")

    # ============== MODO DIMENSIONADO =============
    def on_mode_change(self, event=None):
        mode = self.mode_var.get()
        if mode == "Dimensionar C":
            # C se dimensiona => el user fija V0 => C inhabilitado
            self.entry_C.config(state="disabled")
            self.entry_V0.config(state="normal")
        else:
            # Dimensionar V0 => user fija C => V0 inhabilitado
            self.entry_C.config(state="normal")
            self.entry_V0.config(state="disabled")

    # ============== DIMENSIONAR =============
    def dimensionar(self):
        """
        Lógica de bisección (resumida) para encontrar C o V0
        que aporte la energía requerida en n etapas.
        """
        E_target_total = self.tab_simulator.get_energy_required()
        if E_target_total <= 0:
            self.log_message("E_target <= 0; revisa TabSimulator.", "error")
            return

        n = self.var_n.get()
        if n<1:
            self.log_message("n<1 invalido.", "error")
            return

        E_target_stage = E_target_total/n
        # Leer L y R
        try:
            L_uH = float(self.tab_coil.var_L.get())
            R_coil = float(self.tab_coil.var_R.get())
        except ValueError:
            self.log_message("No se pudo leer L/R en TabCoil.", "error")
            return
        L_coil = L_uH * 1e-6

        # h_c
        try:
            h_c_mm = float(self.tab_coil.var_h_c.get())
        except ValueError:
            self.log_message("No se pudo leer h_c en TabCoil.", "error")
            return
        h_c = h_c_mm/1000.0

        # Tiempo
        dt_ms = self.var_time_ms.get()
        if dt_ms<=0:
            self.log_message("Tiempo/etapa <=0", "error")
            return
        T_stage = dt_ms*1e-3

        mode = self.mode_var.get()
        if mode=="Dimensionar C":
            # El user ha fijado V0
            try:
                V0_fixed = float(self.var_V0.get())
                if V0_fixed<=0:
                    self.log_message("Tensión <=0", "error")
                    return
            except ValueError:
                self.log_message("Tensión no valida", "error")
                return

            lower, upper = 0.1, 1e6
            def test_fun(C_uF):
                E_stage = self.simular_descarga(C_uF, V0_fixed, L_coil, R_coil, T_stage, h_c)
                return E_stage

        else:
            # Dimensionar V0 => user fija C
            try:
                C_uF_fixed = float(self.var_C.get())
                if C_uF_fixed<=0:
                    self.log_message("Capacidad <=0", "error")
                    return
            except ValueError:
                self.log_message("Capacidad no valida", "error")
                return

            lower, upper = 1.0, 5000.0
            def test_fun(V0):
                E_stage = self.simular_descarga(C_uF_fixed, V0, L_coil, R_coil, T_stage, h_c)
                return E_stage

        E_low = test_fun(lower)
        E_up = test_fun(upper)
        if not ((E_low <= E_target_stage <= E_up) or (E_up <= E_target_stage <= E_low)):
            self.log_message("Posible problema con rangos de bisección", "error")

        iteration_count=0
        max_iter=40
        tol=0.01*E_target_stage
        best_param=0.0
        while iteration_count<max_iter:
            mid=0.5*(lower+upper)
            E_mid = test_fun(mid)
            diff = E_mid - E_target_stage
            if abs(diff)<tol:
                best_param=mid
                break
            # suponer monotonicidad
            if E_mid>E_target_stage:
                upper=mid
            else:
                lower=mid
            iteration_count+=1
            best_param=mid

        E_stage_final = test_fun(best_param)
        E_total_final = E_stage_final*n
        diff_total = E_total_final - E_target_total

        # Resultados
        self.log_message("-"*40, "info")
        self.log_message(f"E_target_total= {E_target_total:.3f} J => E_target_stage= {E_target_stage:.3f} J", "info")
        if mode=="Dimensionar C":
            self.log_message(f"MODO: Dimensionar C => V0= {V0_fixed:.1f} V fijo", "info")
            self.log_message(f"C final= {best_param:.2f} µF", "info")
            # guardamos en var_C
            self.var_C.set(best_param)
        else:
            self.log_message(f"MODO: Dimensionar V0 => C= {self.var_C.get():.1f} µF fijo", "info")
            self.log_message(f"V0 final= {best_param:.2f} V", "info")
            self.var_V0.set(best_param)
        self.log_message(f"E_stage_final= {E_stage_final:.3f} J => E_total= {E_total_final:.3f} J (diff= {diff_total:.3f})", "info")
        self.log_message("-"*40, "info")

    def simular_descarga(self, C_uF, V0, L_coil, R_coil, T_stage, h_c):
        """Versión resumida de la descarga R-L(x)-C, devolviendo la energía consumida en 1 etapa."""
        if C_uF<=0 or V0<=0 or T_stage<=0:
            return 0.0

        # Fijamos 20% de variación de L
        C = C_uF*1e-6
        L0 = 1.2*L_coil
        Lf = 0.8*L_coil

        def L_of_x(x):
            return L0 + (Lf-L0)*(x/h_c) if h_c>1e-9 else L_coil
        def dLdx_of_x(x):
            return (Lf-L0)/h_c if h_c>1e-9 else 0.0

        # Integración Euler
        N_steps=1000
        dt = T_stage/N_steps
        vCap = V0
        i=0.0

        # sup vel cte => x(t)= (h_c/T_stage)*t
        def x_of_t(t):
            return (h_c/T_stage)*t if T_stage>1e-9 else 0.0

        for step in range(N_steps+1):
            x = x_of_t(step*dt)
            Lx = L_of_x(x)
            dLdt = dLdx_of_x(x)*(h_c/T_stage)

            if abs(Lx)>1e-12:
                di_dt = (vCap - R_coil*i - i*dLdt)/Lx
            else:
                di_dt=0.0

            i_new = i+di_dt*dt
            vCap_new = vCap - (i/C)*dt
            if vCap_new<0:
                vCap_new=0
            i = i_new
            vCap = vCap_new

        E_consumida= 0.5*C*(V0**2 - vCap**2)
        if E_consumida<0:
            E_consumida=0.0
        return E_consumida

    # ============== SIMUL MOVIMIENTO ================
    def simular_movimiento(self):
        """
        Genera una trayectoria (t, x) con n etapas => n*h_c longitud total.
        Asume un tiempo total= n*(tiempo por etapa).
        Animación en canvas con slider de 0.. t_total(ms).
        """
        self.trayectoria.clear()

        try:
            n = self.var_n.get()
            h_c_mm = float(self.tab_coil.var_h_c.get())
            dt_ms = self.var_time_ms.get()
        except ValueError:
            self.log_message("Valores invalidos para n, h_c, dt_ms", "error")
            return
        if n<1 or h_c_mm<=0 or dt_ms<=0:
            self.log_message("Parametros invalidos en simular_movimiento", "error")
            return

        h_c = h_c_mm/1000.0
        total_dist = n*h_c
        self.t_total = n*(dt_ms*1e-3)  # seg

        # Generamos 200 puntos
        Npts=200
        dt = self.t_total/(Npts-1)
        # vel cte => v= total_dist/t_total
        if self.t_total<1e-9:
            self.log_message("tiempo total ~0 => no se puede simular", "error")
            return
        v = total_dist/self.t_total

        t=0.0
        for i in range(Npts):
            x = v*t
            self.trayectoria.append((t, x))
            t+=dt

        # Ajustar slider => en ms => 0.. t_total*1000
        self.slider_time.config(
            from_=0, to=int(self.t_total*1000), resolution=1
        )
        self.slider_time.set(0)

        self.log_message(f"Sim. movimiento => dist= {total_dist:.3f} m, t_total= {self.t_total:.3f} s", "info")
        self.dibujar_escena()

    def dibujar_escena(self):
        """Dibuja las bobinas (n rectangulos) en canvas y proyectil en pos inicial."""
        self.canvas.delete("all")
        w = self.canvas.winfo_width()
        h = self.canvas.winfo_height()
        if w<10 or h<10:
            return

        margin=30
        usable_w = w-2*margin
        y_base = h*0.5

        n = self.var_n.get()
        try:
            h_c_mm = float(self.tab_coil.var_h_c.get())
        except:
            h_c_mm=50.0
        h_c = h_c_mm/1000.0
        total_dist = n*h_c

        scale=0.0
        if total_dist>1e-9:
            scale= usable_w/total_dist

        # dibujar n rectangulos
        for stage in range(n):
            x0_m = stage*h_c
            x1_m = (stage+1)*h_c
            x0_px= margin+ x0_m*scale
            x1_px= margin+ x1_m*scale
            self.canvas.create_rectangle(
                x0_px, y_base-10, x1_px, y_base+10,
                fill="orange", outline="black"
            )

        # proyectil => circulo
        proj_r=5
        x_init_px= margin
        y_init_px= y_base
        self.proyectil_id= self.canvas.create_oval(
            x_init_px-proj_r, y_init_px-proj_r,
            x_init_px+proj_r, y_init_px+proj_r,
            fill="red"
        )

    def on_resize(self, event):
        # Redibujamos la escena y recolocamos proyectil
        self.dibujar_escena()
        ms = self.slider_time.get()
        self.mover_proyectil(ms)

    def on_slider_move(self, val):
        ms = float(val)
        self.mover_proyectil(ms)

    def mover_proyectil(self, ms):
        """Mueve el proyectil según el tiempo en ms => t=ms/1000, 
           busco x(t) en self.trayectoria (interp)."""
        if not self.trayectoria:
            return
        t_req= ms*1e-3
        # localizamos t_req en self.trayectoria
        # simple => busco el frame mas cercano
        N= len(self.trayectoria)
        if t_req<= self.trayectoria[0][0]:
            x_m= self.trayectoria[0][1]
        elif t_req>= self.trayectoria[-1][0]:
            x_m= self.trayectoria[-1][1]
        else:
            # busqueda lineal
            for i in range(N-1):
                t0, x0= self.trayectoria[i]
                t1, x1= self.trayectoria[i+1]
                if t_req>= t0 and t_req<= t1:
                    # interpolar
                    alpha= (t_req - t0)/(t1-t0) if abs(t1-t0)>1e-12 else 0.0
                    x_m= x0 + alpha*(x1-x0)
                    break

        # convertir x_m => pix
        w = self.canvas.winfo_width()
        h = self.canvas.winfo_height()
        margin=30
        n = self.var_n.get()
        try:
            h_c_mm= float(self.tab_coil.var_h_c.get())
        except:
            h_c_mm=50.0
        h_c= h_c_mm/1000.0
        total_dist= n*h_c
        usable_w= w-2*margin
        scale=0.0
        if total_dist>1e-9:
            scale= usable_w/total_dist

        x_px= margin+ x_m*scale
        y_base= h*0.5

        if self.proyectil_id is not None:
            coords= self.canvas.coords(self.proyectil_id)
            xc= (coords[0]+ coords[2])/2
            yc= (coords[1]+ coords[3])/2
            dx= x_px- xc
            dy= y_base- yc
            self.canvas.move(self.proyectil_id, dx, dy)
