'''
/** 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 StringVar, IntVar, ttk
import typing

from app_config import AppConfig, ConfigSavable
from glob_fun import getResourcesFname, getResourcesPhotoimage
from glob_gui import JtkWinToplevel, TV_Entry, GUI_hide_show_window, \
    JtkPanelPackTop, JtkLabel, JtkPanelPackLeft, JtkTableColdef, JtkTable,\
    ImageLabel 
import glob_ippqs
from ipp_parser import Qsk5_req_send_qsmsg
import qpystat


qsmsgcol_idx="qsmsgcol_idx"
qsmsgcol_from_id="qsmsgcol_from_id"
qsmsgcol_to_id="qsmsgcol_to_id"
qsmsgcol_msg_id="qsmsgcol_msg_id"  
qsmsgcol_status="qsmsgcol_status"  
qsmsgcol_text="qsmsgcol_text"         
    
# -------------------------------------------------------
# Generic way to define a map from a column name to some properties        
qsmsgcol_map = { 
    qsmsgcol_idx      : JtkTableColdef(qsmsgcol_idx     ,'Nr.',True, 40), 
    qsmsgcol_from_id  : JtkTableColdef(qsmsgcol_from_id ,"From",True,50),
    qsmsgcol_to_id    : JtkTableColdef(qsmsgcol_to_id   ,"To",True, 50),
    qsmsgcol_msg_id   : JtkTableColdef(qsmsgcol_msg_id  ,"m ID", True, 50),
    qsmsgcol_status   : JtkTableColdef(qsmsgcol_status  ,"Status",False,60),
    qsmsgcol_text     : JtkTableColdef(qsmsgcol_text    ,"Text",False,300,True),
    }


# ====================================================================
# the new row must be given an ID
class Qsmsg_row():

    # ----------------------------------------------------------------
    def __init__(self, row_idx : int, a_msg : glob_ippqs.Qsk5_res_PollMessages ):
        self.row_idx=row_idx
        self.clear()
        
        self.from_id=a_msg.from_uid
        self.to_id=a_msg.to_uid
        self.msg_id=a_msg.msg_id
        self.m_text=a_msg.messagestring
        self.status=a_msg._get_StatusString()
        
        if a_msg.is_rx:
            self.row_tag='msg_is_rx'
        elif a_msg.is_tx:
            self.row_tag='msg_is_tx'
        else:
            self.row_tag=''

    # ----------------------------------------------------------------
    def clear(self):
        self.from_id=0
        self.to_id=0
        self.msg_id=0
        self.status='null'
        self.m_text=''
        
    # -----------------------------------------------------------------
    # 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,
            self.from_id,
            self.to_id,
            self.msg_id,
            self.status,
            self.m_text
            )
            
        return risul         




# ====================================================================
class Qsmsg_list(list[Qsmsg_row]):
    
    def __init__(self):
        pass
    
    
    # ----------------------------------------------------------------
    def get_row(self, from_id : int, to_id : int, msg_id : int) -> Qsmsg_row:
        
        for row in self:
            arow : Qsmsg_row = row
            
            if arow.from_id==from_id and arow.to_id == to_id and arow.msg_id == msg_id:
                return arow;
    
        return typing.cast(Qsmsg_row,None)
    
    # ----------------------------------------------------------------
    # Update the row, if it exist, otherwise append
    def update_row(self, a_msg : glob_ippqs.Qsk5_res_PollMessages):
    
        arow = self.get_row(a_msg.from_uid, a_msg.to_uid, a_msg.msg_id)
        
        if arow:
            arow.status=a_msg._get_StatusString()
        else:
            self.append_chunk(a_msg)
    
    # -----------------------------------------------------------------
    def append_chunk(self, a_msg : glob_ippqs.Qsk5_res_PollMessages):
        m_idx=len(self)+1
        a_row = Qsmsg_row(m_idx, a_msg)
        self.append(a_row)
        



# ====================================================================
class QsmsgGui (ConfigSavable,GUI_hide_show_window):

    # ----------------------------------------------------------------
    def __init__(self, stat : qpystat.Qpystat):
        self.stat = stat
        
        self.stat.appconfig.addMyselfToSavables(self)
        
        self._qsmsg_list = Qsmsg_list()

        self._toplevel = JtkWinToplevel("Messagy window")
        
        a_panel=self._newCenterPanel(self._toplevel)

        a_panel.pack(fill='both', expand=True)
        
        self.GUI_hide_window() 
        
        self._println("Messagy init complete \u25C4")

    # -------------------------------------------------------------------------------
    # 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):
        a_panel = JtkPanelPackTop( parent) #, borderwidth=3, relief='ridge')
        
        a_panel.addItem(self._newNorthLine(a_panel), fill='x')
        
        self._table_view = JtkTable(a_panel, qsmsgcol_map );
        a_panel.addItem(self._table_view.getComponent(),fill='both', expand=True)

        tk_tbl : ttk.Treeview = self._table_view.getTable()
        
        tk_tbl.tag_configure('msg_is_rx',  background='#FFFCAF')
        tk_tbl.tag_configure('msg_is_tx',  background='#ECFDED')
        tk_tbl.bind('<<TreeviewSelect>>', self._jtable_row_selected)
        
        # --------------------------------------------------- now, the editing panel
        a_panel.addItem(self._newEditmsgLine(a_panel))
        
        return a_panel

    # -------------------------------------------------------------------------------
    # Need a panel for top row
    def _newNorthLine(self, parent) -> ttk.Widget:

        a_panel=JtkPanelPackLeft(parent ) #, borderwidth=3, relief='ridge')
    
        a_panel.addItem(ImageLabel(a_panel,getResourcesPhotoimage('messagy-top-left.png')))
        
        a_panel.addItem(JtkLabel(a_panel,"Press ENTER to send message", style='Header1.TLabel',anchor='e' ),fill='x', expand=True)
    
    
        return a_panel
    
    
        
    # -------------------------------------------------------------------------------
    # make a new frame and put an input line in it
    def _newEditmsgLine(self, parent):

        a_panel=JtkPanelPackLeft(parent)

        # this variable will store whatever the user has typed
        self._edit_toid  = IntVar(a_panel)
        self._edit_qsmsg = StringVar(a_panel)
        self._edit_linelen = IntVar(a_panel, 0)
        
        a_panel.addItem(JtkLabel(a_panel,"To ID "))
        a_panel.addItem(TV_Entry(a_panel, self._edit_toid, width=4))

        a_panel.addItem(JtkLabel(a_panel,"  Message "))

        # this is needed to arrive to the validator function
        self.v_command = (a_panel.register(self._onEntryValidate), '%P', '%V')
        
        # ok let me create the entry
        input_entry=TV_Entry(a_panel, self._edit_qsmsg, validate='all', validatecommand=self.v_command)
        # if one press enter does the given action
        input_entry.bind('<Return>', self._sendMessageLineEnter)

        a_panel.addItem(input_entry,fill='x', expand=True)

        self._entry_linelen=TV_Entry(a_panel, self._edit_linelen, width=3)
        a_panel.addItem(self._entry_linelen)
        
        return a_panel
    
    
    # ---------------------------------------------------------------
    # this is bound to keys typed in the message
    def _onEntryValidate(self, n_val, operation) -> bool:

        #self._println('_onEntryValidate '+str(n_val))
        
        line_len = len(n_val)
        
        if line_len > 40:
            return False
        
        self._edit_linelen.set(line_len)
        
#        if line_len > 39:
#            self._entry_linelen.configure(style='Red.TEntry')
#        elif line_len > 17:
        if line_len > 17:
            self._entry_linelen.configure(style='Red.TEntry')
        else:
            self._entry_linelen.configure(style='TEntry')
            
        return True


    # -------------------------------------------------------------------
    # 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))



    # -------------------------------------------------------------------
    # prints something on the application log
    def _println(self, msg):
        self.stat.app_println(msg)    
        
        
    # -------------------------------------------------------------------
    # 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._qsmsg_list:
            e_row : Qsmsg_row = row
            
            self._jtable_ensureVisible(e_row)
            
        self._table_view.yview_moveto(1)

    # ------------------------------------------------------------------------
    # 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 : Qsmsg_row ):

        if self._table_view.row_exist(a_row.row_idx):
            
            self._jtable_refresh_using_row(a_row)
                
            #self._table_view.see_row(a_row.qsch_idx_po)
        else:
            v_values=a_row.getValuesForTreeview()
            self._table_view.row_append(a_row.row_idx,v_values, tags=a_row.row_tag)
        
    # ------------------------------------------------------------------------------
    # this should update the swing table content for the given row
    def _jtable_refresh_using_row(self, a_row : Qsmsg_row ):

        if not a_row:
            return
            
        self._table_view.set_row(a_row.row_idx,a_row.getValuesForTreeview(),a_row.row_tag)   
        
        
    # -------------------------------------------------------------------
    # called on NON swing thread when a message is received
    def parseIncomingChunk (self, a_msg : glob_ippqs.Qsk5_res_PollMessages ):
        
        if a_msg.is_ack:
            self._qsmsg_list.update_row(a_msg)
        else:
            self._qsmsg_list.append_chunk(a_msg)

            if a_msg.is_rx:
                wav_beep = getResourcesFname('messagy_new_msg.wav')
                self.stat.wiremote_gui.play_wav_file(wav_beep)
            
        self.GUI_refresh()
        

        
        
    # --------------------------------------------------------------------
    # show a window that has been hidden using the withdraw method
    def GUI_show_window(self):
        self._toplevel.deiconify()    

    # --------------------------------------------------------------------
    # hide completely a window
    def GUI_hide_window(self):
        self._toplevel.withdraw()    

    
    # -------------------------------------------------------------------------------
    def _sendMessageLineEnter(self, _extra=None):

        anid = 33

        try:
            anid = int(self._edit_toid.get())   
        except ValueError:
            pass
            
        self._edit_toid.set(anid)            
    
        aline = self._edit_qsmsg.get()
        self._edit_qsmsg.set('')
        
        self._println("to "+str(anid)+" sending "+aline)
        
        cmd = Qsk5_req_send_qsmsg(anid,aline)
        self.stat.qconnect.queuePut(cmd)



    # -------------------------------------------------------------------
    # implement the config savable
    def appImportConfig(self, cfg : AppConfig ):
        adict = cfg.getDictValue("qsmsg_cfg", {})
        
        try:
            self._toplevel.setGeometry(adict['gui_cfg'])
            self._edit_toid.set(adict['edit_toid'])    
        except Exception as _exc :
            pass            

    # -------------------------------------------------------------------
    # implement the config savable
    def appSaveConfig(self, cfg : AppConfig ):
        adict : dict[str,typing.Any] = {}

        adict['gui_cfg'] = self._toplevel.getGeometry()
        adict['edit_toid'] = self._edit_toid.get()

        cfg.setDictValue("qsmsg_cfg", adict )            
    
    

        
        

