'''
/** 
 * 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

Frequency scanlist, defined in EEPROM
There are 16 rows, each with 16 bytes...
  
''' 

from __future__ import annotations

from tkinter import ttk, IntVar, messagebox
import typing

from app_csv import CsvTable, Csv_Writer
from glob_eeprom import GlobEeprom, EEblock
from glob_fun import getResourcesPhotoimage, \
    frequency_1x_to_kHz_str, frequency_1x_to_Mhz_str, bool_to_string, string_to_bool
from glob_gui import JtkTableColdef, EE_events_listener, JtkTable, JtkPanelGrid,\
    TV_EntryFrequency_MHz_1x, TV_Entry,  JtkPanelPackLeft,\
    table_row_selected_background_color, JtkCheckbox, TV_EntryFrequency_kHz_1x,\
    JtkPanelPackTop, JtkButtonImage, JtkLabel
import qpystat


# The # type: ignore is for mypy to stop complaining that it cannot find the correct tyep
qFscancol_idx="qFscancol_idx"
qFscancol_Fstart_1x="qFscancol_Fstart_1x"
qFscancol_Fstep_1x="qFscancol_Fstep_1x"
qFscancol_steps="qFscancol_steps"
#qFscancol_wait_link_cs="qFscancol_wait_link_cs"             # how many CS to wait for possible signal  
#qFscancol_scan_mode_idx="qFscancol_scan_mode_idx"         
#qFscancol_wait_onlink_s="qFscancol_wait_onlink_s"             # how many seconds to wait when audio is present, before going to next
qFscancol_SCL_a="qFscancol_SCL_a"
qFscancol_SCL_b="qFscancol_SCL_b"
qFscancol_SCL_c="qFscancol_SCL_c"
qFscancol_SCL_d="qFscancol_SCL_d"
     
# -------------------------------------------------------
# Generic way to define a map from a column name to some properties        
qFscancol_map = { 
    qFscancol_idx           : JtkTableColdef(qFscancol_idx            ,'Nr.',True, 40), 
    qFscancol_Fstart_1x     : JtkTableColdef(qFscancol_Fstart_1x      ,"Frq start MHz",False,100),
    qFscancol_Fstep_1x      : JtkTableColdef(qFscancol_Fstep_1x       ,"Step kHz",False, 90),
    qFscancol_steps         : JtkTableColdef(qFscancol_steps          ,"Steps N.",False, 80),
#    qFscancol_scan_mode_idx : JtkTableColdef(qFscancol_scan_mode_idx  ,"Scan Mode",False,90),
#    qFscancol_wait_link_cs  : JtkTableColdef(qFscancol_wait_link_cs   ,"Wait Link cs",False,90),
#    qFscancol_wait_onlink_s : JtkTableColdef(qFscancol_wait_onlink_s  ,"Wait Onlink s",False,90),
    qFscancol_SCL_a       : JtkTableColdef(qFscancol_SCL_a        ,"Sc1",False,40),
    qFscancol_SCL_b       : JtkTableColdef(qFscancol_SCL_b        ,"Sc2",False,40),
    qFscancol_SCL_c       : JtkTableColdef(qFscancol_SCL_c        ,"Sc3",False,40),
    qFscancol_SCL_d       : JtkTableColdef(qFscancol_SCL_d        ,"Sc4",False,40),
    }


qsfscan_combo_scan_mode = [ 'Next on Nolink', 'Wait some Time', 'Stop on Link']




