'''
/** 
 * Original work Copyright 2024 damiano IU3QGD
 * https://to-be-defined
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.

 * Addition to the licence: NO company with more than 50employees can use this SW and you are NOT entitled to store this software 
 * on ANY storage that in some way attempts to monetize it (eg: github)
 * corporations and banks are too rich and too powerful, they MUST be split and ALL the money given back to the state

I am moving to a "wired" table, meaning, a table with all info on screen

  
''' 

from __future__ import annotations

import csv
from tkinter import ttk, messagebox, IntVar, DoubleVar
import typing

from app_csv import CsvTable, Csv_Writer, Csv_Reader
from glob_eeprom import GlobEeprom, EEblock
from glob_fun import getResourcesFname, string_to_int, string_to_float
from glob_gui import EE_events_listener, JtkPanelPackTop, JtkPanelGrid, JtkLabel,\
    JtkPanelPackLeft
from glob_mlang import MlangLabel
import qpystat
import qseeprom_gui


# Squelch EEPROM is organized by actions, NOT by levels
# in a row, the level is the index of the element
squelch_EErows_alist = [ 0x1610, 0x1620, 0x1630, 0x1640, 0x1650, 0x1660 ]


# =====================================================================
# There are two list, one for the squelch and one for the params
# It will be signalled when new EEPROM data is received
# NOTE that the GUI is part of the datastructure
class Edsquelch_gui (EE_events_listener):

    # ----------------------------------------------------------------
    def __init__(self, parent_obj : qseeprom_gui.Qseeprom_gui , parent_frame : ttk.Frame ):
      
        self._parent_obj : qseeprom_gui.Qseeprom_gui = parent_obj
        self._stat : qpystat.Qpystat = parent_obj._stat

        self.work_panel = ttk.Frame(parent_frame)
        
        c_panel=self._newCenterPanel(self.work_panel)
        
        c_panel.pack(fill='both', expand=True)

    # ------------------------------------------------------------------------
    def tkraise(self):
        self.work_panel.tkraise()
        
    # ------------------------------------------------------------------------
    def _println(self, msg : str ):
        self._parent_obj._println(msg)

    # ------------------------------------------------------------------------
    def showInfo(self, a_message : str ):
        messagebox.showinfo("Squalch", a_message, parent=self.work_panel, type='ok')    


    # -------------------------------------------------------------------------------
    def _newSquelchTableLabel(self, parent_panel):
        apanel = JtkPanelPackLeft( parent_panel)

        apanel.addItem(JtkLabel(apanel,"Squelch VHF BW6.25",style='Header1.TLabel' ))
        apanel.addItem(ttk.Button(apanel,text="Optimal Squelch Default", command=self._click_squelch_default ))

        return apanel


    # -------------------------------------------------------------------------------
    def _newMultiplierTableLabel(self, parent_panel):
        apanel = JtkPanelPackLeft( parent_panel)

        apanel.addItem(JtkLabel(apanel,"Multiplier ",style='Header1.TLabel' ))
        apanel.addItem(ttk.Button(apanel,text="Optimal Multiplier Default", command=self._click_multiplier_default ))

        return apanel
    
    
    # -------------------------------------------------------------------------------
    # I have two type of content on the center panel
    # A table for the squelch at VHF and 6.25
    # A table for the multipliers
    def _newCenterPanel(self, parent):
        apanel = JtkPanelPackTop( parent) 
        
        apanel.addItem(self._newSquelchTableLabel(apanel))
        apanel.addItem(self._newSquelchTable(apanel))
        
        apanel.addItem(self._newMultiplierTableLabel(apanel))
        apanel.addItem(self._newMultiplierTable(apanel))
        
        apanel.addItem(self._newMlangLabel(apanel,'To apply changes, click on "Write"'))
        
        return apanel
        
    # -------------------------------------------------------------------------------
    # possibly, the best way to layout is by levels 1-9        
    def _newSquelchTable(self, parent):
        apanel = JtkPanelGrid( parent ) #, borderwidth=1, relief='solid')

        for c_label in Edsquelch_row.gui_heading:        
            apanel.addItem(JtkLabel(apanel,c_label))

        self._squelch_list = Edsquelch_list(self, apanel)
        self._stat.appcsv.add_to_tablesList(self._squelch_list)

        return apanel
    
    # -------------------------------------------------------------------------------
    # possibly, the best way to layout is by levels 1-9        
    # the EEPROM layout is to have the multiplier for VHF in the rssi_open and subselection on bwidth
    # basically, the calc table rotated 90 degree I should try to keep the layout as the calc...
    def _newMultiplierTable(self, parent):
        apanel = JtkPanelGrid( parent ) #, borderwidth=1, relief='solid')
      
        for c_name in Edmulti_row.gui_heading:  
            apanel.addItem(JtkLabel(apanel,c_name))

        self._edmulti_list = Edmulti_list(self, apanel)
        self._stat.appcsv.add_to_tablesList(self._edmulti_list)

        return apanel

    # ------------------------------------------------------------------------
    # there is a circular import issue, so, I need to split the addition
    # idiotic snake
    def _newMlangLabel(self, parent_panel : ttk.Frame, en_msg : str ) -> MlangLabel:
        alabel=MlangLabel(parent_panel,en_msg,self._stat.glob_mlang)
        return alabel


    # ---------------------------------------------------------------------
    def _click_squelch_default(self):
        CsvLoader(self, 'squelch_default.csv', self._squelch_list)

    # ---------------------------------------------------------------------
    def _click_multiplier_default(self):
        CsvLoader(self, 'multiplier_default.csv', self._edmulti_list)
        
    # ------------------------------------------------------------------------
    # Called from EEPROM gui, override EE_events_listener
    def updateGuiFromEeprom(self):
        
        g_eeprom = self._stat.globeeprom;
        
        self._squelch_list.pullFromEeprom(g_eeprom)
        self._edmulti_list.pullFromEeprom(g_eeprom)
        
        self.GUI_refresh()

    # ------------------------------------------------------------------------
    # Called from EEPROM gui, override EE_events_listener
    def update_EEblock_from_GUI(self):
        g_eeprom = self._stat.globeeprom;

        self._squelch_list.pushToEeprom(g_eeprom)
        self._edmulti_list.pushToEeprom(g_eeprom)


    # -------------------------------------------------------------------
    # At any time it should be possible to filter the entries and show them
    # Note that this may be called at the beginning , when there is nothing in the list...
    def GUI_refresh(self ):    
        pass



