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


# the basic IPP chunk is a chunk id and a length
# NOTE that when the id is ODD the length is 16 bits and therefore there is a dummy char after the id

# this is the equivalent of toString()
    def __str__(self):

'''


from __future__ import annotations

import struct
from typing import Optional

from glob_ippqs import IppChunk
import glob_ippqs
from uvk5_lib import Qswio


# ===================================================================================
# You could immagine to wrap the GUI in the object.... possibly
# a parser is needed since a packet may have different chunks in it
# and ideally you need to unpack all of them
class Ippparser:
    
    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self, rxbuffer : bytearray ):
        self.rxbuffer : bytearray = rxbuffer
        self.begin_index=0;
        self.remain_len=len(rxbuffer)

    # -------------------------------------------------------------------------------
    # there is a possible chunk to parse ?
    # a chunk is at least two bytes long chunk id and chunk len
    def hasNextChunk(self) -> bool:
        if self.rxbuffer and self.remain_len >= 2:
            return True
        else:
            return False 
            
    # -------------------------------------------------------------------------------
    # there can be more than one chunk in the answer
    # NOTE that now chunks can be of 8 or 16 bits length !
    # TODO, I could dispatch the chunks here !
    def getNextIppChunk(self) -> Optional[IppChunk]:
        if not self.hasNextChunk():
            return None
        
        chunk_id=self.rxbuffer[self.begin_index]
        
        if chunk_id & 1 == 0:
            chunk_id,chunk_len=struct.unpack_from('<BB',self.rxbuffer,self.begin_index)
        else:
            chunk_id,chunk_len=struct.unpack_from('<BxH',self.rxbuffer,self.begin_index)
            
        if chunk_len < 1:
            # it happens if you mess up the header on packet creation
            print("getNextIppChunk: len=0 for id ",chunk_id)
            # it is fair to zap the packet, since I would otherwise loop here !
            self.remain_len=0
            return None
        
        chunk_data = self.rxbuffer[self.begin_index:self.begin_index+chunk_len]
        
        if len(chunk_data) != chunk_len:
            # it happens if you mess up the header on packet creation
            print("getNextIppChunk: BAD len ",chunk_len)
            # it is fair to zap the packet, since I would otherwise loop here !
            self.remain_len=0
            return None
        
        chunk=IppChunk(chunk_data, chunk_id, chunk_len )
        
        self.remain_len  = self.remain_len-chunk_len
        self.begin_index = self.begin_index+chunk_len
        
        if chunk_id == glob_ippqs.IPPU_pollprintf_resid:
            return glob_ippqs.Qsk5_res_RadioPrintf(chunk)
        elif chunk_id == glob_ippqs.IPPU_pollradiostat_resid:
            return glob_ippqs.Qsk5_res_RadioStatus(chunk)
        elif chunk_id == glob_ippqs.IPPU_pollqsmsg_resid:
            return glob_ippqs.Qsk5_res_PollMessages(chunk)
        elif chunk_id == glob_ippqs.IPPU_ident_resid:
            return glob_ippqs.Qsk5_res_RadioIdentity(chunk)
        elif chunk_id == glob_ippqs.IPPU_radiocfg_resid:
            return glob_ippqs.Qsk5_res_RadioConfiguration(chunk)
        elif chunk_id == glob_ippqs.IPPU_generic_resid:
            return glob_ippqs.Qsk5_res_Generic(chunk)
        elif chunk_id == glob_ippqs.IPPU_polldtmf16_resid:
            return glob_ippqs.Qsk5_res_PollDtmf(chunk)
        elif chunk_id == glob_ippqs.IPPU_res_pollipp16:
            return glob_ippqs.Qsk5_res_pollipp16(chunk)
        elif chunk_id == glob_ippqs.IPPU_scan_loudest_resid:
            return glob_ippqs.Qsk5_res_Loudest(chunk)
        elif chunk_id == glob_ippqs.IPPU_res_counters16:
            return glob_ippqs.Qsk5_res_Counters(chunk)
        elif chunk_id == glob_ippqs.IPPU_res_spectrum16:
            return glob_ippqs.Qsk5_res_Spectrum(chunk)
        elif chunk_id == glob_ippqs.IPPU_res_eeprom:
            return glob_ippqs.Qsk5_res_Eeprom(chunk)
        else:
            return chunk
         
         
         

# ===================================================================================
# A subclass can implement this class to dispatch a chunk
class IPP_dispatch():
    
    # --------------------------------------------------------------------------------
    # on creation declare the chunk id this satisfies and a note
    def __init__(self, chunk_id : int, chunk_note : str  ):
        self.chunk_id = chunk_id
        self.chunk_note = chunk_note
        
    # --------------------------------------------------------------------------------
    # subclasses must implement this one
    def dispatch_chunk(self, a_chunk : IppChunk ):
        pass
        


# ===================================================================================
# I need a map of IPP dispatch to be give to a parser to dispatch the received chunks
class IPP_dispatch_map():
    
    # --------------------------------------------------------------------------------
    def __init__(self):
        self.dispatch_map : dict[int, IPP_dispatch ]








































# ===================================================================================
# this wraps a command to request a scan for the loudest frequency around center freq
class Qsk5_req_scan_loud(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self, tmout_cs : int, center_F_1x : int ):
        glob_ippqs.Qsk5_command.__init__(self, "scan loudest")
        
        self.tmout_cs = tmout_cs
        self.center_F_1x = center_F_1x

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return self.command+' '+str(self.tmout_cs)


    # ---------------------------------------------------------------------------------------
    # 
    '''
    struct IPPU_scan_loudest_req
        {
        enum IPPU_cid chunk_id;      // IPPU_scan_loudest_reqid
        uint8_t  chunk_len;
        uint8_t  scan_time_cs;       // from 20 to 160 cs
        uint8_t  scan_flags;
        uint32_t scan_center_freq;   // anything below VHF_UHHF_THRESHOLD_1X enables VHF filtering
        };
    '''
    def send_request(self, qswio : Qswio ):

        s_chunk = struct.pack('<BBI',self.tmout_cs,0,self.center_F_1x)
        qswio.IPP_send_chunk(glob_ippqs.IPPU_scan_loudest_reqid,s_chunk)







# ===================================================================================
# this wraps a command to send a DTMF sequence
# note that the content must already be prepared as required
class Qsk5_req_send_dtmf(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    # NOTE that the msb bit of isIPP_and_digits holds if this is an IPP or normal data packet
    # @param isIPP_and_digits the number of DTMF to send, since every byte holds TWO dtmf and the digits MAY be odd
    # @param i_bytes the buffer holding the digits to send
    def __init__(self,play_len_cs : int, play_mute_cs : int, dtmf_digits_count : int, i_bytes : bytes, is_IPP : bool ):
        glob_ippqs.Qsk5_command.__init__(self, "send dtmf")
        
        self.play_len_cs = play_len_cs
        self.play_mute_cs = play_mute_cs
        self.dtmf_digits_count=dtmf_digits_count
        self.a_bytes=i_bytes
        self.is_IPP=is_IPP

    # --------------------------------------------------
    def __str__(self):
        return self.command+' ipp='+str(self.is_IPP)+' digits='+str(self.dtmf_digits_count)

    # ---------------------------------------------------------------------------------------
    # NOTE that the byte_array is already given
    '''
    struct IPPU_req_senddtmf
       {
       struct IPPU_header16 header; // can send a big amount of data !
       uint8_t  dtmf_play_cs;       // how long the dtmf should play in cs, eg 5
       uint8_t  dtmf_mute_cs;       // how long is the silence between tones ?
       bool     is_IPP;             // if true this is an IPP packet and AC should be added at the beginning
       uint8_t  padding;            //
       uint16_t dtmf_digits_count;  // Needed since this is a generic send and the number of digits may be ODD
       uint8_t  byte_array[0];      // the actual data to send, the length is chunk_len -sizeof(struct Ipchunk_dtmf_req)
       };
    '''
    def send_request(self, qswio : Qswio ):
        
        # I could put the packing into the command.... bute there is no real added value...
        s_chunk = struct.pack('<BB?xH',self.play_len_cs ,self.play_mute_cs ,self.is_IPP ,self.dtmf_digits_count)+self.a_bytes
        qswio.IPP_send_chunk16(glob_ippqs.IPPU_senddtmf16_reqid,s_chunk)












IPPU_SYSCMD_reset=1
IPPU_SYSCMD_reloadcfg=2   # from EEPROM
IPPU_SYSCMD_halt_cpu=3    # halt CPU so a manual reset can be done
IPPU_SYSCMD_BK4_read=4    # read the given Beken4 register
IPPU_SYSCMD_BK4_write=5   # write the given Beken4 register




# ===================================================================================
# Special request, mostly for debugging
class Qsk5_req_syscmd(glob_ippqs.Qsk5_command):


    '''
    struct IPPU_req_system_cmd
        {
        struct IPPU_header8 header;  // IPPU_specialcmd_req
        enum IPPU_SYSCMD_e sys_cmd;  // the actual command
        uint8_t  p0_uint8;           // a parameter, bytes
        uint16_t p1_uint16;          // the power level I wish, 0=low 1=mid 2,high
        uint16_t p2_uint16;          // free to use
        uint32_t p3_uint32;          // the frequency to apply
        };
    '''
    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self, w_cmd : int, p0_uint8=0, p1_uint16=0, p2_uint16=0, p3_uint32=0 ):
        glob_ippqs.Qsk5_command.__init__(self, "system command")
        
        self.w_cmd=w_cmd
        self.p0_uint8=p0_uint8
        self.p1_uint16=p1_uint16
        self.p2_uint16=p2_uint16
        self.p3_uint32=p3_uint32

    # --------------------------------------------------
    def __str__(self) -> str:
        return str(self.command)+' cmd='+str(self.w_cmd)

    # ---------------------------------------------------------------------------------------
    def send_request(self, qswio : Qswio ):
        
        s_chunk = struct.pack('<BBHHI',self.w_cmd,self.p0_uint8,self.p1_uint16,self.p2_uint16,self.p3_uint32)
        qswio.IPP_send_chunk(glob_ippqs.IPPU_systemcmd_reqid,s_chunk)


# ===================================================================================
# Sending radio configuration
class Qsk5_req_set_radio_params(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self, rxfreq_1x : int, power_bias : int, key_event : int ):
        glob_ippqs.Qsk5_command.__init__(self, "radio config")

        self.rxfreq_1x=rxfreq_1x
        self.power_bias=power_bias
        self.key_event = key_event;

    # --------------------------------------------------
    def __str__(self):
        return self.command+" rxf="+str(self.rxfreq_1x)+" pb="+str(self.power_bias)

    # ------------------------------------------------------
    # format and send a radio configuration
    '''