# =====================================================================
# the table with the list of FM channels
# It should take the data from the global EEPROM repository
# It will be signalled when new EEPROM data is received
class QFscan_gui (EE_events_listener):

    # ----------------------------------------------------------------
    def __init__(self, stat : qpystat.Qpystat, parent_frame : ttk.Frame ):
        self.stat = stat

        self._work_panel = ttk.Frame(parent_frame)
        
        self._qFscan_list : QFscan_list = QFscan_list()
        self.stat.appcsv.add_to_tablesList(self._qFscan_list)
        
        c_panel=self._newCenterPanel(self._work_panel)
        
        c_panel.pack(fill='both', expand=True)


    # ------------------------------------------------------------------------
    def getWorkPanel(self) -> ttk.Frame:
        return self._work_panel

    # ------------------------------------------------------------------------
    def tkraise(self):
        self._work_panel.tkraise()
        
    # ------------------------------------------------------------------------
    def _println(self, msg : str ):
        self.stat.app_println(msg)
        
    # -------------------------------------------------------------------------------
    # On center panel there is the table and scroll bar
    # NOTE: The layout of this is described by caller
    # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/ttk-Treeview.html
    def _newCenterPanel(self, parent):
        apanel = ttk.Frame( parent) #, borderwidth=3, relief='ridge')
        
        #apanel.grid_rowconfigure(0, weight = 1)
        #apanel.grid_columnconfigure(0, weight = 1)
        
        self._table_view = JtkTable(apanel, qFscancol_map );
        self._table_view.getComponent().pack(fill='both', expand=True)

        tk_tbl : ttk.Treeview = self._table_view.getTable()
        
        tk_tbl.tag_configure('Updated',  background=table_row_selected_background_color)
        tk_tbl.bind('<<TreeviewSelect>>', self._jtable_row_selected)
        
        # --------------------------------------------------- now, the editing panel
        self._editingPanel = QFscan_edit_gui(self ,apanel)
        self._editingPanel.getComponent().pack(fill='x')
        
        return apanel

    # -------------------------------------------------------------------
    # Called when a row is selected in the tree
    # note that you NEED selectmode='browse' to have a single selection
    # IF multiple rows can be selected with option selectmode='extended' you get a list of selected items that can be not in sequence
    # you can use SHIFT or CTRRL + click to add or remove selected elements
    def _jtable_row_selected(self, sel_coord):
        
        a_sel = self._table_view.getSelected_iid()
        
        if not a_sel:
            return 
        
        #self._println(str(a_sel))
        
        row_idx : int = int(a_sel[0])
        
        sel_item = self._qFscan_list.get_row_using_idx(row_idx)

        self._editingPanel.showThisRow(sel_item)
        
    # ------------------------------------------------------------------------
    def showInfo(self, a_message : str ):
        messagebox.showinfo("Frequency Scanlist", a_message, parent=self._work_panel, type='ok')    
        
    # ------------------------------------------------------------------------
    # the RSSI table is quite messy, let me try to use the radio logic...
    def updateGuiFromEeprom(self):
        self._qFscan_list.updateListFromEeprom(self.stat.globeeprom)
        
        self.GUI_refresh()



    # -----------------------------------------------------------------
    # When this is called, it means the user pressed the write button
    def update_EEblock_from_GUI(self):

        self._println("qFscan.uupdate_EEblock_from_GUI CALL")

        for row in self._qFscan_list.qFscan_list:
            row.update_EEblock_from_GUI(self.stat.globeeprom)
            
        self.GUI_refresh()


    # -------------------------------------------------------------------
    # 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 ):    
        
        for row in self._qFscan_list.qFscan_list:
            e_row : QFscan_row = row
            
            self._jtable_ensureVisible(e_row)

    # ------------------------------------------------------------------------
    # Utility, to make code cleaner
    # NOTE that part of the magic works because the channel number is a unique key
    # This MUST be run from swing thread
    def _jtable_ensureVisible(self, a_row : QFscan_row ):

        if self._table_view.row_exist(a_row.row_idx):
            self._jtable_refresh_using_row(a_row)
        else:
            v_values=a_row.getValuesForTreeview()
            self._table_view.row_append(a_row.row_idx,v_values)



    # ------------------------------------------------------------------------------
    # this should update the swing table content for the given row
    def _jtable_refresh_using_row(self, a_row : QFscan_row ):

        if not a_row:
            return

        #self._table_view.focus_row(a_row.qsch_idx_po)
        
        updated_str=''
        
        if a_row.isUserModified:
            updated_str='Updated'
            
        self._table_view.set_row(a_row.row_idx,a_row.getValuesForTreeview(),updated_str)   