# ========================================================================
class Edmulti_list(CsvTable):

    EE_edmultirow_label = ['VHF 6.25', 'UHF 6.25', 'VHF 12.5', 'UHF 12.5', 'VHF 25', 'UHF 25']

    # -----------------------------------------------------------------
    # I need to preallocate list and never deallocate them
    def __init__(self, parent_o : Edsquelch_gui, grid_panel : JtkPanelGrid ):
        
        self._parent_o = parent_o
        self._multi_list : typing.List[Edmulti_row] = []
        
        for row_index, row_label in enumerate(self.EE_edmultirow_label):

            grid_panel.nextRow();
            grid_panel.addItem(JtkLabel(grid_panel,row_label) )

            # the value is stored right after the squelch table
            arow = Edmulti_row(self, grid_panel, row_index, row_label )
            self._multi_list.append(arow)    

    # -----------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        for arow in self._multi_list:
            arow.pullFromEeprom(ee_glob) 
            
    # -----------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        for arow in self._multi_list:
            m_row : Edmulti_row = arow
            m_row.pushToEeprom(ee_glob) 

    # ----------------------------------------------------------
    # override CSV
    def csv_tableName(self) -> str:
        return 'radio_squelch_multiplier'
    
    # ----------------------------------------------------------
    # override CSV
    def csv_write_header(self, a_writer : Csv_Writer):
        a_writer.writerow(Edmulti_row.csv_heading)

    # ---------------------------------------------------------
    # override CSV
    # in a different thread than swing, write the table to this csv writer
    def csv_write_table(self, a_writer : Csv_Writer):
        
        for row in self._multi_list:
            v_list = row.csv_get_values()
            a_writer.writerow(v_list)

    # -------------------------------------------------------------
    # the difference is that clipboard will insert from a given point
    # for CSV, instead, I am given the row index            
    def csv_parse_row(self, row_values : typing.List[str] ):
        r_iter = iter(row_values)
        
        r_idx = int(next(r_iter))
        
        a_row : Edmulti_row = self._multi_list[r_idx]
        
        a_row.csv_parse_row(r_iter)

        
            


