import tkinter as tk
from tkinter import ttk
import math

class TabSimulator:
    def __init__(self, notebook):
        self.frame = tk.Frame(notebook)

        self.m = 0.0        # Masa
        self.h0 = 0.0       # Altura inicial (0..2m)
        self.trayectoria = []
        self.t_final = 0
        self.proyectil = None
        self.vel_text = None
        self.current_canvas_width = 1
        self.current_canvas_height = 1

        # ============ Estructura general ============
        # TOP
        self.frame_top = tk.Frame(self.frame)
        self.frame_top.pack(side="top", fill="x", padx=5, pady=5)

        # MIDDLE (canvas)
        self.frame_middle = tk.Frame(self.frame)
        self.frame_middle.pack(side="top", fill="both", expand=True)

        # BOTTOM (slider + energía + log)
        self.frame_bottom = tk.Frame(self.frame)
        self.frame_bottom.pack(side="bottom", fill="both", expand=False)

        #   - Izquierda: slider + energía
        self.frame_slider_and_energy = tk.Frame(self.frame_bottom)
        self.frame_slider_and_energy.pack(side="left", fill="y", padx=5, pady=5)

        self.frame_slider = tk.Frame(self.frame_slider_and_energy)
        self.frame_slider.pack(side="top", fill="x", padx=5, pady=5)

        self.frame_energy = tk.Frame(self.frame_slider_and_energy, bd=2, relief="groove")
        self.frame_energy.pack(side="bottom", fill="both", expand=False, padx=5, pady=5)

        #   - Derecha: log
        self.frame_log = tk.Frame(self.frame_bottom)
        self.frame_log.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        # ============ Fila superior de widgets ============
        tk.Label(self.frame_top, text="Parámetro:").grid(row=0, column=0, sticky="w")
        self.parametro_var = tk.StringVar()
        self.combo_param = ttk.Combobox(
            self.frame_top, textvariable=self.parametro_var,
            values=["Velocidad (m/s)", "Alcance (m)"], width=15
        )
        self.combo_param.grid(row=0, column=1, padx=5)
        self.combo_param.current(0)

        self.entry_v0 = tk.Entry(self.frame_top, width=8)
        self.entry_v0.grid(row=0, column=2, padx=5)

        tk.Label(self.frame_top, text="Ángulo (grados):").grid(row=0, column=3, sticky="w")
        self.entry_alpha = tk.Entry(self.frame_top, width=8)
        self.entry_alpha.grid(row=0, column=4, padx=5)

        tk.Label(self.frame_top, text="Masa (kg):").grid(row=0, column=5, sticky="w")
        self.entry_masa = tk.Entry(self.frame_top, width=8)
        self.entry_masa.grid(row=0, column=6, padx=5)

        self.check_rozamiento = tk.BooleanVar()
        self.check_rozamiento.set(False)
        self.chk = tk.Checkbutton(
            self.frame_top, text="Incluir rozamiento",
            variable=self.check_rozamiento,
            command=self.on_toggle_rozamiento
        )
        self.chk.grid(row=0, column=7, padx=15)

        tk.Label(self.frame_top, text="Coef. (b):").grid(row=0, column=8, sticky="e")
        self.entry_b = tk.Entry(self.frame_top, width=8, state="disabled")
        self.entry_b.grid(row=0, column=9, padx=5)

        # Altura inicial (0..2)
        tk.Label(self.frame_top, text="Altura (m):").grid(row=0, column=10, sticky="e")
        self.entry_h0 = tk.Entry(self.frame_top, width=8)
        self.entry_h0.grid(row=0, column=11, padx=5)
        self.entry_h0.insert(0, "0.0")  # por defecto

        # Botón Calcular
        self.button_calcular = tk.Button(
            self.frame_top, text="Calcular",
            command=self.calcular_trayectoria
        )
        self.button_calcular.grid(row=0, column=12, padx=10)

        # Cajitas para pos X e Y
        tk.Label(self.frame_top, text="Pos X (m):").grid(row=0, column=13, padx=5)
        self.entry_pos_x = tk.Entry(self.frame_top, width=8)
        self.entry_pos_x.grid(row=0, column=14, padx=5)

        tk.Label(self.frame_top, text="Pos Y (m):").grid(row=0, column=15, padx=5)
        self.entry_pos_y = tk.Entry(self.frame_top, width=8)
        self.entry_pos_y.grid(row=0, column=16, padx=5)

        # ============ Canvas ============
        self.canvas = tk.Canvas(self.frame_middle, bg="white")
        self.canvas.pack(fill="both", expand=True)
        self.canvas.bind("<Configure>", self.on_resize)

        # ============ Slider tiempo ============
        self.slider_time = tk.Scale(
            self.frame_slider, from_=0, to=1, resolution=0.01,
            orient=tk.HORIZONTAL, label="Tiempo (s):",
            command=self.actualizar_posicion
        )
        self.slider_time.pack(fill="x")

        # ============ Cuadro energía ============
        tk.Label(self.frame_energy, text="Energía mecánica", font=("Arial", 10, "bold")).pack()

        self.label_Ec = tk.Label(self.frame_energy, text="Ec: 0.0 J")
        self.label_Ec.pack(anchor="w", padx=5)

        self.label_Ep = tk.Label(self.frame_energy, text="Ep: 0.0 J")
        self.label_Ep.pack(anchor="w", padx=5)

        self.label_Etot = tk.Label(self.frame_energy, text="E_total: 0.0 J")
        self.label_Etot.pack(anchor="w", padx=5)

        self.label_Esobredim = tk.Label(self.frame_energy, text="E_total x1.15: 0.0 J")
        self.label_Esobredim.pack(anchor="w", padx=5)
        
        self.energy_required = 0.0

        # Logger
        self.text_log = tk.Text(self.frame_log, height=2, state="normal")
        self.text_log.pack(fill="both", expand=True)

    def set_log_message(self, mensaje, bg_color="white", fg_color="black"):
        self.text_log.config(state="normal", bg=bg_color, fg=fg_color)
        self.text_log.delete("1.0", tk.END)
        self.text_log.insert(tk.END, mensaje + "\n")
        self.text_log.config(state="disabled")

    def set_b_value(self, new_b):
        self.entry_b.config(state="normal")
        self.entry_b.delete(0, tk.END)
        self.entry_b.insert(0, f"{new_b:.4f}")
        self.entry_b.config(state="disabled")

    def on_toggle_rozamiento(self):
        if self.check_rozamiento.get():
            self.entry_b.config(state="normal")
        else:
            self.entry_b.config(state="disabled")

    def on_resize(self, event):
        self.current_canvas_width = event.width
        self.current_canvas_height = event.height
        if self.trayectoria:
            self.dibujar_trayectoria()

    def calcular_trayectoria(self):
        # 1) Lee alpha, masa
        try:
            alpha_deg = float(self.entry_alpha.get())
            self.m = float(self.entry_masa.get())
        except ValueError:
            self.set_log_message("Error: revisa (ángulo, masa).", "red", "white")
            return

        # 2) Altura
        try:
            h0_val = float(self.entry_h0.get())
        except ValueError:
            h0_val = 0.0
        if h0_val < 0:  h0_val = 0.0
        if h0_val > 2:  h0_val = 2.0
        self.h0 = h0_val

        # 3) Validar ángulo
        if alpha_deg < 0 or alpha_deg > 90:
            self.set_log_message("Introduce un ángulo entre 0 y 90", "red","white")
            return

        alpha_rad = math.radians(alpha_deg)

        # 4) Leer param (Vel/Alcance)
        modo = self.parametro_var.get()
        try:
            valor = float(self.entry_v0.get())
        except ValueError:
            self.set_log_message("Error en el valor (vel/alcance).", "red","white")
            return

        v0 = 0.0
        if modo == "Velocidad (m/s)":
            v0 = valor
        else:
            # Modo alcance
            X_final = valor
            if not self.check_rozamiento.get():
                v0_min=0.0
                v0_max=1000.0
                for _ in range(100):
                    guess=0.5*(v0_min+v0_max)
                    dist_alcanzado = self.simular_dist_sin_rozamiento(guess, alpha_rad, self.h0)
                    if abs(dist_alcanzado - X_final)<0.1:
                        v0=guess
                        break
                    if dist_alcanzado<X_final:
                        v0_min=guess
                    else:
                        v0_max=guess
                    v0=guess
            else:
                v0_min=0.0
                v0_max=1000.0
                for _ in range(100):
                    guess=0.5*(v0_min+v0_max)
                    dist_alcanzado = self.simular_dist_con_rozamiento(guess, alpha_rad, self.h0)
                    if abs(dist_alcanzado - X_final)<0.1:
                        v0=guess
                        break
                    if dist_alcanzado<X_final:
                        v0_min=guess
                    else:
                        v0_max=guess
                    v0=guess

        if v0<=0:
            self.set_log_message("No se pudo determinar v0>0", "red","white")
            return

        b=0.0
        if self.check_rozamiento.get():
            try:
                b = float(self.entry_b.get())
            except:
                self.set_log_message("Coef rozamiento no válido", "red","white")
                return

        # 5) Integramos la trayectoria
        self.canvas.delete("all")
        self.trayectoria.clear()
        self.proyectil=None
        self.vel_text=None

        dt=0.01
        t=0.0
        x=0.0
        y=self.h0
        vx = v0*math.cos(alpha_rad)
        vy = v0*math.sin(alpha_rad)

        if not self.check_rozamiento.get():
            # sin rozamiento
            while True:
                self.trayectoria.append((t,x,y,vx,vy))
                vy_new = vy-9.8*dt
                x_new = x+vx*dt
                y_new = y+vy*dt
                t+=dt
                vx= vx
                vy= vy_new
                x= x_new
                y= y_new
                if y<=0 and t>0.01:
                    break
        else:
            # con rozamiento lineal
            while True:
                self.trayectoria.append((t,x,y,vx,vy))
                ax=-(b/self.m)*vx
                ay=-9.8-(b/self.m)*vy
                vx_new = vx+ax*dt
                vy_new = vy+ay*dt
                x_new = x+vx_new*dt
                y_new = y+vy_new*dt
                t+=dt
                vx= vx_new
                vy= vy_new
                x= x_new
                y= y_new
                if y<=0 and t>0.01:
                    break

        self.t_final = t
        self.slider_time.config(from_=0, to=self.t_final)
        self.slider_time.set(0)

        self.dibujar_trayectoria()
        self.set_log_message(f"Cálculo OK. v0={v0:.2f} m/s", "green","white")

    def simular_dist_sin_rozamiento(self, v0_guess, alpha_rad, h0):
        dt=0.01
        x=0.0
        y=h0
        vx=v0_guess*math.cos(alpha_rad)
        vy=v0_guess*math.sin(alpha_rad)
        t=0.0
        while True:
            vy_new= vy-9.8*dt
            x_new= x+ vx*dt
            y_new= y+ vy*dt
            t+=dt
            vx= vx
            vy= vy_new
            x= x_new
            y= y_new
            if y<=0 and t>0.01:
                break
        return x

    def simular_dist_con_rozamiento(self, v0_guess, alpha_rad, h0):
        dt=0.01
        x=0.0
        y=h0
        vx=v0_guess*math.cos(alpha_rad)
        vy=v0_guess*math.sin(alpha_rad)
        t=0.0
        try:
            b=float(self.entry_b.get())
        except:
            b=0.1
        while True:
            ax=-(b/self.m)*vx
            ay=-9.8-(b/self.m)*vy
            vx_new= vx+ax*dt
            vy_new= vy+ay*dt
            x_new= x+vx_new*dt
            y_new= y+vy_new*dt
            t+=dt
            vx= vx_new
            vy= vy_new
            x= x_new
            y= y_new
            if y<=0 and t>0.01:
                break
        return x

    def dibujar_trayectoria(self):
        if not self.trayectoria:
            return

        min_x = min(pt[1] for pt in self.trayectoria)
        max_x = max(pt[1] for pt in self.trayectoria)
        min_y = min(pt[2] for pt in self.trayectoria)
        max_y = max(pt[2] for pt in self.trayectoria)

        rx = max_x - min_x
        ry = max_y - min_y
        if rx<1e-9: rx=1.0
        if ry<1e-9: ry=1.0

        w = self.current_canvas_width
        h = self.current_canvas_height
        margen=20
        scale = min((w-2*margen)/rx, (h-2*margen)/ry)

        self.canvas.delete("all")
        pts=[]
        for (tt,xx,yy,vx_,vy_) in self.trayectoria:
            sx = margen + (xx - min_x)*scale
            sy = (h - margen) - (yy - min_y)*scale
            pts.append((sx, sy))

        for i in range(len(pts)-1):
            x1,y1=pts[i]
            x2,y2=pts[i+1]
            self.canvas.create_line(x1,y1,x2,y2,fill="blue")

        if pts:
            x0,y0 = pts[0]
            r=5
            self.proyectil = self.canvas.create_oval(x0-r,y0-r, x0+r,y0+r, fill="red")
            self.vel_text  = self.canvas.create_text(x0+15,y0, text="v=0.00 m/s", fill="black")

        self.scale=scale
        self.margen=margen
        self.min_x=min_x
        self.min_y=min_y

        self.actualizar_energia(0)

    def actualizar_posicion(self, val):
        if not self.trayectoria or not self.proyectil:
            return

        t_slider = float(val)
        if t_slider<=0:
            x_real=self.trayectoria[0][1]
            y_real=self.trayectoria[0][2]
            vx_real=self.trayectoria[0][3]
            vy_real=self.trayectoria[0][4]
        elif t_slider>=self.t_final:
            x_real=self.trayectoria[-1][1]
            y_real=self.trayectoria[-1][2]
            vx_real=self.trayectoria[-1][3]
            vy_real=self.trayectoria[-1][4]
        else:
            tiempos=[p[0] for p in self.trayectoria]
            idx=0
            while idx<len(self.trayectoria)-1 and tiempos[idx+1]<t_slider:
                idx+=1
            x_real=self.trayectoria[idx][1]
            y_real=self.trayectoria[idx][2]
            vx_real=self.trayectoria[idx][3]
            vy_real=self.trayectoria[idx][4]

        # Pos en canvas
        w = self.current_canvas_width
        h = self.current_canvas_height
        sx = self.margen + (x_real - self.min_x)*self.scale
        sy = (h - self.margen) - (y_real - self.min_y)*self.scale

        coords_act = self.canvas.coords(self.proyectil)
        x_c=(coords_act[0]+coords_act[2])/2
        y_c=(coords_act[1]+coords_act[3])/2
        dx=sx-x_c
        dy=sy-y_c
        self.canvas.move(self.proyectil, dx, dy)

        vel = math.sqrt(vx_real**2 + vy_real**2)
        if self.vel_text:
            coords_text=self.canvas.coords(self.vel_text)
            xt=coords_text[0]
            yt=coords_text[1]
            dx_t=sx-xt
            dy_t=(sy+15)-yt
            self.canvas.move(self.vel_text, dx_t, dy_t)
            self.canvas.itemconfig(self.vel_text, text=f"v={vel:.2f} m/s")

        # Pos X e Y (en metros, no en pixeles)
        self.entry_pos_x.delete(0, tk.END)
        self.entry_pos_x.insert(0, f"{x_real:.2f}")

        self.entry_pos_y.delete(0, tk.END)
        self.entry_pos_y.insert(0, f"{y_real:.2f}")

        self.actualizar_energia(t_slider)

    def actualizar_energia(self, t_slider):
        if not self.trayectoria:
            return

        if t_slider<=0:
            vx_=self.trayectoria[0][3]
            vy_=self.trayectoria[0][4]
            x_=self.trayectoria[0][1]
            y_=self.trayectoria[0][2]
        elif t_slider>=self.t_final:
            vx_=self.trayectoria[-1][3]
            vy_=self.trayectoria[-1][4]
            x_=self.trayectoria[-1][1]
            y_=self.trayectoria[-1][2]
        else:
            tiempos=[p[0] for p in self.trayectoria]
            idx=0
            while idx<len(self.trayectoria)-1 and tiempos[idx+1]<t_slider:
                idx+=1
            vx_=self.trayectoria[idx][3]
            vy_=self.trayectoria[idx][4]
            x_=self.trayectoria[idx][1]
            y_=self.trayectoria[idx][2]

        Ec = 0.5*self.m*(vx_**2 + vy_**2)
        Ep=0.0
        if y_>0:
            Ep=self.m*9.8*y_
        E_tot=Ec+Ep
        self.energy_required = E_tot
        E_sobredim=1.15*E_tot

        self.label_Ec.config(text=f"Ec: {Ec:.2f} J")
        self.label_Ep.config(text=f"Ep: {Ep:.2f} J")
        self.label_Etot.config(text=f"E_total: {E_tot:.2f} J")
        self.label_Esobredim.config(text=f"E_total x1.15: {E_sobredim:.2f} J")
        
    def get_energy_required(self):
        return self.energy_required

