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

 
  
''' 
# ------------------------------------------------------------------------- 
# There is only ONE poll, this is a GUI and if there is something to send it should be set to the poll
# ====================================================================

from __future__ import annotations

from tkinter import ttk, IntVar, StringVar
from typing import List
import typing

from app_csv import CsvTable, Csv_Writer
from glob_eeprom import GlobEeprom, EEblock
from glob_fun import getResourcesPhotoimage, string_to_int
from glob_gui import JtkTableColdef, EE_events_listener, JtkPanelGrid, TV_Entry, JtkTable,\
    table_row_selected_background_color
import qpystat


qscntc_idx="qscntc_idx"
qscntc_name="qscntc_name"
qscntc_dtmfid="qscntc_dtmfid"
qscntc_talkch="qscntc_talkch"                 # jump to channel
qscntc_msgyid="qscntc_msgyid"                 # messagy ID

    
# -------------------------------------------------------
# Generic way to define a map from a column name to some properties        
qscntcols_map = { 
    qscntc_idx       : JtkTableColdef(qscntc_idx     ,'Nr.',True, 40), 
    qscntc_name      : JtkTableColdef(qscntc_name    ,"Name",False,100),
    qscntc_dtmfid    : JtkTableColdef(qscntc_dtmfid  ,"DTMF ID",True, 100),
    qscntc_talkch    : JtkTableColdef(qscntc_talkch   ,"Talk CH",True, 90),
    qscntc_msgyid    : JtkTableColdef(qscntc_msgyid   ,"Messagy ID",True, 90),
    }




# ===================================================================
# It is safer to deal with objects instead of a generic map
class Qcontact_row:
    
    csv_heading=['cntc_index' ,'cntc_name', 'cntx_dtmfid', 'cntc_toch', 'cntc_msgyid']
    
    # ----------------------------------------------------------------
    # The table is made of "empty" rows that will be populated in a few runs
    # do NOT detach the object from the list, just clear it
    def __init__(self, index : int):
        self.qscntc_idx=index   
        self.clear()

    # ----------------------------------------------------------------
    # Clearring is NOT detaching from list            
    def clear(self):

        self.qscntc_name=''
        self.qscntc_dtmfid=0
        self.qscntc_jtoch=0        # an ONLY positive number
        self.qscntc_msgyid=0       # an ONLY positive number
                
        self.show=True
        self.isUserModified=False

    # -------------------------------------------------------------------
    # return the eeprom address of this row
    def EE_get_address(self) -> int:
        return  0x1C00 + (self.qscntc_idx * 16)

    # -----------------------------------------------------------------
    # must return the values as a list of strings in the same order as the header TODO
    def csv_get_values(self) -> typing.List[str]:
        
        risul : typing.List[str] = []
        risul.append(str(self.qscntc_idx))
        risul.append(str(self.qscntc_name))
        risul.append(str(self.qscntc_dtmfid))
        risul.append(str(self.qscntc_jtoch))
        risul.append(str(self.qscntc_msgyid))
        
        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.qscntc_name   = next(r_iter)
        self.qscntc_dtmfid = int(next(r_iter))
        self.qscntc_jtoch  = int(next(r_iter))
        self.qscntc_msgyid = string_to_int(next(r_iter,'0'),0)

        # a paste from clipboard is equivalent of a user modified
        self.isUserModified=True


        
    # -----------------------------------------------------------------
    # somebody ask this channel to update the backing eeprom
    # but ONLY if the data has changed
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom ):
        
        if not self.isUserModified:
            return

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

        ee_block : EEblock = eeprom.get_block_from_address(self.EE_get_address())

        ee_block.set_user_string_new(self.qscntc_name,0,10)
        ee_block.set_user_byte(10, self.qscntc_msgyid)
        ee_block.set_user_byte(11, self.qscntc_jtoch)
        ee_block.eeprom_pack_into('<I',12, self.qscntc_dtmfid)
        

    # ------------------------------------------------------------------
    # set the core data of the channel, taking value from the bytearray
    def _setFromEeprom(self, ee_block : EEblock ):
        
        self.clear()
        
        if not ee_block:
            return

        a_name : str =ee_block.get_string_new(0, 10)
        self.qscntc_name = a_name.strip()
        
        m_id,jtoch=ee_block.eeprom_unpack('<BB', 10)
        self.qscntc_msgyid=m_id
        self.qscntc_jtoch=jtoch
        
        a_touple=ee_block.eeprom_unpack('<I', 12)
        self.qscntc_dtmfid=a_touple[0]
                    
    # -----------------------------------------------------------------
    # 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.qscntc_idx+1,
            self.qscntc_name,
            str(self.qscntc_dtmfid),
            str(self.qscntc_jtoch),
            str(self.qscntc_msgyid)
            )
            
        return risul         
    
    
# ========================================================================
# Let me try to have some decent OO wrapping in this shitty language
# The raw background data is always allocated
class Qcontacts_list(CsvTable):

    qscntc_max_len=32   

    # -----------------------------------------------------------------
    # I need to preallocate dhte chans and never deallocate them
    def __init__(self):
        
        self.cntclist : List[Qcontact_row] = []
        
        for idx in range(self.qscntc_max_len):
            self.cntclist.append(Qcontact_row(idx))
        
        
    # ----------------------------------------------------------
    # override CSV
    def csv_tableName(self) -> str:
        return 'radio_contacts'
    
    # ----------------------------------------------------------
    # override CSV TODO
    def csv_write_header(self, a_writer : Csv_Writer):
        a_writer.writerow(Qcontact_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.cntclist:
            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)
        
        row_idx = int(next(r_iter))
        
        a_row : Qcontact_row = self.cntclist[row_idx]
        
        a_row.csv_parse_row(r_iter)


        

# =====================================================================
# 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 EE_qscntc_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._qscntc_list : Qcontacts_list = Qcontacts_list()
        self.stat.appcsv.add_to_tablesList(self._qscntc_list)
        
        self.c_panel=self._newCenterPanel(self.work_panel)
        
        self.c_panel.pack(fill='both', expand=True)

    def tkraise(self):
        self.work_panel.tkraise()

    # ------------------------------------------------------------------------
    def _clearTable(self):

        for row in self._qscntc_list.cntclist:
            e_row : Qcontact_row = row
            
            if self._chlist_tview.row_exist(e_row.qscntc_idx):
                self._chlist_tview.row_delete(e_row.qscntc_idx) 
                
    # ------------------------------------------------------------------------
    def _println(self, msg : str ):
        self.stat.app_println(msg)
        
    # ------------------------------------------------------------------------
    # Here I should first pick up the attributes, then the channels, and then channel names
    def updateGuiFromEeprom(self):

        self._clearTable()

        for row_idx in range(Qcontacts_list.qscntc_max_len):
            a_contact : Qcontact_row = self._qscntc_list.cntclist[row_idx]

            ee_block : EEblock = self.stat.globeeprom.get_block_from_address(a_contact.EE_get_address())
            
            a_contact._setFromEeprom(ee_block)    
                
            a_contact.isUserModified=False
        
        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._qscntc_list.cntclist:
            e_row : Qcontact_row = row
            
            if e_row.show:
                self._jtable_ensureVisible(e_row)
            elif self._chlist_tview.row_exist(e_row.qscntc_idx):
                self._chlist_tview.row_detach(e_row.qscntc_idx)

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

        self.stat.app_println("contacts.update_EEblock_from_GUI: CALL")

        for row in self._qscntc_list.cntclist:
            row.update_EEblock_from_GUI(self.stat.globeeprom)
            
        self.GUI_refresh()
            
        
    # ------------------------------------------------------------------------
    # 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 : Qcontact_row ):

        v_values=a_row.getValuesForTreeview()

        if self._chlist_tview.row_exist(a_row.qscntc_idx):
            
            if a_row.isUserModified:
                self._chlist_tview.set_row(a_row.qscntc_idx,v_values,'Updated')
            else:
                self._chlist_tview.set_row(a_row.qscntc_idx,v_values,'')
                
            self._chlist_tview.see_row(a_row.qscntc_idx)
        else:
            self._chlist_tview.row_append(a_row.qscntc_idx,v_values)

    # ------------------------------------------------------------------------------
    # this should update the tree whose row is updated values
    def _treeView_refreshUpdated(self, a_row : Qcontact_row ):
        self._chlist_tview.focus_row(a_row.qscntc_idx)
        
        self._chlist_tview.set_row(a_row.qscntc_idx,a_row.getValuesForTreeview(),'Updated')
        
        a_row.isUserModified=True
        
        
        

    # -------------------------------------------------------------------------------
    # On center panel there is the table and scroll bar
    # NOTE: The layout of this is described by caller
    # NOTE: The style for this part is defined in the main gui, using style
    # 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._chlist_tview = JtkTable(apanel, qscntcols_map, selectmode='extended' );
        self._chlist_tview.getComponent().grid(row=0,column=0, sticky='nsew')

        self._chlist_tview.getTable().tag_configure('Updated',  background=table_row_selected_background_color)
        self._chlist_tview.getTable().bind('<<TreeviewSelect>>', self._jtable_row_selected)
        
        # an item as example        
        #self._chlist_tview.insert(parent='',index='end',iid=0,text='',values=('1','band','chan','88.88'))
        
        # --------------------------------------------------- now, the editing panel
        self._editingPanel = QscntcEditGui(self ,apanel)
        self._editingPanel.grid(row=1,column=0, sticky='nsew')
        
        return apanel

    # -------------------------------------------------------------------
    # Called when a row is selected in the tree
    def _jtable_row_selected(self, sel_coord):
        
        a_sel = self._chlist_tview.getSelected_iid()
        
        if not a_sel:
            return 
        
        row_idx : int = int(a_sel[0])
        
        sel_item = self._qscntc_list.cntclist[row_idx]

        self._editingPanel.showThisRow(sel_item)
            
            
# ==============================================================================
# it is kind of easier to have a separate class            
class QscntcEditGui(ttk.Frame):

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

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

        self._newEditContactPanel(self).pack(side='top',anchor='nw')

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


    # -------------------------------------------------------------------------------
    def _newEditContactPanel(self, parent_panel) -> JtkPanelGrid:

        gridpanel = JtkPanelGrid(parent_panel)
        gridpanel.g_padx=5

        self._edit_name   = StringVar(gridpanel,value='')
        self._edit_dtmfid = IntVar(gridpanel)
        self._edit_jtoch  = IntVar(gridpanel)
        self._edit_msgyid = IntVar(gridpanel)
        self._a_i1=getResourcesPhotoimage('save-64x64.png')

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

        gridpanel.addItem(ttk.Label(gridpanel,text="Name (max 10 chars)",style="Bold.TLabel"))
        gridpanel.addItem(ttk.Label(gridpanel,text="DTMF ID (max 10 digits)",style="Bold.TLabel"))
        gridpanel.addItem(ttk.Label(gridpanel,text="Talk CH ",style="Bold.TLabel"))
        gridpanel.addItem(ttk.Label(gridpanel,text="Messagy ID ",style="Bold.TLabel"))
        gridpanel.addItem(ttk.Button(gridpanel,image=self._a_i1, command=self._clicSaveEdit ), rowspan=2)

        gridpanel.nextRow()
        
        gridpanel.addItem(TV_Entry(gridpanel, self._edit_name,width=16))
        gridpanel.addItem(TV_Entry(gridpanel, self._edit_dtmfid,width=10))
        gridpanel.addItem(TV_Entry(gridpanel, self._edit_jtoch,width=4))
        gridpanel.addItem(TV_Entry(gridpanel, self._edit_msgyid,width=4))

        return gridpanel

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

        if not self.sel_item:
            self.parent_class.stat.showMessageBox("Please select a row")

        self.sel_item.qscntc_dtmfid=self._edit_dtmfid.get()
        self.sel_item.qscntc_name=self._edit_name.get()
        self.sel_item.qscntc_jtoch=self._edit_jtoch.get()
        self.sel_item.qscntc_msgyid=self._edit_msgyid.get()
        
        self.parent_class._treeView_refreshUpdated(self.sel_item)

    # -------------------------------------------------------------------------------
    # Parent request to show this row
    def showThisRow(self, sel_item : Qcontact_row ):
        
        self.sel_item=sel_item  # will be used in the save
        
        self._edit_dtmfid.set(sel_item.qscntc_dtmfid)
        self._edit_name.set(sel_item.qscntc_name)
        self._edit_jtoch.set(sel_item.qscntc_jtoch)
        self._edit_msgyid.set(sel_item.qscntc_msgyid)
        


                


    
    
    
    