/*********************************************************************************
 *
 *  lora_aprs_node_pico is a LoRa APRS KISS modem with additional PE1RXF telemetry
 *  capabilities. It runs on a Raspberry Pi Pico.
 *
 * (C)2023 M.T. Konstapel https://meezenest.nl/mees
 *
 * This file is part of lora_aprs_node_pico.
 *
 *    lora_aprs_node_pico is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    lora_aprs_node_pico is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with lora_aprs_node_pico. If not, see <https://www.gnu.org/licenses/>.
 *
 **********************************************************************************/
#include "kiss.h"

/*
 * KISS encoder uses two steps:
 * 
 * Step 1: encodes the LoRa APRS frame into an AX.25 frame. DONE
 * Step 2: encapsulates the AX.25 frame into a KISS frame. DONE
 * 
 *  struct aprs_frame {
 *      uint8_t source_address[10];
 *      uint8_t digi_path[255];
 *      uint8_t data_field[255];
 *      uint8_t message[255];
 *      uint8_t digis[10][10];
 *      uint8_t acknowledge_number[255];
 *      bool acknowledge_request = false;
 *      uint16_t server_command = 0;
 *      uint16_t number_of_digipeaters = 0;
 *      
 *      uint8_t valid_apsr_data = false;
 *  };
 *
 */
uint16_t KissClass::EncodeFrame(struct aprs_frame *aprsframe, struct ax25_frame *ax25frame)
{
    uint8_t *encoded_call;
    uint16_t cnt=0;
    uint16_t position = 0;
    uint8_t digi_cnt = 0;
    uint8_t escaped_string_cnt=0;
    
    //printf("Encode KISS");
    
    // Destination call
    //printf("Destination: ");
    encoded_call = EncodeCall(aprsframe->digis[0]);
    cnt = 0;
    while (cnt < 7)
    {
        ax25frame->complete[position] = *(encoded_call+cnt);
        //printf("0x%X ", *(encoded_call+cnt));
        //printf("0x%X ", ax25frame->complete[position]);
        cnt++;
        position++;
    }
    //printf("\n");
    
    // Source call
    //printf("Source: ");
    cnt=0;
    encoded_call = EncodeCall(aprsframe->source_address);
    while (cnt < 7)
    {
        ax25frame->complete[position] = *(encoded_call+cnt);
        //printf("0x%X ", *(encoded_call+cnt));
        //printf("0x%X ", ax25frame->complete[position]);
        cnt++;
        position++;
    }
    //printf("\n");
    
    // No digipeaters in path, so destination is the last call and therefore the 'last' flag should be set
    if (aprsframe->number_of_digipeaters == 0)
    {
        ax25frame->complete[position-1] |= 0x01;
    }
    else
    {

        // Path (digipeaters)
        //printf("Path: ");
        cnt=0;
        digi_cnt = 1;       // Start at position 1 as position 0 contains destination (which we already encoded)
        while (aprsframe->number_of_digipeaters-- != 0) {
            
            encoded_call = EncodeCall(aprsframe->digis[digi_cnt]);            
            while (cnt < 7) {
                ax25frame->complete[position] = *(encoded_call+cnt);
                //printf("0x%X ", *(encoded_call+cnt));
                //printf("0x%X ", ax25frame->complete[position]);
                cnt++;
                position++;
            }
            digi_cnt++;
            cnt = 0;
            
        }
        //printf("\n");
        
        // Set 'last' flag
        ax25frame->complete[position-1] |= 0x01;
    }
    
    // Add control fields
    ax25frame->complete[position++] = 0x03;
    ax25frame->complete[position++] = 0xF0;
    
    // All the dificult bits are done, now we just add the payload.
    //printf("Data:");
    cnt = 0;
    while (aprsframe->data_field[cnt] != 0 && cnt < 256) {
        ax25frame->complete[position] = aprsframe->data_field[cnt];
        //printf("0x%X ", ax25frame->complete[position]);
        cnt++;
        position++;
    }
    //printf( "\n");
    
    // Store length of AX25 frame
    ax25frame->lenght = position;
    
    // Encapsulate AX.25 frame in KISS frame (including escaping FEND codes)    
    putchar(FEND);
    putchar(CMD_DATA);
    escaped_string_cnt=0;
    ax25frame->encoded_kiss_frame[escaped_string_cnt++] = FEND;
    ax25frame->encoded_kiss_frame[escaped_string_cnt++] = CMD_DATA;
    cnt=0;
    position=ax25frame->lenght;
    while (position-- != 0)
    {
        // Escape FESC and TFEND
        if (ax25frame->complete[cnt] == FEND) {
            putchar(FESC);
            putchar(TFEND);
            ax25frame->encoded_kiss_frame[escaped_string_cnt++] = FESC;
            ax25frame->encoded_kiss_frame[escaped_string_cnt++] = TFEND;
        } else if (ax25frame->complete[cnt] == FESC) {
            putchar(FESC);
            putchar(TFESC);
            ax25frame->encoded_kiss_frame[escaped_string_cnt++] = FESC;
            ax25frame->encoded_kiss_frame[escaped_string_cnt++] = TFESC;
        } else {
            putchar(ax25frame->complete[cnt]);
            ax25frame->encoded_kiss_frame[escaped_string_cnt++] = ax25frame->complete[cnt];
        }
        cnt++;
    }
    putchar(FEND);
    ax25frame->encoded_kiss_frame[escaped_string_cnt++] = FEND;
    //printf("\n");
    

    return 0;
}

