import sys
import os
import json
import tkinter as tk
import serial, struct, time
from tkinter import simpledialog
from tkinter import messagebox



class ModelSelector(tk.simpledialog.Dialog):
    ''' Dialog per selezionare modelli di suoni. '''
    def __init__(self, parent, cache):
        self.cache = cache
        self.model = None
        super().__init__(parent, title='Scelta modello')

    def body(self, frame):
        self.frame = frame
        self.lst = tk.Listbox(frame)
        self.lst.grid(row=0, column=0, padx=(5,1), pady=5, sticky='nsew')
        vscrol = tk.Scrollbar(frame, orient=tk.VERTICAL,
                              command=self.lst.yview)
        self.lst.configure(yscrollcommand=vscrol.set)
        vscrol.grid(row=0, column=1, padx=(1,5), pady=5, sticky='ns')

        frame.grid_rowconfigure(0, weight=1)
        frame.grid_columnconfigure(0, weight=1)
        self._load_models()
        return frame

    def buttonbox(self):
        pn_cmd = tk.Frame(self.frame)
        pn_cmd.grid(row=1, column=0, columnspan=2, sticky='ew')
        bt_ok = tk.Button(pn_cmd, text='Ok', command=self._on_ok)
        bt_ok.grid(row=0, column=0, padx=5, pady=5, sticky='ew')
        bt_cancel = tk.Button(pn_cmd, text='Annulla', command=self._on_cancel)
        bt_cancel.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
        pn_cmd.grid_columnconfigure(0, weight=1, uniform='bt')
        pn_cmd.grid_columnconfigure(1, weight=1, uniform='bt')
      

    def _on_ok(self):
        sel = self.lst.curselection()
        if not sel: return
        idx = sel[0]
        value = self.lst.get(idx)
        if value:
            self.model = value
            self.destroy()

    def _on_cancel(self):
        self.destroy()

    def _load_models(self):
        contents = os.listdir(self.cache)
        models = [x.split('.')[0] for x in contents if x.endswith('.json')]
        for m in models:
            self.lst.insert(tk.END, m)

def select_model(app, cache):
    dialog = ModelSelector(app, cache)
    return dialog.model
    