# ==============================================================================
# this is also trasposed, comared to EEPROM
# The stored items are grouped by 2, meaning that there is the multiplier and then there is the addition
# elements 
class Edmulti_row():
    csv_heading=['sqm_idx','sqm_label','sqm_rssi_mult','sqm_rssi_add','sqm_noise_mult','sqm_noise_add','sqm_glitch_mult','sqm_glitch_add']
    gui_heading=["","Mult RSSI","Add RSSI","Mult Noise","Add Noise","Mult Glitch","Add Glitch"]

    # --------------------------------------------------------------------------
    # I should have a list of the EEPROM bloks and iterate over that
    def __init__(self, parent_o : Edmulti_list, parent_grid : JtkPanelGrid , row_index : int, row_label : str ):

        self._parent_o = parent_o
        self._row_index = row_index
        self._row_label = row_label

        self._values_list : typing.List[EepromEntry] = []

        ee_base = squelch_EErows_alist[row_index]
        p_index = 10  # it begins after the other params
        
        p_index = self._add_mult_add(parent_grid, ee_base, p_index)
        p_index = self._add_mult_add(parent_grid, ee_base, p_index)
        p_index = self._add_mult_add(parent_grid, ee_base, p_index)

    # -----------------------------------------------------------------
    def _add_mult_add(self, parent_grid, ee_base, p_index) -> int:

        bvalue = Decimal_Entry(parent_grid, ee_base, p_index)
        parent_grid.addItem(bvalue)
        self._values_list.append(bvalue)

        p_index += 1

        avalue = Byte_Signed(parent_grid, ee_base, p_index)
        parent_grid.addItem(avalue)
        self._values_list.append(avalue)
        
        return p_index+1

    # --------------------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        for aval in self._values_list:
            aval.pullFromEeprom(ee_glob) 

    # -----------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        for aval in self._values_list:
            aval.pushToEeprom(ee_glob) 
            
    # -----------------------------------------------------------------
    # must return the values as a list of strings in the same order as the header
    def csv_get_values(self) -> typing.List[str]:
        
        risul : typing.List[str] = []

        risul.append(str(self._row_index))
        risul.append(str(self._row_label))
        
        for anitem in self._values_list:
            risul.append(str(anitem))
        
        return risul

    # ------------------------------------------------------------------
    # this assumes tha values are in the SAME order and same format as the export !
    # note that s_split is an iterator for strings and the FIRST item (the row index) has already been taken out
    def csv_parse_row(self, r_iter ):
        if not r_iter:
            return 
        
        w_label = next(r_iter)

        if self._row_label != w_label:
            return 
        
        for anitem in self._values_list:
            anitem.setFromString(next(r_iter))


# ==============================================================================
# this whould somehow have a GUI
# I can init a row once I have a GUI and the main parameter is the level