# ==============================================================================
# Q squelch row is made of the following components
# note that there is a value for the band and also for the level
# also, there is something on a delay for squelch... on reg_4E
class QFscan_row():

    csv_heading=['qfs_idx',
                 'qfs_start_F_1x (int)',
                 'qfs_step_F_1x (int)',
                 'qfs_steps (int)',
                 'qfs_scan_mode (str)',
                 'qfs_wait_link_cs (int)',
                 'qfs_wait_audio_s (int)',
                 'qfs_on_scanlist_a (bool)',
                 'qfs_on_scanlist_b (bool)',
                 'qfs_on_scanlist_c (bool)',
                 'qfs_on_scanlist_d (bool)'
                 ]

    # --------------------------------------------------------------------------
    # NOTE that the variable definition is basically useless
    def __init__(self, r_idx : int ):

        self.row_idx : int = r_idx       # row index, to pick up values from the table click

        self.qfsrow_clear()

        self.isUserModified=False

    # --------------------------------------------------------------------------
    # this is also used to clera up the table, if needed
    def qfsrow_clear(self):
        self.F_start_1x : int    = 0
        self.F_step_1x : int     = 0
        self.steps_count : int   = 0   
        self.wait_link_cs : int  = 0  
        self.scan_mode_idx : int = 0
        self.wait_onlink_s : int = 0
        self.on_scanlist_a : bool = False
        self.on_scanlist_b : bool = False
        self.on_scanlist_c : bool = False
        self.on_scanlist_d : bool = False

        self.isUserModified=True

    # -----------------------------------------------------------------
    # return the list of values to show into the table, the GUI table
    # NOTE that the order has to be consistent with the GUI definition
    def getValuesForTreeview(self):
        
        risul = (
            self.row_idx,
            frequency_1x_to_Mhz_str(self.F_start_1x),
            frequency_1x_to_kHz_str(self.F_step_1x),
            self.steps_count,
 #           self.scan_mode_idx,
 #           self.wait_link_cs,
 #           self.wait_onlink_s,
            bool_to_string(self.on_scanlist_a,'','1'),
            bool_to_string(self.on_scanlist_b,'','1'),
            bool_to_string(self.on_scanlist_c,'','1'),
            bool_to_string(self.on_scanlist_d,'','1')
            )
            
        return risul         

        
    # -----------------------------------------------------------------
    # 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_idx))
        risul.append(str(self.F_start_1x))
        risul.append(str(self.F_step_1x))
        risul.append(str(self.steps_count))
        risul.append(str(self.scan_mode_idx))
        risul.append(str(self.wait_link_cs))
        risul.append(str(self.wait_onlink_s))
        risul.append(str(self.on_scanlist_a))
        risul.append(str(self.on_scanlist_b))
        risul.append(str(self.on_scanlist_c))
        risul.append(str(self.on_scanlist_d))
        
        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 
        
        self.F_start_1x    = int(next(r_iter))
        self.F_step_1x     = int(next(r_iter))
        self.steps_count   = int(next(r_iter))
        self.scan_mode_idx = int(next(r_iter))
        self.wait_link_cs  = int(next(r_iter))
        self.wait_onlink_s = int(next(r_iter))
        self.on_scanlist_a  = string_to_bool(next(r_iter), self.on_scanlist_a)
        self.on_scanlist_b  = string_to_bool(next(r_iter), self.on_scanlist_b)
        self.on_scanlist_c  = string_to_bool(next(r_iter), self.on_scanlist_c)
        self.on_scanlist_d  = string_to_bool(next(r_iter), self.on_scanlist_d)

        # a paste from clipboard is equivalent of a user modified
        self.isUserModified=True
        
    # =-----------------------------------------------------------------
    def _getFscanBase(self) -> int:
        return 0x1B00 + self.row_idx*16

    # =-----------------------------------------------------------------
    def _check_scan_mode(self, i_val : int ) -> int:
        if i_val < 0:
            return 0;
        
        if i_val >= len(qsfscan_combo_scan_mode):
            return 0;
        
        return i_val
            

    # =-----------------------------------------------------------------
    # A row holds exactly one block        
    def updateRowFromEeprom(self, g_eeprom : GlobEeprom ):
        
        base_addres = self._getFscanBase()

        ee_block : EEblock = g_eeprom.get_block_from_address(base_addres)

        v_array=ee_block.eeprom_unpack('<IHHHBBB', 0)
        
        self.F_start_1x=v_array[0]
        self.F_step_1x=v_array[1]
        self.steps_count=v_array[2]
        #self.RSSI_min=v_array[3]
        self.scan_mode_idx=self._check_scan_mode(v_array[5])
        self.wait_link_cs=v_array[4]
        self.wait_onlink_s=v_array[6]
        
        self.on_scanlist_a = ee_block.get_boolean(13, 0)
        self.on_scanlist_b = ee_block.get_boolean(13, 1)
        self.on_scanlist_c = ee_block.get_boolean(13, 2)
        self.on_scanlist_d = ee_block.get_boolean(13, 3)
        
        self.isUserModified=False


    # -----------------------------------------------------------------
    # Note that what can be changed are just the values, 
    # update ONLY if the data has changed
    def update_EEblock_from_GUI(self, g_eeprom : GlobEeprom ):
        
        if not self.isUserModified:
            return

        # once written to EEPROM block I can consider it clear
        self.isUserModified=False

        e_addres = self._getFscanBase()

        ee_block : EEblock = g_eeprom.get_block_from_address(e_addres)
        
        ee_block.eeprom_pack_into('<IHHHBBB', 0, self.F_start_1x,self.F_step_1x,self.steps_count,0,self.scan_mode_idx,self.wait_link_cs,self.wait_onlink_s)
        
        ee_block.set_user_boolean(13, 0, self.on_scanlist_a)
        ee_block.set_user_boolean(13, 1, self.on_scanlist_b)
        ee_block.set_user_boolean(13, 2, self.on_scanlist_c)
        ee_block.set_user_boolean(13, 3, self.on_scanlist_d)

        
