'''
Created on Nov 30, 2025

@author: damiano
'''
import pathlib
import struct
import typing

from glob_class import IppChunk, IppChunkReq, WisocketTcp
import wiradio_gui


IPPW_req_poll    =2
IPPW_res_generic =4
IPPW_res_printf  =6
IPPW_req_datetime=8 
IPPW_res_datetime=10
IPPW_req_gpio    =12 
IPPW_res_gpio    =14
IPPW_req_wfs       =15
IPPW_res_wfs       =17
IPPW_res_dirfile   =19
IPPW_res_dirfile   =19
IPPW_res_filechunk =21

# look into wira_ipp.h, constants used for filesystem handling    
WFS_dir              =1
WFS_copy_to_computer =2   # copy a given file to computer
WFS_copy_to_wiradio  =3   # copy a given file to wiradio           
WFS_tbd              =4              
WFS_record           =5   # should be record start/stop           
WFS_play             =6   # should be play start/stop
WFS_delete           =7     


IPPW_WFS_FCHU_MAXLEN=1024


# ===================================================================================
# A generic res, mostly for error messages

class Wira_res_generic(IppChunk):

    # -------------------------------------------------------------------------------
    #   struct IPPW_chunk8 header;    // IPPU_generic_resid
    #   uint16_t from_reqid;          // the original request this response is bound to
    #   uint16_t reason;              // some sort of reason for the message
    #   uint16_t rescode;             // some sort of error code
    #   char     message[100];        // a short message
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)

        pattern = '<HHH'
        
        hdr_len = 2
        pa_len = struct.calcsize(pattern)
        
        from_reqid,reason,rescode=struct.unpack_from(pattern,self.rxbuffer,hdr_len)

        self.from_reqid=from_reqid
        self.reason=reason
        self.rescode=rescode

        msg_bytes = self.rxbuffer[hdr_len+pa_len:]
        self.msg_utf8 = msg_bytes.decode('utf8','ignore')  # this should be long exactly right
        
        # this actually works to remove trailing 0, split the string at first zero         
        #self.fname=a_fname.split('\x00', 1)[0]

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "{:d} {:d} {:d} {:s}".format(self.from_reqid,self.reason,self.rescode,self.msg_utf8)


# ===================================================================================
# a file dir list is a separate chunk, makes things easier
class Wira_res_filename(IppChunk):

    # -------------------------------------------------------------------------------
    # note that the given buffer INCLUDES the id and len
    #struct IPPW_chunk16 header;  // IPPW_res_dirfile
    #uint32_t cur_dir_index;      // this is the index of this entry, id ZERO, end of directory
    #uint32_t dirf_len_bytes;     // can be longer than 65K
    #char creation_date[14];      // AAAAmmddhhmmss
    #uint16_t fname_len;          // kind of redundant
    #char fname_utf8[0];          // the name, up to fill up the chunk
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)

        pattern = '<II14sH'
        
        hdr_len = 4
        pa_len = struct.calcsize(pattern)
        
        cur_dir_index,dirf_len_bytes,creation_date,fname_len=struct.unpack_from(pattern,self.rxbuffer,hdr_len)

        self.cur_dir_index=cur_dir_index
        self.dirf_len_bytes=dirf_len_bytes
        self.creation_date=creation_date.decode('utf8','ignore')

        pattern = "<{:d}s".format(fname_len)

        a_touple=struct.unpack_from(pattern,self.rxbuffer,hdr_len + pa_len)
        
        a_fname = a_touple[0]
        self.fname = a_fname.decode('utf8','ignore')  # this should be long exactly right
        
        # this actually works to remove trailing 0, split the string at first zero         
        #self.fname=a_fname.split('\x00', 1)[0]

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "{:d} {:s} {:d} {:s}".format(self.cur_dir_index,self.fname,self.dirf_len_bytes,self.creation_date)


# ===================================================================================
# you can "decode" into this one
class Wira_res_wfs(IppChunk):

    # -------------------------------------------------------------------------------
    # note that the given buffer INCLUDES the id and len
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)
        
        wfs_cid,_pad_a = struct.unpack_from('<BB', chunk.rxbuffer, 4)

        self.wfs_cid = wfs_cid
        
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "res  wfs_cid {:d}".format(self.wfs_cid)