class Edsquelch_row():
    csv_heading=['sqt_idx','sqt_lvl','sqt_rssi_gt_open','sqt_rssi_lt_close','sqt_noise_lt_open','sqt_noise_gt_close','sqt_glitch_lt_open','sqt_glitch_gt_close']
    gui_heading=[" ","Rssi > Open","Rssi < Close","Noise < Open","Noise > Close","Glith < Open","Glitch > Close"]

    # --------------------------------------------------------------------------
    # I should have a list of the EEPROM bloks and iterate over that
    def __init__(self, parent_o : Edsquelch_list, parent_grid : JtkPanelGrid , level : int ):

        self._parent_o = parent_o
        self._level = level

        self._values_list : typing.List[Byte_Entry] = []

        for eerow_base in squelch_EErows_alist:
            avalue = Byte_Entry(parent_grid, eerow_base, level)
            parent_grid.addItem(avalue)
            
            self._values_list.append(avalue)

    # -----------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):        
        for aval in self._values_list:
            aval.pullFromEeprom(ee_glob) 

    # -----------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        for aval in self._values_list:
            aval.pushToEeprom(ee_glob) 
    
    # -----------------------------------------------------------------
    # must return the values as a list of strings in the same order as the header
    def csv_get_values(self) -> typing.List[str]:
        
        risul : typing.List[str] = []

        risul.append(str(self._level))
        risul.append(str(self._level))
        
        for anitem in self._values_list:
            risul.append(str(anitem))
        
        return risul

    # ------------------------------------------------------------------
    # this assumes tha values are in the SAME order and same format as the export !
    # note that s_split is an iterator for strings and the FIRST item (the row index) has already been taken out
    def csv_parse_row(self, r_iter ):
        if not r_iter:
            return 
        
        have_level = next(r_iter)
        
        if self._level != int(have_level):
            return 

        for anitem in self._values_list:
            anitem.setFromString(next(r_iter))

        
# ========================================================================
# Let me try to have some decent OO wrapping in this shitty language
# The raw background data is always allocated
# when the work panel is allocated, also the GUI part will be allocated
class Edsquelch_list(CsvTable):

    # -----------------------------------------------------------------
    # I need to preallocate list and never deallocate them
    # NOTE that squelch goes from zero to 9, inclusive
    def __init__(self, parent_o : Edsquelch_gui, grid_panel : JtkPanelGrid ):
        
        self._parent_o = parent_o
        self._squelch_list : typing.List[Edsquelch_row] = []
        
        for level in range(0, 10):

            grid_panel.nextRow();
            grid_panel.addItem(JtkLabel(grid_panel,"Lvl %i" % (level)) )

            arow = Edsquelch_row(self, grid_panel, level)
            self._squelch_list.append(arow)    
        
    # -----------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        for arow in self._squelch_list:
            arow.pullFromEeprom(ee_glob) 
            
    # -----------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        for arow in self._squelch_list:
            arow.pushToEeprom(ee_glob) 
            
    # ----------------------------------------------------------
    # override CSV
    def csv_tableName(self) -> str:
        return 'radio_squelch_vhf625'
    
    # ----------------------------------------------------------
    # override CSV
    def csv_write_header(self, a_writer : Csv_Writer):
        a_writer.writerow(Edsquelch_row.csv_heading)

    # ---------------------------------------------------------
    # override CSV
    # in a different thread than swing, write the table to this csv writer
    def csv_write_table(self, a_writer : Csv_Writer):
        
        for row in self._squelch_list:
            v_list = row.csv_get_values()
            a_writer.writerow(v_list)

    # -------------------------------------------------------------
    # the difference is that clipboard will insert from a given point
    # for CSV, instead, I am given the row index            
    def csv_parse_row(self, row_values : typing.List[str] ):
        r_iter = iter(row_values)
        
        r_idx = int(next(r_iter))
        
        a_row : Edsquelch_row = self._squelch_list[r_idx]
        
        a_row.csv_parse_row(r_iter)
        
        
# ==================================================================================
# BOTH my Byte and Decimal should support getting and setting values from eeprom        
class EepromEntry():        
        
    # -------------------------------------------------------------------------------
    def __init__(self, ee_base : int, ee_offset : int ):
        self._EEblock_addr = ee_base
        self._value_offset = ee_offset
        
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        pass
    
    def pushToEeprom(self, ee_glob : GlobEeprom):
        pass
    
    # must also support setting the value from a string
    def setFromString(self, s_value : str ):
        pass
        
