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

'''
# -------------------------------------------------------------------------
# GUI for the activation of custom radio bands for TX
# the KEY point is that the additional radio bands are bound to a well defined radio
# AND that the user should make it clear on WHO is going to use that radio
# the information WILL be visible in the radio to any law officer and you better have a reason for using that TX band
# The rationale is that there ARE legitimate uses for additional bands and I am NOT the one that has the authority 
# to deny a legitimate use
# it is up the the person that activate the band to make sure he is entitled to use
# The key point is that this is a SINGLE activation bound to an identity that can be verified if required
# writing a fake user name in the "identification" will just make you (as bearer of the radio) guilty of fraud
# NOTE: NO INFORMATION is sent anywhere in internet, the information is stored on the specific radio !

from __future__ import annotations

from Cryptodome.Cipher import AES
import struct
from tkinter import StringVar, IntVar, messagebox
import typing

from app_config import AppConfig, ConfigSavable
from glob_eeprom import EEblock
from glob_gui import  JtkPanelGrid, TV_Entry, GUI_hide_show_window,  \
    LogPanel, TV_EntryFrequency_MHz_1x, JtkComboIndexed, JtkPanelPackTop, JtkLabel 
from glob_mlang import MlangLabel
from ipp_parser import Qsk5_req_syscmd, IPPU_SYSCMD_reset
import ipp_parser
import qpystat
import qseeprom_gui
import tkinter.ttk as ttk 


raband_banduse_type_str = ['NOT Usable','Unlicensed','Ham-Radio','Licensed']


# ===================================================================
# maybe I should just let the radio encrypt ? 
# not really, I need to make sure that the CPU ID is shown to the user and has a way to check it 
# the API call should be the crypted part and the clear part
# 16 bytes of clear part (first byte the number of defined bands, can be zero, this is used as IV, also
# 16 bytes of crypted part(first byte is the same info) and the rest is the rest of ident
# then, depending on the number of bands, the crypted 16bytes that make two bands (in any case 16 bytes are crypted)
class Rabands_gui (ConfigSavable,GUI_hide_show_window):

    _rabands_max=4

    # ----------------------------------------------------------------
    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._stat.appconfig.addMyselfToSavables(self)

        self.work_panel = ttk.Frame(parent_frame)

        self.radio_bands : list[RadioBand] = []

        self.c_panel=self._newWorkPanel(self.work_panel)
        
        self.c_panel.pack(fill='both', expand=True)
        


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

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

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

        apanel.addItem(JtkLabel(apanel,"Additional Radio Bands in TX", style='Header1.TLabel',anchor='center' ))
        apanel.addItem(self._newWarningMessageLabel(apanel,"raband-must-be-used-lawfully" ))
        apanel.addItem(self._newWarningMessageLabel(apanel,"insert-name-city-state-of-person-requesting-additional-band"))
        
        apanel.addItem(self._newIdentityPanel(apanel))
        apanel.addItem(self._newTablePanel(apanel))
        
        # I wish for it to take only a small size and be on the left
        w_b=ttk.Button(apanel,text="⮞ Write on Radio" ,style="EEPanels.TButton" ,command=self._clickWriteRabands )
        apanel.addItem(w_b)
        
        return apanel

        
    # ----------------------------------------------------------------
    def _newIdentityPanel(self, parent_panel : ttk.Frame ) -> ttk.Frame:
        work_panel=JtkPanelGrid(parent_panel,padding=3)

        self._user_ident = StringVar(work_panel)
        self._cpu_id = StringVar(work_panel)
        
        work_panel.addItem(JtkLabel(work_panel,'User Identity'))
        work_panel.addItem(TV_Entry(work_panel,self._user_ident))
        
        work_panel.nextRow()
        
        work_panel.addItem(JtkLabel(work_panel,'CPU ID'))
        work_panel.addItem(TV_Entry(work_panel,self._cpu_id, width=32))
        
        work_panel.addItem(ttk.Button(work_panel,text="Request Identity" , command=self._reqRadioIdentity ))

        
        return work_panel

    # --------------------------------------------------------------------------------------
    def update_CPU_id(self, cpuid_hex : str ):
        self._cpu_id.set(cpuid_hex)

    # --------------------------------------------------------------------------------------
    def _reqRadioIdentity(self):
        cmd = ipp_parser.Qsk5_req_identity()
        self._stat.qconnect.queuePut(cmd)

    # ----------------------------------------------------------------
    def _newTablePanel(self, parent_panel : ttk.Frame ) -> ttk.Frame:
        work_panel=JtkPanelGrid(parent_panel,padding=3)
        
        work_panel.nextColumn(1)
        work_panel.addItem(JtkLabel(work_panel,'Frq start'))
        work_panel.addItem(JtkLabel(work_panel,'+ span (< 167Mhz)'))
        work_panel.addItem(JtkLabel(work_panel,'Use'))

        work_panel.nextRow()
        
        #v_power = ['xLow','Min','Med','Max']
        
        for index in range(self._rabands_max):
            raband = RadioBand(work_panel, self._parent_obj._log_gui)
            
            self.radio_bands.append(raband)
            
            work_panel.addItem(JtkLabel(work_panel,'Band%d ' % (index)))
            work_panel.addItem(raband.start_freq_1x)
            work_panel.addItem(raband._F_span_1x)
            work_panel.addItem(JtkComboIndexed(work_panel,raband.banduse_type, width=11, values=raband_banduse_type_str))

            work_panel.nextRow()
        
        return work_panel

    # -------------------------------------------------------------------
    # this should make the blob of bytes that should go to the radio
    # it is a bunch of bytes ....
    def _clickUpdateCpuid(self):
        self._cpu_id.set(self._stat.qconnect.cur_radio_cpu_id_hex)

    # -------------------------------------------------------------------
    def _showInfo(self, msg : str ):
        messagebox.showinfo( "Radio Bands", msg, parent=self.work_panel, type='ok')

    # -------------------------------------------------------------------
    # this should make the blob of bytes that should go to the radio
    # it is a bunch of bytes ....
    # the first 16 are part of the user identification + bands number at the end
    # the second 16 bytes are crypted and are the remaining 15 bytes user id plus the bands number, crypted
    # then there are two "bands" every 16 bytes, crypted. a band can be 0 , not used
    def _clickWriteRabands(self):
        user_ident = self._user_ident.get()
        
        user_bytes=user_ident.encode('ascii', errors='ignore')
        
        if len(user_bytes) < 25:
            self._showInfo("please insert at least 25 characters for user identitication")
            return

        aes_key = bytes.fromhex(self._cpu_id.get())
        if len(aes_key) != 16:
            self._showInfo("CPU ID lenght is not 16 bytes")
            return
        
        bands_bytes : list[bytes] = []
        bands_count = 0
        
        eeprom_row=bytes()
        
        # I need to merge the radio bands two by two so to obtain something that can be crypted
        for raband in self.radio_bands:
            raband_b=raband.toBytes()
            
            if raband_b:
                bands_count += 1
            else:
                raband_b=bytes(8)
        
            if not eeprom_row:
                # if the eeprom eb_row is empty
                eeprom_row=raband_b
            else:
                # it has the first part, need to add the second and save
                bands_bytes.append(eeprom_row + raband_b)
                eeprom_row=bytes()
        
        # I should write the bands even if zero, to clear up possible values !        
        init_vector  = struct.pack('<15sB',user_bytes[0:15],bands_count)
        first_block = struct.pack('<15sB',user_bytes[15:],bands_count)

        aes_cypher = AES.new(aes_key, AES.MODE_CBC, init_vector)

        eeprom_bytes : list[bytes] = []
        
        eeprom_bytes.append(init_vector)
        eeprom_bytes.append(aes_cypher.encrypt(first_block))

        for row in bands_bytes:
            self._println("b_bytes "+row.hex())
            eeprom_bytes.append(aes_cypher.encrypt(row))
                            
        # at this point I have all the blocks to write to eeprom
        # start writing blocks from this address
        a_address = 0x1F90

        ee_block : EEblock = self._stat.globeeprom.get_block_from_address(a_address)
        if not ee_block.isLoadedFromRadio:
            self._showInfo("Please load radio EEPROM")
            return
        
        for eb_row in eeprom_bytes:
            self._println(eb_row.hex())
            ee_block = self._stat.globeeprom.get_block_from_address(a_address)
            ee_block.set_bytearray(eb_row)
            a_address += len(eb_row)

        self._stat.globeeprom.ee_queueWriteToRadio(True)
        
        # then ask for radio reboot
        cmd = Qsk5_req_syscmd(IPPU_SYSCMD_reset)
        self._stat.qconnect.queuePut(cmd)
        

    # -------------------------------------------------------------------
    # implement the config savable
    def appImportConfig(self, cfg : AppConfig ):
        adict = cfg.getDictValue("rabands_cfg", {})
        
        if not adict:
            return 
        
        try:
            
            self._user_ident.set(adict['user_ident'])
            
            for index,row in enumerate(self.radio_bands):
                row.setFromConfig(adict['raband'+str(index)])
             
        except Exception as _exc :
            pass            

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

        adict['user_ident'] = self._user_ident.get()

        for index,row in enumerate(self.radio_bands):
            adict['raband'+str(index)] = str(row)

        cfg.setDictValue("rabands_cfg", adict )            
    
# -----------------------------------------------------------------------
# I wish an array of radio band    
class RadioBand():
    
    # --------------------------------------------------
    def __init__(self, parent_panel : ttk.Frame, log_panel : LogPanel ):
        
        self._log_panel = log_panel
        
        self.start_freq_1x = TV_EntryFrequency_MHz_1x(parent_panel)
        self._F_span_1x    = TV_EntryFrequency_MHz_1x(parent_panel)
        self.txpw_max      = IntVar(parent_panel)
        self.banduse_type  = IntVar(parent_panel)
        
    # --------------------------------------------------
    def _printf(self, msg : str ):
        self._log_panel.println(msg)
        
    # --------------------------------------------------
    def setFromConfig(self, config : str ):
        if not config:
            return 
        
        a_split=config.split(' ')
        
        if len(a_split) < 4:
            return
        
        self.start_freq_1x.set_value(int(a_split[0]))
        self._F_span_1x.set_value(int(a_split[1]))
        self.txpw_max.set(int(a_split[2]))
        self.banduse_type.set(int(a_split[3]))
        
    # --------------------------------------------------
    # @return a bytearray representing the band, can be empty !
    def toBytes(self) ->bytes:
        
        start_f : int =self.start_freq_1x.get_value()
        if not start_f:
            # not really an error
            self._printf("SKIP: start_f is empty")
            return bytearray()
        
        F_span : int = self._F_span_1x.get_value()
        if not F_span:
            self._printf("ERROR F_span is empty")
            return bytearray()
        
        if F_span >= 0x00FFFFFF:
            self._printf("ERROR span > 0x00FFFFFF "+str(F_span))
            return bytearray()
        
        txpw : int = self.txpw_max.get() & 0x0F
        usetype : int = self.banduse_type.get() & 0x0F
        
        # quick "fix" on need to have a non null band type
        if not usetype:
            usetype=1
        
        resto = (F_span << 8) | (txpw << 4) | usetype;
        
        return struct.pack('<II',start_f,resto)
        
        
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return '%d %d %d %d' % (self.start_freq_1x.get_value(), self._F_span_1x.get_value(), self.txpw_max.get(), self.banduse_type.get())        
         
                
