import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np

TEXT_FONT = ('DejaVu Sans', 13)
SLIDER_FONT = ('DejaVu Sans', 9)
SINGLE_CHAR_BUTTON_WIDTH = 3
GRAL_PAD = 5
GRAL_MARGIN = 50
MARGIN_SLIDER = 25
TEXT_BBOX = dict(boxstyle='round', edgecolor='none', facecolor='white', alpha=0.7)
WHITE_TEXT_BBOX = dict(boxstyle='round', edgecolor='none', facecolor='white', alpha=1)
POINT_TEXT_PAD = 0.2
LABEL_GRAL_WIDTH = 10

class Controlador(tk.Tk):
    def __init__(self, title, firstFrame, firstFrameArgs=(), *, height=None, width=None):
        super().__init__()
        self.configure(bg='white', highlightbackground='white', highlightthickness=0, bd=0)
        self.option_add('*Background', 'white')
        self.option_add('*highlightBackground', 'white')
        self.option_add('*highlightThickness', 0)
        
        if height == None:
            self.height = 650
        else:
            self.height = height
        if width == None:
            self.width = 450
        else:
            self.width = width
            
        self.geometry(f'{self.width}x{self.height}')
        self.title(title)
        self.setFrame(firstFrame, args=firstFrameArgs)

    def setFrame(self, frame: tk.Frame, *, args: tuple=()) -> tk.Frame:
        self.currFrame = frame(self, *args)
        self.showFrame(self.currFrame)

    def showFrame(self, frame: tk.Tk) -> None:
        frame.pack(fill='both', expand=True)

    def hideFrame(self, frame: tk.Tk) -> None:
        frame.pack_forget()
        self.currFrame = None

    def getCurrFrame(self):
        return self.currFrame

    def warningField(self, field, ms=1000):
        changeColor = lambda color: field.config(highlightbackground=color, highlightcolor=color, highlightthickness=1)
        changeColor('red')
        field.after(ms, lambda: changeColor('white'))

    def addChar(self, char: str, field, leftShift=None) -> None:
        field.insert(tk.INSERT, char)

        if leftShift != None:
            field.icursor(field.index(tk.INSERT) - leftShift)
        
class SimpleInteractiveGraphFrame(tk.Frame):
    def setPanels(self, *, ejes=1, horizontal=True, toolBar=True, showEjes=True):

        # Panel principal
        panel = tk.Frame(self, bg='white')
        panel.pack(fill='both', expand=True)
        
        # 1. Panel superior (gráfica)
        panelUp = tk.Frame(panel, bg='white')
        panelUp.pack(fill='both', expand=True, padx=GRAL_MARGIN)
        self.fig = plt.figure()
        
        if ejes == 1:
            self.ejes = self.fig.add_subplot()
        else:
            self.ejes = []
            for i in range(ejes):
                if horizontal:
                    self.ejes.append(self.fig.add_subplot(1, ejes, i + 1))
                else:
                    self.ejes.append(self.fig.add_subplot(ejes, 1, i + 1))
                self.ejes[i].grid(True)

        self.canvas = FigureCanvasTkAgg(self.fig, master=panelUp)
        self.canvas.get_tk_widget().pack(fill='both', expand=True)

        if toolBar:
            NavigationToolbar2Tk(self.canvas, panelUp).update()
        if not showEjes:
            self.ejes.axis('off')
            
        plt.tight_layout()

        # 2. Panel inferior (interacción con la gráfica)
        self.panelDown = tk.Frame(panel)
        self.panelDown.pack(fill='x', padx=GRAL_MARGIN, pady=MARGIN_SLIDER)

    def setLineasEjes(self, ejes, *, variosEjes=False):
        if variosEjes:
            for eje in ejes:
                eje.axhline(0, color='black', linestyle='dotted', zorder=-1)
                eje.axvline(0, color='black', linestyle='dotted', zorder=-1)
        else:
            ejes.axhline(0, color='black', linestyle='dotted', zorder=-1)
            ejes.axvline(0, color='black', linestyle='dotted', zorder=-1)

class TrigonometriaFrame(SimpleInteractiveGraphFrame):
    def setCirculoGraph(self):
        # Configuraciones
        self.ejes[0].set_title('Círculo unitario')
        self.ejes[0].set_box_aspect(1)
        self.ejes[0].set_xlim(-1.25, 1.25)
        self.ejes[0].set_ylim(-1.25, 1.25)

        # Figuras
        self.t = np.pi/4
        self.ejes[0].add_patch(plt.Circle((0, 0), 1, fill=False))
        self.circlePoint, = self.ejes[0].plot([np.cos(self.t)], [np.sin(self.t)], color='red', marker='o')
        self.circleLine, = self.ejes[0].plot([0, np.cos(self.t)], [0, np.sin(self.t)], color='red')
        self.circleGuiaH, = self.ejes[0].plot([0, np.cos(self.t)], [0, 0], color='blue', linestyle='dashed')
        self.circleGuiaV, = self.ejes[0].plot([np.cos(self.t), np.cos(self.t)], [0, np.sin(self.t)], color='blue', linestyle='dashed')

    def updateCirculoGraph(self, _=None, *, draw=False):
        self.circlePoint.set_data([np.cos(self.t)], [np.sin(self.t)])
        self.circleLine.set_data([0, np.cos(self.t)], [0, np.sin(self.t)])
        self.circleGuiaH.set_data([0, np.cos(self.t)], [0, 0])
        self.circleGuiaV.set_data([np.cos(self.t), np.cos(self.t)], [0, np.sin(self.t)])

        # Color de las proyecciones en función del cuadrante
        self.circleGuiaH.set_color('red' if np.cos(self.t) < 0 else 'green')
        self.circleGuiaV.set_color('red' if np.sin(self.t) < 0 else 'green')