struct IPPU_req_radiocfg
   {
   struct IPPU_header8 header;   // IPPU_radiocfg_reqid
   uint16_t pad_even;
   uint32_t rx_freq_1x;       // receive on this f, zero means leave it alone, _1x means that you need to mult by 10
   uint32_t tx_freq_1x;       // send on this f, zero means leave it alone, can be > or < of rx
   uint8_t  tx_power;         // 0 means do not touch configuration, FF max power, intermediate numbers as appropriate
   uint8_t  select_chan_po;   // selected channel, PLUS ONE, so, 0 is a NULL value
   union IPPU_key_event_u key_event;   // if you wish to send a key event, write it here
   uint8_t  pad[1];
   };
    '''
    def send_request(self, qswio : Qswio ):
        
        s_chunk = struct.pack('<HIIBBBB',0,self.rxfreq_1x,self.rxfreq_1x,self.power_bias,0,self.key_event,0)
        qswio.IPP_send_chunk(glob_ippqs.IPPU_radiocfg_reqid,s_chunk)







# ===================================================================================
# this wraps a command to send a quansheng message
class Qsk5_req_send_qsmsg(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    def __init__(self,to_address, s_msg : str ):
        glob_ippqs.Qsk5_command.__init__(self, "send qsmsg")
        
        self.to_address=to_address
        self.s_msg=s_msg

    # --------------------------------------------------
    def __str__(self):
        return self.command+' '+str(self.to_address)+' '+self.s_msg

    # ------------------------------------------------------
    # format and send a quansheng send message
    def send_request(self, qswio : Qswio ):
        
        #print('QS send try '+str(self.s_msg))
        
        ascii_bytes = bytearray(self.s_msg,'ascii', errors='replace')
        ascii_len = len(ascii_bytes)
        
        chunk_len = struct.calcsize('<HBBHBBBBBB')+ascii_len
        
        pack_len = chunk_len+2
        
        s_chunk = struct.pack('<HBBHBBBBBB',pack_len,0x20,chunk_len,1,0,self.to_address,0,1,ascii_len,0)+ascii_bytes
        qswio.IPP_send_chunk(2,s_chunk)

        
# ===================================================================================
# this wraps a command to request a spectrum scan
class Qsk5_req_spectrum_scan(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self, start_F_1x : int, step_F_1x : int, step_count : int, stime_cs ):
        glob_ippqs.Qsk5_command.__init__(self, "spectrum scan")
        
        self.start_F_1x = start_F_1x
        self.step_F_1x = step_F_1x
        self.step_count = step_count
        self.stime_cs = stime_cs

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return self.command+' F='+str(self.start_F_1x)+' dF='+str(self.step_F_1x)
        
    # ---------------------------------------------------------------------------------------
    '''
    struct IPPU_req_spectrum
       {
       struct IPPU_header8 header;  // IPPU_req_spectrum
       uint16_t samples_count;      // this is the number of steps (samples) to do
       uint32_t start_freq_1x;      // the starting frequency
       uint32_t step_freq_1x;       // the step can be HUGE, but should really not be
       uint16_t sample_time_cs;     // the time to take for a sample in cs, so you can do 100 samples per second, max
       uint16_t padding_even;       //
       };
    '''
    def send_request(self, qswio : Qswio ):

        s_chunk = struct.pack('<HIIHH',self.step_count,self.start_F_1x, self.step_F_1x, self.stime_cs, 0)
        qswio.IPP_send_chunk(glob_ippqs.IPPU_req_spectrum,s_chunk)
        
        
        
        
        
        
# ===================================================================================
# this wraps a command to send a request identity
class Qsk5_req_identity(glob_ippqs.Qsk5_command):

    # -------------------------------------------------------------------------------
    # standard constructor
    def __init__(self):
        glob_ippqs.Qsk5_command.__init__(self, "req identity")

    # -------------------------------------------------------------------------------
    def send_request(self, qswio : Qswio ):
        qswio.IPP_send_chunk(glob_ippqs.IPPU_ident_reqid,bytes())

    # --------------------------------------------------
    def __str__(self):
        return self.command





























