from .interfaz import *
import numpy as np
from sympy import sympify, lambdify, latex, symbols

WIDTH_FUNC = 20
SLIDER_MAX = 10
SLIDER_INC = 0.1
RESOLUTION = 100

class FrameTransformacion(tk.Frame):
    def __init__(self, controlador):
        self.controlador = controlador
        super().__init__(controlador)

        # Panel principal
        panel = tk.Frame(self)
        panel.pack(fill='both', expand=True)

        # 1. Panel superior (entradas)
        panelUp = tk.Frame(panel)
        panelUp.pack(fill='x', expand=True)

        # 1.1. Panel superior (función)
        panelUpUp = tk.Frame(panelUp)
        self.__defaultF()
        tk.Label(panelUpUp, text='f(x) = ', font=TEXT_FONT, padx=GRAL_PAD).pack(side='left')
        self.fEntry = tk.Entry(panelUpUp, font=TEXT_FONT, width=WIDTH_FUNC)
        self.fEntry.pack(side='left')
        tk.Button(panelUpUp, text='→', font=TEXT_FONT, padx=GRAL_PAD, width=SINGLE_CHAR_BUTTON_WIDTH, command=lambda: self.__updateGraph(ignoreF=False)).pack(side='left', padx=2*GRAL_PAD)
        panelUpUp.pack()

        # 1.2. Panel inferior (símbolos matemáticos)
        panelUpDown = tk.Frame(panelUp)
        tk.Button(panelUpDown, text='⋅', font=TEXT_FONT, width=SINGLE_CHAR_BUTTON_WIDTH, command=lambda: self.controlador.addChar('⋅', self.fEntry)).pack(side='left', padx=GRAL_PAD, pady=GRAL_PAD)
        tk.Button(panelUpDown, text='√', font=TEXT_FONT, width=SINGLE_CHAR_BUTTON_WIDTH, command=lambda: self.controlador.addChar('√()', self.fEntry, leftShift=1)).pack(side='left', padx=GRAL_PAD, pady=GRAL_PAD)
        tk.Button(panelUpDown, text='^', font=TEXT_FONT, width=SINGLE_CHAR_BUTTON_WIDTH, command=lambda: self.controlador.addChar('^()', self.fEntry, leftShift=1)).pack(side='left', padx=GRAL_PAD, pady=GRAL_PAD)
        panelUpDown.pack()

        # 2. Panel central (gráfica)
        panelCenter = tk.Frame(panel)
        panelCenter.pack(fill='y', expand=True, padx=GRAL_MARGIN)
        self.fig = plt.figure()
        self.ejes = self.fig.add_subplot()
        self.ejes.grid(True)
        self.xLim = [0, 1]
        self.canvas = FigureCanvasTkAgg(self.fig, master=panelCenter)
        self.canvas.get_tk_widget().pack(fill='both', expand=True)
        NavigationToolbar2Tk(self.canvas, panelCenter).update()
        plt.tight_layout()

        # 3. Panel inferior (interacción con la gráfica)
        panelDown = tk.Frame(panel)
        panelDown.pack(fill='x', expand=True, padx=GRAL_MARGIN)

        # 3.1. Panel izquierdo (sliders)
        panelDownLeft = tk.Frame(panelDown)
        panelDownLeft.pack(side='left', fill='x', expand=True)

        # 3.1.1. Panel superior (f(x)+k)
        panelDownLeftUp = tk.Frame(panelDownLeft)
        panelDownLeftUp.pack(fill='x', expand=True)
        tk.Label(panelDownLeftUp, text='f(x) + k', font=TEXT_FONT, width=8).pack(side='left', padx=GRAL_PAD)
        self.sliderUp = tk.Scale(panelDownLeftUp, from_=-SLIDER_MAX, to=SLIDER_MAX, font=SLIDER_FONT, resolution=SLIDER_INC, orient='horizontal', command=self.__updateGraph)
        self.sliderUp.pack(side='left', fill='x', expand=True, padx=GRAL_PAD)

        # 3.1.2. Panel central (f(x+k))
        panelDownLeftCenter = tk.Frame(panelDownLeft)
        panelDownLeftCenter.pack(fill='x', expand=True)
        tk.Label(panelDownLeftCenter, text='f(x + k)', font=TEXT_FONT, width=8).pack(side='left', padx=GRAL_PAD)
        self.sliderCenter = tk.Scale(panelDownLeftCenter, from_=-SLIDER_MAX, to=SLIDER_MAX, font=SLIDER_FONT, resolution=SLIDER_INC, orient='horizontal', command=self.__updateGraph)
        self.sliderCenter.pack(side='left', fill='x', expand=True, padx=GRAL_PAD)

        # 3.1.1. Panel inferior (k*f(x))
        panelDownLeftDown = tk.Frame(panelDownLeft)
        panelDownLeftDown.pack(fill='x', expand=True)
        tk.Label(panelDownLeftDown, text='k ⋅ f(x)', font=TEXT_FONT, width=8).pack(side='left', padx=GRAL_PAD)
        self.sliderDown = tk.Scale(panelDownLeftDown, from_=-SLIDER_MAX, to=SLIDER_MAX, font=SLIDER_FONT, resolution=SLIDER_INC, orient='horizontal', command=self.__updateGraph)
        self.sliderDown.pack(side='left', fill='x', expand=True, padx=GRAL_PAD)

        # 3.2. Panel derecho (botones)
        panelDownRight = tk.Frame(panelDown)
        panelDownRight.pack(side='right')
        self.minusF = tk.BooleanVar(panelDown, value=False)
        tk.Checkbutton(panelDown, text='-f(x)', font=TEXT_FONT, variable=self.minusF, command=self.__updateGraph).pack(fill='y', expand=True, padx=GRAL_PAD)
        self.minusX = tk.BooleanVar(panelDown, value=False)
        tk.Checkbutton(panelDown, text='f(-x)', font=TEXT_FONT, variable=self.minusX, command=self.__updateGraph).pack(fill='y', expand=True, padx=GRAL_PAD)
        tk.Button(panelDown, text='Reset', font=tk.font.Font(family=TEXT_FONT[0], size=TEXT_FONT[1], slant='italic'), command=self.__resetGraphComponents).pack(fill='y', expand=True, padx=GRAL_PAD)

        self.__updateGraph(ignoreF=True, init=True)
        self.__resetGraphComponents()
        self.ejes.callbacks.connect('xlim_changed', self.__updateXLim)
        
    def __updateGraph(self, _=None, *, ignoreF=True, init=False):

        # Función original
        if not ignoreF:
            try:
                self.__getF()
            except:
                self.controlador.warningField(self.fEntry)
                return
        
        x = np.linspace(*self.xLim, RESOLUTION)
        with np.errstate(invalid='ignore'):
            y = self.f(x)
        if isinstance(y, int) or isinstance(y, float):
            y = [y] * len(x)

        # Función convertida
        self.sympyF1 = self.sliderDown.get() * ((-1 if self.minusF.get() else 1) * self.sympyF.subs('x', f'{"-x" if self.minusX.get() else "x"} + {self.sliderCenter.get()}')) + self.sliderUp.get()
        self.latexF1 = latex(self.sympyF1)
        self.f1 = lambdify('x', self.sympyF1, 'numpy')
        with np.errstate(invalid='ignore'):
            y1 = self.f1(x)
        if isinstance(y1, int) or isinstance(y1, float):
            y1 = [y1] * len(x)

        if init:
            SimpleInteractiveGraphFrame().setLineasEjes(self.ejes)
            self.lineF, = self.ejes.plot(x, y, color='black')
            self.lineF1, = self.ejes.plot(x, y1, color='red', linestyle='dashdot')
            self.__updateXLim()
        else:
            self.lineF.set_data(x, y)
            self.lineF.set_label(rf'$f(x)={self.latexF}$')
            self.lineF1.set_data(x, y1)
            self.lineF1.set_label(rf'$g(x)={self.latexF1}$')

        plt.legend()
        self.canvas.draw()

    def __defaultF(self):
        self.sympyF = sympify('x^2')
        self.f = lambda x: x**2
        self.latexF = 'x^{2}'

    def __getF(self):
        rawEntry = self.fEntry.get()
        x = symbols('x')
        self.sympyF = sympify(self.__parser(rawEntry), locals={'x': x})
        self.latexF = latex(self.sympyF)
        self.f = lambdify('x', self.sympyF, 'numpy')
        if self.sympyF.free_symbols - {x}:
            raise Exception

    def __parser(self, expr):
        return expr.replace('⋅', '*').replace('√', 'sqrt')

    def __updateXLim(self, _=None):
        self.xLim = self.ejes.get_xlim()
        self.__updateGraph()

    def __resetGraphComponents(self):
        self.sliderUp.set(0)
        self.sliderDown.set(1)
        self.sliderCenter.set(0)
        self.minusF.set(False)
        self.minusX.set(False)

def main():
    app = Controlador(
        title='III. Funciones — Transformación de Funciones',
        firstFrame=FrameTransformacion,
        height=800, width=775
    )
    app.mainloop()

if __name__ == '__main__':
    main()