/*
 * Decodes a KISS frame from the usb serial port
 * 
 * Input : string starting with FEND and ending with FEND
 * Output: filled struct AprsFrame
 * Return: 0 = OK, 1 = ERROR, 2 = EXIT KISS MODE
 */
uint16_t KissClass::DecodeFrame(uint8_t string[], struct kiss_tx_frame *kisstxframe)
{
    uint16_t position  =0 ;
    uint16_t loop_counter = 0;
    uint16_t cnt  =0 ;
    uint16_t ssid = 0;
    uint16_t last_digi = 0;
    uint16_t digi_cnt = 0;
    
    //printf("Decode KISS");
    
    kisstxframe->valid_data = false;
    
    // Not a valid frame
    if (string[position] != FEND)
        return 1;
    
    position++;
    
    // Is data frame: we de-escape the string and put it back in the same string with a NULL at the end as terminator.
    if (string[position] == CMD_DATA)
    {
        position++;
        while (string[position] != FEND)
        {
            // De-escape codes
            if (string[position] == FESC && string[position+1] == TFEND)
            {
                string[cnt] = FEND;
                position++;
            }
            else if (string[position] == FESC && string[position+1] == TFESC)
            {
                string[cnt] = FESC;
                position++;
            }
            else {
                string[cnt] = string[position];
            }
            cnt++;
            position++;
        }
       string[cnt] = 0;             //Terminate string
        
    }
    // Is command to exit KISS MODE
    else if (string[position] == CMD_EXIT_KISS) {
        return 2;
    }
    
    // If we are here we have a valid AX.25 frame in 'string' which is decapsulated from its KISS frame
    /*
    0-5 destination
    6 destination ssid
    7-12 source
    13 desitination sidd
    
    vanaf byte 13 LAST FLAG zoeken
    
    03
    f0
    payload tot aan NULL
    */
    
    // Decode destination call
    position = 0;
    cnt = 0;
    loop_counter = 0;
    while (loop_counter < 6)
    {
        kisstxframe->digis[0][cnt] = string[position] >> 1;
        // Remove space
        if (kisstxframe->digis[0][cnt] == ' ')
            cnt--;
        position++;
        loop_counter++;
        cnt++;
        //printf("%u ", position);
    }
    
    // Extract SSID
    //printf("position: %u\n", position);
    ssid = (string[position] & 0b00011110) >> 1;
    //printf("DIGI SSID: %u (%u)\n", ssid, position);
    
    // Place SSID behind call or not if it is zero
    if (ssid != 0) {
        kisstxframe->digis[0][cnt++] = '-';
        if (ssid < 10)
        {
            kisstxframe->digis[0][cnt++] = ssid + 48;
        }
        else
        {
            kisstxframe->digis[0][cnt++] = '1';
            kisstxframe->digis[0][cnt++] = (ssid%10)+48;
        }
    }
    // Is has-been-repeated flag set?
    if(string[position] & 0b10000000) {
        kisstxframe->digis[0][cnt] = '*';
        cnt++;
    }
    kisstxframe->digis[0][cnt] = 0;   //terminate string
    position++;
    
    // Decode source call
    cnt = 0;
    loop_counter=0;
    while (loop_counter < 6)
    {
        kisstxframe->source_address[cnt] = string[position] >> 1;
        // Remove space
        if (kisstxframe->source_address[cnt] == ' ')
            cnt--;
        position++;
        loop_counter++;
        cnt++;
        //printf("%u ", position);
    }
    
    // Extract SSID
    ssid = (string[position] & 0b00011110) >> 1;
    //printf("sources SSID: %u (%u)\n", ssid, position);
    
   // Place SSID behind call or not if it is zero
    if (ssid != 0) {
        kisstxframe->source_address[cnt++] = '-';
        if (ssid < 10)
        {
            kisstxframe->source_address[cnt++] = ssid + 48;
        }
        else
        {
            kisstxframe->source_address[cnt++] = '1';
            kisstxframe->source_address[cnt++] = (ssid%10)+48;
        }
    }
    // Is has-been-repeated flag set?
    if(string[position] & 0b10000000) {
        kisstxframe->source_address[cnt] = '*';
        cnt++;
    }
    kisstxframe->source_address[cnt] = 0;   //terminate string
    
    // Check LAST flag
    if ( (string[position] & 0b00000001))
        last_digi = 1;
    
    position++;
     
    // Decode digi path
    digi_cnt=1;
    while (last_digi == 0) {
        
        cnt = 0;
        loop_counter=0;
        while (loop_counter < 6)
        {
            kisstxframe->digis[digi_cnt][cnt] = string[position] >> 1;
            // Remove space
            if (kisstxframe->digis[digi_cnt][cnt] == ' ')
                cnt--;
            position++;
            loop_counter++;
            cnt++;
            //printf("%u ", position);
        }
        
        // Extract SSID
        ssid = (string[position] & 0b00011110) >> 1;
        //printf("DIGI  SSID: %u (%u)\n", ssid, position);
        
        // Place SSID behind call or not if it is zero
        if (ssid != 0) {
            kisstxframe->digis[digi_cnt][cnt++] = '-';
            if (ssid < 10)
            {
                kisstxframe->digis[digi_cnt][cnt++] = ssid + 48;
            }
            else
            {
                kisstxframe->digis[digi_cnt][cnt++] = '1';
                kisstxframe->digis[digi_cnt][cnt++] = (ssid%10) + 48;
            }
        }
     
        // Is has-been-repeated flag set?
        if(string[position] & 0b10000000) {
            kisstxframe->digis[digi_cnt][cnt] = '*';
            cnt++;
        }
        kisstxframe->digis[digi_cnt][cnt] = 0;   //terminate string
        
        // Check LAST flag (also ends when more than 10 digis)
        if ( (string[position] & 0b00000001) || digi_cnt > 8)
            last_digi = 1;
    
        digi_cnt++;
        position++;
    }
    kisstxframe->number_of_digipeaters = digi_cnt-1;
    //printf("Digipeaters: %u \n",kisstxframe->number_of_digipeaters );
    
    //Skip the two control fields
    position++;
    position++;
   
    //Rest of string up to NULL is payload
    cnt = 0;
    kisstxframe->data_field[cnt++] = ':';
    while (string[position] != 0 && position < 512)
    {
        kisstxframe->data_field[cnt] = string[position];
        position++;
        cnt++;
    }
    kisstxframe->data_field[cnt] = 0;     //terminate string
    
    kisstxframe->valid_data = true;
    
    
    return 0;
}