# ==================================================================================
# this is used for the squelch values
# it should know where to read and write values in eeprom and be able to refresh content from to eeprom        
class Byte_Entry(ttk.Entry, EepromEntry):
    # -------------------------------------------------------------------------------
    # @parent the parent panel where to bind this fentry
    # @param the base of the block where to read
    # @param the offset of the byte
    def __init__(self, parent_panel, ee_base : int, ee_offset : int ):

        EepromEntry.__init__(self, ee_base, ee_offset)
        
        self._intvar = IntVar(parent_panel)

        ttk.Entry.__init__(self, parent_panel, textvariable=self._intvar)

        afont=ttk.Style().lookup("TEntry", "font")

        if afont:
            self['font'] = afont        

        self['width']=3

    # ----------------------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        abyte = a_block.get_byte(self._value_offset)
        self._intvar.set(abyte)

    # ----------------------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        abyte = self._intvar.get() & 0xFF
        a_block.set_user_byte(self._value_offset, abyte)
        
    # --------------------------------------------------
    def setFromString(self, s_value:str):
        self._intvar.set(string_to_int(s_value,0))

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return str(self._intvar.get())


# ============================================================================
# this is like a byte Entry but it has a signed in/out
class Byte_Signed(Byte_Entry):

    def __init__(self, parent_panel, ee_base : int, ee_offset : int ):
        Byte_Entry.__init__(self, parent_panel, ee_base, ee_offset)

    # ----------------------------------------------------------------------------
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        touples=a_block.eeprom_unpack('<b',self._value_offset)
        self._intvar.set(touples[0])

    # ----------------------------------------------------------------------------
    def pushToEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        anint = self._intvar.get()
        a_block.eeprom_pack_into('<b', self._value_offset, anint) 

# ==================================================================================
# this is used for the multiplier values
# it should know where to read and write values in eeprom and be able to refresh content from to eeprom        
class Decimal_Entry(ttk.Entry, EepromEntry):
    # -------------------------------------------------------------------------------
    # @parent the parent panel where to bind this fentry
    # @param the base of the block where to read
    # @param the offset of the byte
    def __init__(self, parent_panel, ee_base : int, ee_offset : int ):
        EepromEntry.__init__(self, ee_base, ee_offset)
        self._doublevar = DoubleVar(parent_panel)
        ttk.Entry.__init__(self, parent_panel, textvariable=self._doublevar)

        afont=ttk.Style().lookup("TEntry", "font")

        if afont:
            self['font'] = afont        

        self['width']=3

    # ----------------------------------------------------------------------------
    # Values are bytes, signed, with one decimal
    # ee_blk.eeprom_pack_into('<b', self._ee_byte_index, avalue)
    # touples=ee_blk.unpack('<b',self._ee_byte_index)
    
    def pullFromEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        touples=a_block.eeprom_unpack('<b',self._value_offset)
        afloat = float(touples[0]) / 10.0
        self._doublevar.set(afloat)

    # ----------------------------------------------------------------------------
    # This should be able to store the proper value
    def pushToEeprom(self, ee_glob : GlobEeprom):
        a_block : EEblock = ee_glob.get_block_from_address(self._EEblock_addr)
        anint = int(self._doublevar.get() * 10.0)
        a_block.eeprom_pack_into('<b', self._value_offset, anint) 
        
    # --------------------------------------------------
    def setFromString(self, s_value:str):
        self._doublevar.set(string_to_float(s_value,0.0))
        
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "%f" % (self._doublevar.get())

# ======================================================================================
# Used to load a specific CSV table into a list
class CsvLoader():
    
    # ----------------------------------------------------------------------------------
    def __init__(self, parent_gui : Edsquelch_gui, filename : str, storage_list ):

        self._storage_list = storage_list

        try:
            self._open_file(filename)
        except Exception as _exc:
            parent_gui.showInfo(str(_exc))

    # ---------------------------------------------------------------------
    def _open_file(self, filename ):
        fpath = getResourcesFname(filename)
        
        with open(fpath, 'r', newline='') as csv_file:
            self._csv_parse(csv_file)

    # ---------------------------------------------------------------------
    def _csv_parse(self, csv_file ):
        
        c_reader : Csv_Reader = csv.reader(csv_file, delimiter='\t', escapechar='\\', quotechar='"', quoting=csv.QUOTE_MINIMAL)
        
        for row in c_reader:
            self._storage_list.csv_parse_row(row)
        
        