class MainWin(tk.Tk):
    freqsa = [0,0,0,0,0]   #freqsa
    mixera = [0,0,0,0,0]    #mixera
    freqsb = [0,0,0,0]       #freqsb
    mixerb = [0,0,0,0]        #mixerb
    #shape =  ['1','2','3','4','5'] 

   

    def __init__(self):
        super().__init__()
        self._cache = ''
        self._model = ''
        self._set_environment()
        self._make_menus()
        self.populate()
        self.mixera = [0,0,0,0,0]
        self.freqsa = [0,0,0,0,0]
        self.mixerb = [0,0,0,0]
        self.freqsb = [0,0,0,0]
        self.values = []


    def _set_environment(self):
        ''' Stabilisce la posizione di storage dei modelli di combinazioni. '''
        # stabilisce la locazione per la cache in una sub-directory
        # della cartella contenente lo script denominata "Modelli"
        app_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
        self._cache = os.path.join(app_dir, 'Modelli')
        # se la sub-directory non esiste la crea
        if not os.path.exists(self._cache) and not os.path.isdir(self._cache):
            os.mkdir(self._cache)
        
    def _make_menus(self):
        ''' Costruisce un sistema di menu per la finestra. '''        
        menubar = tk.Menu(self, bg='black', fg='white',
                          activebackground='blue', activeforeground='white')
        filemenu = tk.Menu(menubar, bg='black', fg='white',
                          activebackground='blue', activeforeground='white')
        filemenu.add_command(label="New", command=self._on_initialize)
        filemenu.add_separator()
        filemenu.add_command(label="Open", command=self._on_open)
        filemenu.add_separator()
        filemenu.add_command(label="Save", command=self._on_save)
        filemenu.add_command(label="Save As ...",  command=self._on_save_as)
        filemenu.add_separator()
        filemenu.add_command(label="Close", command=self.destroy)
        menubar.add_cascade(label="File", menu=filemenu)
        self.config(menu=menubar)
        
    def populate(self):
        self.title(' SINTHETIZER')
        self.configure(bg='black')
        self._scales = {1: [],  #freqsa
                        2: [],  #mixera
                        3: [],  #freqsb
                        4: []}  #mixerb
        #st_pnl = tk.LabelFrame(self, text='', fg='white', background='black')
        #st_pnl.pack(padx=5, pady=5)
        #st_pnl.grid(row=0, column=0, sticky='nsew')
        #self.v_scales = []
        #self.h_scales = []
        #st_pnl.grid_rowconfigure(1, weight=1)
        fr_pnl = tk.LabelFrame(self, text=' Frequency (Hz) side A ', fg="white", background="black")
        fr_pnl.grid(row=0, column=1, sticky='ne')
        for i in range(len(self.freqsa)):
            sc = tk.Scale(fr_pnl, from_=1500, to=0, showvalue=True, length=200, tickinterval=1, bg='black', fg='white', orient=tk.VERTICAL)
            sc.set(0)
            sc.bind("<ButtonRelease>", self.aggiorna_freq)       #binding  freq
            sc.grid(row=0, column=i, padx=5, pady=5, sticky='se')
            self._scales[1].append(sc)
            fr_pnl.grid_columnconfigure(i, weight=1, uniform='fsc')
        fr_pnl.grid_rowconfigure(1, weight=1)
        fr_pnl = tk.LabelFrame(self, text=' Mixer (ohms x 100) side A', fg="white", background="black")
        fr_pnl.grid(row=1, column=1, sticky='se')
        for i in range(len(self.mixera)):
            lbl = tk.Label(fr_pnl, text=self.mixera[i], fg="white", background="black")
            lbl.grid(row=1, column=i, padx=5, pady=5, sticky='se')
            sc = tk.Scale(fr_pnl, from_=1000, to=0, showvalue=True, length=200, tickinterval=1,background='black', fg='white', orient=tk.VERTICAL)
            sc.set(0)
            sc.bind("<ButtonRelease>", self.aggiorna_mixer)       #binding mixer
            sc.grid(row=1, column=i, padx=5, pady=5, sticky='se')
            self._scales[2].append(sc)   

        self.grid_columnconfigure(0, weight=1)
        #slader per oscillatori lato b
        self.configure(bg='black')
        st_pnlb = tk.LabelFrame(self, text='', fg='white', background='black')
        #st_pnlb.pack(padx=10, pady=10)
        #st_pnlb.grid(row=10, column=10, sticky='nsew')
        self.v_scales = []
        self.h_scales = []
        #st_pnlb.grid_rowconfigure(1, weight=1)
        fr_pnlb = tk.LabelFrame(self, text=' Frequency (Hz) side B', fg="white", background="black")
        fr_pnlb.grid(row=0, column=0, sticky='nw')
        for i in range(len(self.freqsb)):
            scb = tk.Scale(fr_pnlb, from_=1500, to=0, showvalue=True, length=200, tickinterval=1, bg='black', fg='white', orient=tk.VERTICAL)
            scb.set(0)
            scb.bind("<ButtonRelease>", self.aggiorna_freqb)       #binding  freq
            scb.grid(row=0, column=i, padx=5, pady=5, sticky='ne')
            self._scales[3].append(scb)
            fr_pnlb.grid_columnconfigure(i, weight=1, uniform='fsc')
        fr_pnlb = tk.LabelFrame(self, text=' Mixer (ohms x 10) side B', fg="white", background="black")
        fr_pnlb.grid(row=1, column=0, sticky='sw')
        for i in range(len(self.mixerb)):
            lblb = tk.Label(fr_pnlb, text=self.mixerb[i], fg="white", background="black")
            lblb.grid(row=1, column=i, padx=5, pady=5, sticky='se')
            scb = tk.Scale(fr_pnlb, from_=10000, to=0, showvalue=True, length=200, tickinterval=1,background='black', fg='white', orient=tk.VERTICAL)
            scb.set(0)
            scb.bind("<ButtonRelease>", self.aggiorna_mixerb)       #binding mixer
            scb.grid(row=1, column=i, padx=5, pady=5, sticky='se')
            self._scales[4].append(scb)  
        #disegna un bottone di invio modelli
        fr = tk.Frame(bg="black")
        fr.grid(row=2, column=0, columnspan=2, sticky='ew')
        button = tk.Button(fr, text="Send pattern", command=self.invia)
        button["bg"] = "black"
        button["fg"] = "white"
        # Posiziona il bottone nella finestra
        button.grid(row=2, column=0, pady=10, padx=10, sticky='sw')
        #disegna controlli check        
        
    def invia(self):
        values = app.freqsa + app.mixera + app.freqsb + app.mixerb
        #invia modello
        try:
            # Apri la porta seriale
            ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
            print(f"Porta seriale /dev/ttyACM0 aperta con successo.")
            time.sleep(2) # Attendi un momento per la connessione
            data = b''.join(struct.pack('<I', v) for v in values)
            ser.write(data)
            print("Dati inviati!")
            # Chiudi la porta seriale
            ser.close()
            print("Porta seriale chiusa.")

        except serial.SerialException as e:
            print(f"Errore durante l'apertura o la comunicazione della porta seriale: /dev/ttyACM0")       
      


    def _on_initialize(self):
        ''' Imposta lo stato iniziale per un nuovo modello di frequenze. '''
        self._model = ''
        for s in self._scales[1]:   #freqsa
            s.set(0)
        for s in self._scales[2]: #mixera
            s.set(0)
        for s in self._scales[3]: #freqsb
            s.set(0)
        for s in self._scales[4]: #mixerb
            s.set(0)
        app.values = app.freqsa + app.mixera + app.freqsb + app.mixerb
           

    def _on_save(self):
        app.values = app.freqsa + app.mixera + app.freqsb + app.mixerb
        print(app.values)
        if not self._model:
            name = simpledialog.askstring('Nome modello', 'inserire il nome da assegnare', parent=self)
            if not name:
                return
            self._model = os.path.join(self._cache, name + '.json')
        if os.path.exists(self._model):
            msg = 'Nome modello già in uso'
            messagebox.showerror('Duplicazione', msg, parent=self)
            return
        for k in self._scales.keys():
            sets = []
            for i in range(len(self._scales[k])):
                sets.append(self._scales[k][i].get())
            #app.values[k] = sets
        try:
            with open(self._model, 'w') as f:
                json.dump(app.values, f)
        except OSError as e:
            msg = 'Errore salvataggio dati mixer:\n\n' + repr(e)
            messagebox.showerror('Errore I/O', msg, parent=self)
            return

    def _on_save_as(self):
        app.values = app.freqsa + app.mixera + app.freqsb + app.mixerb
        if not self._model:
            msg = 'Questo modello non ha nome, utilizzare il comando "save"'
            messagebox.showinfo('Comando improprio', msg, parent=self)
            return
        name = simpledialog.askstring('Nome modello', 'inserire il nome da assegnare', parent=self)
        if not name:
            return
        name = os.path.join(self._cache, name + '.json')
        if os.path.exists(name):
            msg = 'Nome modello già in uso'
            messagebox.showerror('Duplicazione', msg, parent=self)
            return
        self._model = name
        for k in self._scales.keys():
            sets = []
            for i in range(len(self._scales[k])):
                sets.append(self._scales[k][i].get())
            app.values[k] = sets
        try:
            with open(self._model, 'w') as f:
                json.dump(app.values, f)
        except OSError as e:
            msg = 'Errore salvataggio dati mixer:\n\n' + repr(e)
            messagebox.showerror('Errore I/O', msg, parent=self)
            return        

    def _on_open(self):
        model = select_model(self, self._cache)
        if not model: return
        self._model = os.path.join(self._cache, model + '.json')
        try:
            with open(self._model) as f:
                app.values = json.load(f)    
        except OSError as e:
            msg = 'Errore salvataggio dati mixer:\n\n' + repr(e)
            messagebox.showerror('Errore I/O', msg, parent=self)
            return
        for k in self._scales.keys():
            for i in range(len(self._scales[k])):
                self._scales[k][i].set(app.values[i])  #controllare quì
        print(app.values)
                

    def aggiorna_freq(self, evt):
        w = evt.widget
        try:
            index= self._scales[1].index(w)
            app.freqsa[index] = w.get()
            print(app.freqsa)
        except ValueError as e:
            return                        
            
    def aggiorna_mixer(self, evt):
        w = evt.widget
        try:
            index = self._scales[2].index(w)
            app.mixera[index] = w.get()
            print(app.mixera)
        except ValueError as e:
            return           
    
    def aggiorna_freqb(self, evt):
        w = evt.widget
        try:
            index= self._scales[3].index(w)
            app.freqsb[index] = w.get()
            print(app.freqsb)
        except ValueError as e:
            return                        

    def aggiorna_mixerb(self, evt):
        w = evt.widget
        try:
            index = self._scales[4].index(w)
            app.mixerb[index] = w.get()
            print(app.mixerb)
        except ValueError as e:
            return               
    
    

if __name__ == '__main__':
    #crea finestra
    app = MainWin()
    app.configure(background='black')
    app.geometry("1000x1000+0+0")
    app.resizable(False,False)
    app.configure(background="black")
    #app.bottone()
    app.mainloop()

    