# ===================================================================================
# you can "decode" into this one
class Wira_res_file_pull(IppChunk):

    # -------------------------------------------------------------------------------
    # note that the given buffer INCLUDES the id and len
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)

        fchu_index,fchu_count,fchu_bylen = struct.unpack_from('<HHH', chunk.rxbuffer, 4)

        self.fchu_index=fchu_index
        self.fchu_count=fchu_count
        self.fchu_bylen=fchu_bylen

    def isEof(self) -> bool:
        return self.fchu_index==self.fchu_count 
    
    def getFilePart(self) -> bytes:
        return self.rxbuffer[10:]
        
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "Wira_res_wfs_pull {:d} {:d} {:d} ".format(self.fchu_index,self.fchu_count,self.fchu_bylen)

# ===================================================================================
# you can "decode" into this one
class Wira_res_datetime(IppChunk):

    # -------------------------------------------------------------------------------
    # note that the given buffer INCLUDES the id and len
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)

        year,command,month,day,hour,minut,seconds=struct.unpack_from('<HBBBBBB',self.rxbuffer,2)

        self.year=year
        self.command=command
        self.month=month
        self.day=day
        self.hour=hour
        self.minut=minut
        self.seconds=seconds

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "{:d}-{:d}-{:d} {:d}:{:d}:{:d}".format(self.year,self.month,self.day,self.hour,self.minut,self.seconds)


# ===================================================================================
# you can "decode" into this one
class Wira_res_gpio(IppChunk):

    # -------------------------------------------------------------------------------
    # note that the given buffer INCLUDES the id and len
    def __init__(self,chunk : IppChunk ):
        IppChunk.__init__(self,chunk.rxbuffer)

        _command,gpio_ptt,gpio_a,gpio_b,gpio_c,gpio_d,gpio_e,gpio_f,gpio_g=struct.unpack_from('<BBBBBBBBB',self.rxbuffer,2)

        self.gpio_ptt=gpio_ptt
        self.gpio_a=gpio_a
        self.gpio_b=gpio_b
        self.gpio_c=gpio_c
        self.gpio_d=gpio_d
        self.gpio_e=gpio_e
        self.gpio_f=gpio_f
        self.gpio_g=gpio_g

        in_ptt,in_b,in_c,in_d,in_sw2=struct.unpack_from('<BBBBxxxB',self.rxbuffer,11)

        self.in_ptt=in_ptt
        self.in_b=in_b
        self.in_c=in_c
        self.in_d=in_d
        self.in_sw2=in_sw2

    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        l1 = "{:d} {:d} {:d} {:d} {:d} {:d}".format(self.gpio_ptt,self.gpio_a,self.gpio_b,self.gpio_c,self.gpio_d,self.gpio_e)
        l2 = "{:d} {:d} {:d} {:d} {:d}".format(self.in_ptt,self.in_b,self.in_c,self.in_d,self.in_sw2)
        return l1+'\n'+l2


# =====================================================================================
# You can queue a command to be sent and then a response will be received
# now, how do I handle something like reading a file ?
# in theory, send a request for a chunk, receive a response, parse it and send another request
# so, possibly, there is no need to have a "tight loop", what is needed is a well formed request response
class Wira_request:
    
    # -------------------------------------------------------------------------------
    # standard constructor requires the command identifier that is later used, if needed
    def __init__(self,command):
        self.command=command

        self._chunk_list : list[IppChunkReq] = list()
        
        # if defined it is a special parser 
        self._special_parser : wiradio_gui.Wipp_parse_packet = typing.cast(wiradio_gui.Wipp_parse_packet,None)
        
    # --------------------------------------------------------------------------------
    # Add a chunk to the list of chunks to send
    def add_chunk(self, a_chunk : IppChunkReq):
        
        if a_chunk:
            self._chunk_list.append(a_chunk)
    
    # --------------------------------------------------------------------------------
    # send the request trough the given TCP socket
    def send_request(self, a_socket : WisocketTcp):
        
        total_len = 2
        
        a_bytes = struct.pack('<H',total_len)
        
        for a_chunk in self._chunk_list:
            
            chunk_bytes = a_chunk.chunk_bytes
            
            a_bytes = a_bytes + chunk_bytes
            
        a_bytearray = bytearray(a_bytes)
                    
        struct.pack_into('<H', a_bytearray, 0, len(a_bytearray))     
        
        a_socket.socket_sendall(a_bytearray) 

        #print(a_bytearray.hex())
    
    
    # --------------------------------------------------
    # this is the equivalent of toString()
    # if you wish more details, override it into subclasses
    def __str__(self):
        return 'Wira_request: '+str(self.command)
        
    