# ========================================================================
# Let me try to have some decent OO wrapping in this shitty language
# The raw background data is always allocated
class QFscan_list(CsvTable):

    # -----------------------------------------------------------------
    # I need to preallocate list and never deallocate them
    # NOTE that squelch goes from zero to 9, inclusive
    def __init__(self):
        
        self.qFscan_list : typing.List[QFscan_row] = []
        
        for r_idx in range(16):
            self.qFscan_list.append(QFscan_row(r_idx))
        
    # -----------------------------------------------------------------------
    def qfslist_clear(self):
        for row in self.qFscan_list:
            row.qfsrow_clear()
        
    # -----------------------------------------------------------------------
    # if idx is out of range, return None
    def get_row_using_idx(self, idx : int ) -> QFscan_row:
        return self.qFscan_list[idx]
        
    # -----------------------------------------------------------------------
    def updateListFromEeprom(self, g_eeprom : GlobEeprom ):
        for row in self.qFscan_list:
            row.updateRowFromEeprom(g_eeprom)
        
    # ----------------------------------------------------------
    # override CSV
    def csv_tableName(self) -> str:
        return 'frequency_scanlist'
    
    # ----------------------------------------------------------
    # override CSV
    def csv_write_header(self, a_writer : Csv_Writer):
        a_writer.writerow(QFscan_row.csv_heading)

    # ---------------------------------------------------------
    # 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.qFscan_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 : QFscan_row = self.qFscan_list[r_idx]
        
        a_row.csv_parse_row(r_iter)

        
        