/*
 * Encodes call according to AX.25 specs.
 * 
 * input : string with call and ssid as readable characters
 * output: pointer to memory location (7 bytes with the encoded call)
 * 
 */
uint8_t * KissClass::EncodeCall(uint8_t string[])
{
    uint8_t position = 0;
    uint8_t cnt = 0;
    uint8_t repeat_flag = 0;
    
    static uint8_t call[7] = { 0,0,0,0,0,0,0} ;
    uint8_t ssid = 0;
    
    // if asterix is pressent in string, than the message has been repeated, so set the 'has been repeated' flag in the ssid register
    // At the same time delete the asterix and place a NULL (string terminator) at its place. As the asterix should be the last character in the string, this should work.
    position = 0;
    while( string[position] != 0)
    {
        if (string[position] == '*') {
            repeat_flag = 1;
            string[position] == 0;
            break;
        }
        
        position++;
    }
    
    position = 0;
    // extract call
    while( string[position] != 0 || cnt < 6)
    {
        if ( string[position] == '-') {
            position++;
            break;
        } else {
            call[cnt] = string[position] << 1;            
        }
        cnt++;
        position++;
    }
    // pad with spaces to a length of 6
    while( cnt < 6 )
    {
        call[cnt++] = ' ' << 1;
    }
    
    // extract ssid
    cnt=0;
    while( string[position] != 0 )
    {
        if (string[position] >= 48 && string[position] <= 57) {
            ssid = 10*ssid + string[position]-48;
            position++;
        }
    }
    
    ssid = ssid << 1;
    ssid |= 0b01100000;
    
    if (repeat_flag == 1)            
        ssid |= 0b10000000;
    
    // add encoded ssid to encoded call array
    call[6] = ssid;
    
    /*
    //printf("SSID: 0x%X\n", ssid);
    
    cnt = 0;
    while (cnt < 7)
    {
        //printf("0x%X ", call[cnt++]);
    }
    //printf("\n");
    */
    
    return call;
    
}
