# -*- coding: utf-8 -*-


from tkinter import (
    Frame,
    LabelFrame,
    Text,
    Scrollbar,
    Button,
    Label,
    RIDGE,
    W,
    E,
    N,
    S,
    FLAT,
    CENTER,
    SUNKEN,
    END,
    INSERT,
)
from tkinter.simpledialog import askinteger, askstring
from tkinter.messagebox import askquestion, showinfo, YES, WARNING

from pdp8 import pdp8


class Emulatore(object):

    """
    Interfaccia grafica per l'emulatore del pdp8
    """

    def __init__(self, master, codice, calcolatore, emulatore):
        """
        Inizializza i frame per l'interfaccia dell'emulatore
        """
        self.CD = calcolatore
        self.codice = codice
        self.delay = 100
        self.master = Frame(master)
        self.root = emulatore

        # Memoria Ram
        self.ram = LabelFrame(
            self.master,
            text="Memoria RAM",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
            pady=5,
        )
        self.ram.rowconfigure(0, weight=1)
        self.ram.columnconfigure(0, weight=1)
        self.ram.grid(row=0, column=0, rowspan=3, columnspan=5, sticky=W + E + N + S)

        # Controlli
        self.controlli = Frame(self.master, padx=10, pady=10)
        self.controlli.grid(row=0, column=5, rowspan=1)

        # Status CD
        self.registri = LabelFrame(
            self.master,
            text="REGISTRI",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
            padx=25,
            pady=10,
        )
        self.registri.grid(row=0, column=6, rowspan=1, sticky=W + E + N + S)
        self.unita = LabelFrame(
            self.master,
            text="UC",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
            padx=10,
            pady=10,
        )
        self.unita.grid(row=2, column=6, rowspan=1, sticky=N)

        # Var
        self.variabili = Frame(self.master)
        self.variabili.grid(row=2, column=5)
        self.nstep = LabelFrame(
            self.variabili,
            text="Num. Step",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
        )
        self.nstep.grid(row=0, column=5, sticky=W + E)
        self.delays = LabelFrame(
            self.variabili, text="Delay", relief=RIDGE, borderwidth=5, labelanchor="n"
        )
        self.delays.grid(row=1, column=5, sticky=W + E)
        self.tempo = LabelFrame(
            self.variabili, text="Tempo", relief=RIDGE, borderwidth=5, labelanchor="n"
        )
        self.tempo.grid(row=1, column=6, sticky=W + E)

        # Unita' di controllo
        self.unitas = LabelFrame(self.unita, text="S", labelanchor="s", padx=10)
        self.unitas.grid(row=0, column=0, sticky=N)
        self.unitaf = LabelFrame(self.unita, text="F", labelanchor="s", padx=10)
        self.unitaf.grid(row=0, column=1, sticky=N)
        self.unitar = LabelFrame(self.unita, text="R", labelanchor="s", padx=10)
        self.unitar.grid(row=0, column=2, sticky=N)
        self.unitaint = LabelFrame(self.unita, text="Int.", labelanchor="s", padx=10)
        self.unitaint.grid(row=0, column=3, sticky=N)

        # Registri
        self.programc = LabelFrame(
            self.registri, text="PC", relief=FLAT, labelanchor="e", padx=5
        )
        self.programc.grid(row=0, column=0, sticky=W + E)
        self.mar = LabelFrame(
            self.registri, text="MAR", relief=FLAT, labelanchor="e", padx=5
        )
        self.mar.grid(row=1, column=0, sticky=W + E)
        self.mbr = LabelFrame(
            self.registri, text="MBR", relief=FLAT, labelanchor="e", padx=5
        )
        self.mbr.grid(row=2, column=0, sticky=W + E)
        self.lopr = LabelFrame(
            self.registri, text="OPR", relief=FLAT, labelanchor="e", padx=5
        )
        self.lopr.grid(row=3, column=0, sticky=W + E)
        self.vari = LabelFrame(
            self.registri, text="I", relief=FLAT, labelanchor="e", padx=5
        )
        self.vari.grid(row=4, column=0, sticky=W + E)
        self.vare = LabelFrame(
            self.registri, text="E", relief=FLAT, labelanchor="e", padx=5
        )
        self.vare.grid(row=5, column=0, sticky=W + E)
        self.lac = LabelFrame(
            self.registri, text="AC", relief=FLAT, labelanchor="e", padx=5
        )
        self.lac.grid(row=6, column=0, sticky=W + E)
        self.lacint = LabelFrame(
            self.registri, text="INT AC", relief=FLAT, labelanchor="e", padx=5
        )
        self.lacint.grid(row=7, column=0, sticky=W + E)
        self.lachex = LabelFrame(
            self.registri, text="HEX AC", relief=FLAT, labelanchor="e", padx=5
        )
        self.lachex.grid(row=8, column=0, sticky=W + E)

        # Microistruzioni
        self.micro = LabelFrame(
            self.master,
            text="Microistruzioni eseguite",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
            pady=5,
        )
        self.micro.rowconfigure(0, weight=1)
        self.micro.columnconfigure(0, weight=1)
        self.micro.grid(row=3, column=4, rowspan=5, columnspan=5, sticky=W + E + N + S)

        # Inout
        self.inout = LabelFrame(
            self.master,
            text="Input & Output",
            relief=RIDGE,
            borderwidth=5,
            labelanchor="n",
            pady=5,
        )
        self.inout.rowconfigure(0, weight=1)
        self.inout.columnconfigure(0, weight=1)
        self.inout.grid(row=3, column=0, columnspan=4, sticky=W + E + N + S)

        self.create_widgets()

    def create_widgets(self):
        """
        Crea il layout del programma, finestra dell'emulatore
        """
        # Memoria RAM
        self.Visualizza = Text(self.ram, width=80)
        self.Visualizzascrollbar = Scrollbar(self.ram)
        self.Visualizzascrollbar.config(command=self.Visualizza.yview)
        self.Visualizza.config(yscrollcommand=self.Visualizzascrollbar.set)
        self.Visualizzascrollbar.grid(row=0, column=1, sticky=N + S)
        self.Visualizza.grid(row=0, column=0, sticky=W)

        # INOUT
        self.Visualizzainout = Text(
            self.inout, width=62, height=7, fg="green", bg="black"
        )
        self.Visualizzascrollbar_inout = Scrollbar(self.inout)
        self.Visualizzascrollbar_inout.config(command=self.Visualizzainout.yview)
        self.Visualizzainout.config(yscrollcommand=self.Visualizzascrollbar_inout.set)
        self.Visualizzascrollbar_inout.grid(row=0, column=1, sticky=N + S)
        self.Visualizzainout.grid(row=0, column=0, sticky=W)

        # Mircroistruzioni
        self.Visualizzamicro = Text(self.micro, width=55, height=7)
        self.Visualizzascrollbar_m = Scrollbar(self.micro)
        self.Visualizzascrollbar_m.config(command=self.Visualizzamicro.yview)
        self.Visualizzamicro.config(yscrollcommand=self.Visualizzascrollbar_m.set)
        self.Visualizzascrollbar_m.grid(row=0, column=1, sticky=N + S)
        self.Visualizzamicro.grid(row=0, column=0, sticky=W)

        # Pulsanti
        self.butload = Button(
            self.controlli,
            text="LOAD",
            anchor=CENTER,
            width=15,
            command=self.loading,
            bg="SkyBlue",
        )
        self.butload.grid(row=0, column=0)
        self.butstep = Button(
            self.controlli,
            text="Step",
            anchor=CENTER,
            width=15,
            command=self.step,
            bg="linen",
        )
        self.butstep.grid(row=1, column=0)
        self.butminstep = Button(
            self.controlli,
            text="miniStep",
            anchor=CENTER,
            width=15,
            command=self.mini_step,
            bg="linen",
        )
        self.butminstep.grid(row=2, column=0)
        self.butstep = Button(
            self.controlli,
            text="microStep",
            anchor=CENTER,
            width=15,
            command=self.micro_step,
            bg="linen",
        )
        self.butstep.grid(row=3, column=0)
        self.butsetstep = Button(
            self.controlli,
            text="Set n Step",
            anchor=CENTER,
            width=15,
            command=self.setnstep,
            bg="linen",
        )
        self.butsetstep.grid(row=4, column=0)
        self.butsetdelay = Button(
            self.controlli,
            text="Set Delay",
            anchor=CENTER,
            width=15,
            command=self.setdelay,
            bg="linen",
        )
        self.butsetdelay.grid(row=5, column=0)
        self.butstart = Button(
            self.controlli,
            text="START",
            anchor=CENTER,
            width=15,
            command=self.start,
            bg="DarkOliveGreen3",
        )
        self.butstart.grid(row=6, column=0)
        self.butreset = Button(
            self.controlli,
            text="RESET",
            anchor=CENTER,
            width=15,
            command=self.resetCD,
            bg="Orange3",
        )
        self.butreset.grid(row=7, column=0)
        self.butstop = Button(
            self.controlli,
            text="STOP",
            anchor=CENTER,
            width=15,
            command=self.stop,
            bg="IndianRed",
        )
        self.butstop.grid(row=8, column=0)
        self.butbreak = Button(
            self.controlli,
            text="BREAK",
            anchor=CENTER,
            width=15,
            command=self.breakpoint,
            bg="Magenta2",
        )
        self.butbreak.grid(row=9, column=0)
        self.butcontinue = Button(
            self.controlli,
            text="CONTINUA",
            anchor=CENTER,
            width=15,
            command=self.continua,
            bg="Magenta2",
        )
        self.butcontinue.grid(row=10, column=0)
        self.butesegui = Button(
            self.controlli,
            text="ESEGUI",
            anchor=CENTER,
            width=15,
            command=self.esegui,
            bg="Yellow",
        )
        self.butesegui.grid(row=11, column=0)

        # Labels
        self.labelprogramc = Label(
            self.programc, text="00000000000", relief=SUNKEN, bg="red"
        )
        self.labelprogramc.grid()
        self.labelmar = Label(self.mar, text="00000000000", relief=SUNKEN, bg="yellow")
        self.labelmar.grid()
        self.labelmbr = Label(self.mbr, text="000000000000000", relief=SUNKEN)
        self.labelmbr.grid()
        self.labelvari = Label(self.vari, text="0", relief=SUNKEN)
        self.labelvari.grid()
        self.labelopr = Label(self.lopr, text="000", relief=SUNKEN)
        self.labelopr.grid()
        self.labelucs = Label(self.unitas, text="0")
        self.labelucs.grid()
        self.labelucf = Label(self.unitaf, text="0")
        self.labelucf.grid()
        self.labelucr = Label(self.unitar, text="0")
        self.labelucr.grid()
        self.labelucint = Label(self.unitaint, text="0")
        self.labelucint.grid()
        self.labelnstep = Label(self.nstep, text="1")
        self.labelnstep.grid()
        self.labeldelay = Label(self.delays, text=str(self.delay))
        self.labeldelay.grid()
        self.labeltempo = Label(self.tempo, text=str(self.CD.tempo))
        self.labeltempo.grid()
        self.labelac = Label(self.lac, text="000000000000000", relief=SUNKEN)
        self.labelac.grid()
        self.labelacint = Label(self.lacint, text="000000000000000", relief=SUNKEN)
        self.labelacint.grid()
        self.labelachex = Label(self.lachex, text="000000000000000", relief=SUNKEN)
        self.labelachex.grid()
        self.labelvare = Label(self.vare, text="0", relief=SUNKEN)
        self.labelvare.grid()

    def continua(self):
        """
        Continua l'esecuzione dopo un break
        """
        self.CD.S = True
        self.esegui()

    def micro_step(self):
        """
        Esegue il metodo step del calcolatore didattico ed aggiorna
        """
        if self.CD.S:
            self.CD.step(self.root, self.codice)
            if self.CD.tempo == 0 and not self.CD.F and not self.CD.R:
                self.CD.previstr = self.CD.nextistr
            self.aggiornaall()

    def step(self):
        """
        Esegue il metodo step del calcolatore didattico ed aggiorna
        """
        var = True
        if self.CD.S and self.CD.nstep > 0:
            while var and self.CD.S:
                self.CD.step(self.root, self.codice)
                if not self.CD.F and not self.CD.R and self.CD.tempo == 0:
                    self.CD.nstep -= 1
                    self.aggiornaall()
                    self.CD.previstr = self.CD.nextistr
                    var = False
            if self.CD.nstep > 0:
                self.butstep.after(self.delay, self.step)
            else:
                self.CD.setnstep(1)
        else:
            self.CD.setnstep(1)
            self.aggiornaall()

    def esegui(self):
        """
        Esegue il programma fino all'arresto della macchina tramite
        l'istruzione HLT
        """
        while self.CD.S:
            self.CD.step(self.root, self.codice)
            if not self.CD.F and not self.CD.R and self.CD.tempo == 0:
                self.aggiornaall()
                self.CD.previstr = self.CD.nextistr
                break
        if self.CD.S:
            self.butesegui.after(self.delay, self.esegui)
        else:
            self.CD.setnstep(1)
            self.aggiornaall()

    def mini_step(self):
        """
        Esegue un singolo ciclo della macchina
        """
        if self.CD.S:
            for x in range(0, 4):
                self.CD.step(self.root, self.codice)
                self.CD.nstep = 1
                self.aggiornaall()
            if self.CD.F is False and self.CD.R is False:
                self.CD.previstr = self.CD.nextistr

    def cerca_istr_prev(self):
        """
        Evidenzia di VERDE l'ultima istruzione eseguita
        """
        if self.CD.PC == "000000000000":
            return
        try:
            if self.CD.previstr == "" and int(self.CD.PC, 2) == self.CD.START:
                return
            else:
                pospc = str(3.0 + self.CD.previstr)
                self.Visualizza.tag_add(
                    "PISTR", str(pospc[:-1] + "16"), str(pospc[:-1] + "end")
                )
                self.Visualizza.tag_config("PISTR", background="green")
                self.Visualizza.see(pospc)
        except TypeError:
            pass  # Errore che si ottiene durante il reset del CD

    # NOTA : METODO NON NECESSARIO NEL PROGRAMMA FINALE
    # def cerca_istr_corr(self):
    #    """
    #    Evidenzia di verde l'istruzione che si dovrà eseguire
    #    """
    #    if self.CD.PC == '000000000000':
    #        return
    #    try:
    #        if int(self.CD.PC,2) == self.CD.START:
    # Inizio esecuzione del programma
    # Il PC e l'istruzione da eseguire sono allo stesso 'livello'
    #            pos = str(3.0)
    #            self.Visualizza.tag_add("ISTR", str(pos[0]+'.16'), str(pos[:-1]+'end'))
    #            self.Visualizza.tag_config("ISTR", background = "green")
    #        else:
    #            pospc = str(3.0 + self.CD.nextistr)
    #            self.Visualizza.tag_add("ISTR", str(pospc[:-1]+'16'),  str(pospc[:-1]+'end'))
    #            self.Visualizza.tag_config("ISTR", background = "green")
    #            self.Visualizza.see(pospc)
    #    except TypeError:
    # pass ## Errore che si ottiene durante il reset del CD

    def cerca_MAR(self):
        """
        Evidenzia di giallo l'indirizzo puntato dal MAR
        """
        try:
            pos = 3.0
            stringa = self.Visualizza.get(str(pos), "end")
            while (
                stringa[:12] != self.CD.MAR
                and int(pos) < len(self.CD.RAM) + 3
                and len(self.CD.RAM) > 0
            ):
                pos += 1
                stringa = self.Visualizza.get(str(pos), "end")
            if int(pos) >= len(self.CD.RAM) + 3:
                return
            self.Visualizza.tag_add("MAR", pos, str(float(pos) + 0.12))
            self.Visualizza.tag_config("MAR", background="yellow")
        except TypeError:
            pass  # Errore che si ottiene durante il reset del CD

    def cerca_PC(self):
        """
        Evidenzia di rosso l'indirizzo puntato da PC
        """
        try:
            pos = 3.0
            stringa = self.Visualizza.get(str(pos), "end")
            while (
                stringa[:12] != self.CD.PC
                and int(pos) < len(self.CD.RAM) + 3
                and len(self.CD.RAM) > 0
            ):
                pos += 1
                stringa = self.Visualizza.get(str(pos), "end")
            if int(pos) >= len(self.CD.RAM) + 3:
                return
            self.Visualizza.tag_add("PC", pos, str(float(pos) + 0.12))
            self.Visualizza.tag_config("PC", background="red")
        except TypeError:
            pass  # Errore che si ottiene durante il reset del CD

    def aggiornaout(self):
        """
        Aggiorna micro e input/output
        """
        self.aggiornamicro()
        self.aggiornainout()

    def aggiornamicro(self):
        """
        Aggiorna le microistruzioni eseguite
        """
        self.Visualizzamicro.delete(1.0, END)
        stringa = self.CD.microistruzioni
        self.Visualizzamicro.insert(INSERT, stringa)
        self.Visualizzamicro.see(END)

    def aggiornainout(self):
        """
        Aggiorna gli input ed output di sistema
        """
        self.Visualizzainout.delete(1.0, END)
        stringa = self.CD.inout
        self.Visualizzainout.insert(INSERT, stringa)
        self.Visualizzainout.see(END)

    def aggiornaram(self):
        """
        Aggiorna lo stato della RAM
        """
        self.Visualizza.delete(1.0, END)
        stringa = self.CD.statusRAM()
        self.Visualizza.insert(INSERT, stringa)
        self.cerca_MAR()
        self.cerca_PC()
        self.cerca_istr_prev()
        # self.cerca_istr_corr() #Non più necessaria nella versione finale

    def aggiornareg(self):
        """
        Aggiorna lo stato dei Registri
        """
        self.labelprogramc.config(text=self.CD.PC)
        self.labelmar.config(text=self.CD.MAR)
        self.labelmbr.config(text=self.CD.MBR)
        self.labelac.config(text=self.CD.AC)
        self.labelacint.config(text=str(self.CD.range(int(self.CD.AC, 2))))
        self.labelachex.config(text=str((hex(int(self.CD.AC, 2))[2:].upper())).zfill(4))
        self.labelvare.config(text=self.CD.E)
        self.labelvari.config(text=self.CD.I)
        self.labelopr.config(text=self.CD.OPR)

    def aggiornauc(self):
        """
        Aggiorna lo stato dell'unita' di controllo
        """
        if self.CD.S and not self.CD.breaks:
            self.labelucs.config(text=self.CD.S, bg="green")
            self.unitas.config(bg="green")
        elif not self.CD.S and self.CD.breaks:
            self.labelucs.config(text=self.CD.S, bg="Magenta2")
            self.unitas.config(bg="Magenta2")
        else:
            self.labelucs.config(text=self.CD.S, bg="red")
            self.unitas.config(bg="red")
        self.labelucf.config(text=self.CD.F)
        self.labelucr.config(text=self.CD.R)
        self.labelucint.config(text=self.CD.Interrupt)
        self.labeltempo.config(text=self.CD.tempo)

    def aggiornaall(self):
        """
        Aggiorna tutto
        """
        self.aggiornaram()
        self.aggiornareg()
        self.aggiornauc()
        self.aggiornamicro()
        self.aggiornaout()
        self.labelnstep.config(text=self.CD.nstep)

    def loading(self):
        """
        Carica il contenuto del codice assembly decodificandolo in binario
        nella RAM
        """
        contenuto = self.codice.Inserisci.get(1.0, END)
        if len(contenuto) > 1:
            self.resetCD()
            if self.CD.carica(contenuto, self) is not None:
                self.CD.S = 0
                self.aggiornaall()

    def resetCD(self):
        """
        Resetta il calcolatore didattico
        """
        self.CD = pdp8()
        self.aggiornaall()

    def start(self):
        """
        Mette la variabile Start (S) ad 1, cioe' True
        """
        self.CD.S = True
        if self.CD.breaks == True:
            self.CD.breaks = False
        self.aggiornauc()

    def stop(self):
        """
        Mette la variabile Start (S) ad 0, cioe' False
        """
        self.CD.S = False
        self.aggiornauc()

    def setnstep(self):
        """
        Setta, in base al valore passato, il numero di cicli da eseguire
        """
        temp = askinteger(
            "Num Step",
            "Numero di step da eseguire",
            initialvalue=1,
            minvalue=1,
            parent=self.root,
        )
        if temp is None:
            self.CD.setnstep(1)
        else:
            self.CD.setnstep(temp)
            self.labelnstep.config(text=self.CD.nstep)

    def setdelay(self):
        """
        Setta, in base al valore passato, il ritardo di esecuzione.
        Il valore è espresso in millisecondi, di default = 1000
        """
        temp = askinteger(
            "Set Delay",
            "Ritardo in millisecondi",
            initialvalue=100,
            minvalue=1,
            parent=self.root,
        )
        if temp is not None:
            self.delay = temp
            self.labeldelay.config(text=self.delay)

    def breakpoint(self):
        """
        Setta o elimina i breakpoint dal programma caricato in memoria
        """
        temp = askstring("Cella di memoria", "Indirizzo esadecimale", parent=self.root)
        if temp is not None:
            temp = self.CD.binario(int(temp, 16)).zfill(12)
            self.CD.breakpoint(temp)
            self.aggiornaram()

    def exit(self):
        """
        Esce dal programma
        """
        if askquestion("Exit", "Sicuro di voler uscire?", parent=self.master) == YES:
            self.codice.master.quit()
            self.codice.master.destroy()
        else:
            showinfo(
                "Suggerimento",
                """Forse e' meglio fare una pausa!""",
                icon=WARNING,
                parent=self.master,
            )