# ===================================================================================
# You can create e file chunk to send here
class Wira_req_file_chunk(IppChunkReq):

    # -------------------------------------------------------------------------------
    # for a chunk being sent I need to know the chunk index and the buffer to send
    def __init__(self, fchu_index : int, fchu_count : int, c_bytes : bytes ):
        IppChunkReq.__init__(self,IPPW_res_filechunk)
        
        c_bytes_len = len(c_bytes)
        
        self.append_bytearray(struct.pack('<HHH', fchu_index, fchu_count, c_bytes_len))     

        self.append_bytearray(c_bytes)
        
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return "Wira_req_file_chunk {:d} {:d} ".format(self.chunk_id,self.get_chunk_len())
    
        
        
# ===================================================================
# add the part that is common to all IPPW_req_wfs        
class IPPW_wfs_hdr(IppChunkReq):        

    # ----------------------------------------------------------------
    def __init__(self, wfs_cid):

        IppChunkReq.__init__(self,IPPW_req_wfs)
        
        self.append_bytearray(struct.pack('<Bx', wfs_cid))     
        
# ===================================================================================
# request to push a given file TO wiradio
# the response should be a "go ahead" or an error
# follow a stream of blocks, where it is possible to have an error block
class Wira_push_file(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, fname : pathlib.Path ):
        
        IPPW_wfs_hdr.__init__(self,WFS_copy_to_wiradio)

        short_fname = fname.name

        fname_utf8 = short_fname.encode('utf8')

        chunk_data = struct.pack('<H', len(fname_utf8))+fname_utf8     

        self.append_bytearray(chunk_data)


# ===================================================================================
# request to pulla a given file from wiradio
class Wira_wfs_delete_file(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, fname : str ):
        
        IPPW_wfs_hdr.__init__(self,WFS_delete)

        fname_utf8 = fname.encode('utf8')

        chunk_data = struct.pack('<H', len(fname_utf8))+fname_utf8     

        self.append_bytearray(chunk_data)


# ===================================================================================
# request to pulla a given file from wiradio
class Wira_pull_file(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, fname : str ):
        
        IPPW_wfs_hdr.__init__(self,WFS_copy_to_computer)

        fname_utf8 = fname.encode('utf8')

        chunk_data = struct.pack('<H', len(fname_utf8))+fname_utf8     

        self.append_bytearray(chunk_data)

# ===================================================================================
# 
class Wira_req_directory(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self):
        
        IPPW_wfs_hdr.__init__(self,WFS_dir)
        
        
# ===================================================================================
# 
class Wira_req_record_fname(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, fname : str ):
        
        IPPW_wfs_hdr.__init__(self,WFS_record)
        
        fname_utf8 = fname.encode('utf8')

        chunk_data = struct.pack('<H', len(fname_utf8))+fname_utf8     

        self.append_bytearray(chunk_data)


# ===================================================================================
# 
class Wira_req_play_fname(IPPW_wfs_hdr):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, fname : str ):
        
        IPPW_wfs_hdr.__init__(self,WFS_play)
        
        fname_utf8 = fname.encode('utf8')

        chunk_data = struct.pack('<H', len(fname_utf8))+fname_utf8     

        self.append_bytearray(chunk_data)
        
        


# ===================================================================================
# 
class Wira_req_datetime(IppChunkReq):

    # --------------------------------------------------------------------------------
    # can set or request datetime
    def __init__(self, set_req=0, year=0, month=0, day=0, hour=0, minute=0, seconds=0 ):

        IppChunkReq.__init__(self,IPPW_req_datetime)
        
        abuffer = struct.pack('<HBBBBBB',year,set_req,month,day,hour,minute,seconds)
                
        self.append_bytearray(abuffer)

# ===================================================================================
# 
class Wira_req_gpio(IppChunkReq):

    # --------------------------------------------------------------------------------
    # can set or request gpio
    # if set_Req is 1, it is a SET, otherwise a get
    def __init__(self, set_req=0, gpio_ptt=0, gpio_a=0, gpio_b=0, gpio_c=0, gpio_d=0 ):

        IppChunkReq.__init__(self,IPPW_req_gpio)
        
        abuffer = struct.pack('<BBBBBBxx',set_req,gpio_ptt,gpio_a,gpio_b,gpio_c,gpio_d)
                
        self.append_bytearray(abuffer)
        
        
        