# ==============================================================================
# it is kind of easier to have a separate class            
class QFscan_edit_gui():


    # ------------------------------------------------------------
    # I make this a frame for ease of use, it should a bit hidden...    
    def __init__(self, parent_class : QFscan_gui , parent_panel : ttk.Frame ):
        
        self.parent_class = parent_class

        # need an empty row, by default
        self.sel_item : QFscan_row = QFscan_row(0)

        self._work_panel = self._newWorkPanel(parent_panel)

    # ------------------------------------------------------------
    def getComponent(self) -> ttk.Frame:
        return self._work_panel

    def _newWorkPanel(self, parent_panel : ttk.Frame ) -> ttk.Frame:
        apanel = JtkPanelPackLeft(parent_panel)
        
        apanel.addItem(self._newGridPanel(apanel))
        apanel.addItem(self._newButtonsPanel(apanel))
        
        return apanel

    # ------------------------------------------------------------
    def _newButtonsPanel(self, parent_panel : ttk.Frame ):
        apanel = JtkPanelPackTop(parent_panel)

        apanel.addItem(JtkButtonImage(apanel,getResourcesPhotoimage('save-64x64.png'), command=self._clicSaveEditFscanlist ))
        
        apanel.addItem(ttk.Button(apanel, text='Clear ALL', command=self._clickClearTable ))
        
        return apanel;

    # ------------------------------------------------------------
    def _clickClearTable(self):
        self.parent_class._qFscan_list.qfslist_clear()
        self.parent_class.GUI_refresh()


    # ------------------------------------------------------------
    def _println(self, msg : str ):
        self.parent_class._println(msg)

    # -------------------------------------------------------------------------------
    def _newCheckboxPanel(self, parent_panel) -> ttk.Frame:
        apanel = JtkPanelPackLeft(parent_panel)
                
        apanel.p_padx = 4

        self._edit_en_SCL_a = JtkCheckbox(apanel,"Scanlist 1")
        apanel.addItem(self._edit_en_SCL_a)
        
        self._edit_en_SCL_b = JtkCheckbox(apanel,"Scanlist 2")
        apanel.addItem(self._edit_en_SCL_b)

        self._edit_en_SCL_c = JtkCheckbox(apanel,"Scanlist 3")
        apanel.addItem(self._edit_en_SCL_c)

        self._edit_en_SCL_d = JtkCheckbox(apanel,"Scanlist 4")
        apanel.addItem(self._edit_en_SCL_d)

        return apanel

    # -------------------------------------------------------------------------------
    def _newGridPanel(self, parent_panel) -> JtkPanelGrid:
        gridpanel = JtkPanelGrid(parent_panel) # , borderwidth=1, relief='solid', padding=3)

        self._edit_Fstart_1x = TV_EntryFrequency_MHz_1x(gridpanel)
        self._edit_Fstep_1x = TV_EntryFrequency_kHz_1x(gridpanel)
        self._edit_steps_count = IntVar(gridpanel)

        # ------------------------------------------------------------

        gridpanel.addItem(JtkLabel(gridpanel,"Frq start"))
        gridpanel.addItem(self._edit_Fstart_1x)
        
        gridpanel.addItem(JtkLabel(gridpanel,"Frq Step"),sticky='e')
        gridpanel.addItem(self._edit_Fstep_1x) 
        gridpanel.nextRow()
        
        gridpanel.addItem(JtkLabel(gridpanel,"Steps N."))
        gridpanel.addItem(TV_Entry(gridpanel, self._edit_steps_count,width=4))
        gridpanel.nextRow()
        
        gridpanel.addItem(self._newCheckboxPanel(gridpanel),columnspan=2, sticky='e')
        gridpanel.nextRow()

        return gridpanel

        

    # -------------------------------------------------------------------------------
    # The user click SAVE in the edit panel
    # this should map the values back to the selected row
    def _clicSaveEditFscanlist(self):

        if not self.sel_item:
            self.parent_class.showInfo("_clicSaveEditFscanlist: Please select a row")

        self.sel_item.F_start_1x = self._edit_Fstart_1x.get_value()
        self.sel_item.F_step_1x = self._edit_Fstep_1x.get_value()

        self.sel_item.steps_count = self._edit_steps_count.get()
        #self.sel_item.RSSI_min = self._edit_RSSI_min_dB.get()

        #self.sel_item.scan_mode_idx = self._edit_scan_mode.get()
        #self.sel_item.wait_link_cs = self._edit_wait_link_cs.get()

        #self.sel_item.wait_onlink_s = self._edit_wait_audio_s.get()
        self.sel_item.on_scanlist_a = self._edit_en_SCL_a.isChecked()
        self.sel_item.on_scanlist_b = self._edit_en_SCL_b.isChecked()
        self.sel_item.on_scanlist_c = self._edit_en_SCL_c.isChecked()
        self.sel_item.on_scanlist_d = self._edit_en_SCL_d.isChecked()
        
        self.sel_item.isUserModified=True

        self.parent_class._jtable_refresh_using_row(self.sel_item)

    # -------------------------------------------------------------------------------
    # Parent request to show this row
    def showThisRow(self, sel_item : QFscan_row ):

        if not sel_item:
            self.parent_class.showInfo("showThisRow: Please select a row")
            return;
        
        self.sel_item=sel_item  # will be used in the save
        
        self._edit_Fstart_1x.set_value(sel_item.F_start_1x)
        self._edit_Fstep_1x.set_value(sel_item.F_step_1x)
        
        self._edit_steps_count.set(sel_item.steps_count)
        #self._edit_RSSI_min_dB.set(sel_item.RSSI_min)
        
        #self._edit_wait_link_cs.set(sel_item.wait_link_cs)
        #self._edit_scan_mode.set(sel_item.scan_mode_idx)

        #self._edit_wait_audio_s.set(sel_item.wait_onlink_s)
        self._edit_en_SCL_a.setChecked(sel_item.on_scanlist_a)
        self._edit_en_SCL_b.setChecked(sel_item.on_scanlist_b)
        self._edit_en_SCL_c.setChecked(sel_item.on_scanlist_c)
        self._edit_en_SCL_d.setChecked(sel_item.on_scanlist_d)

                

        
        
        


