/**
 *@file ftds_decode.c
 *
 *@brief This file contains functions that provides demonstration of
 *       usage of FTDS frame decoding APIs
 *
 *The functions provided in the file provides demonstration on
 *usage of FTDS frame decoding APIs
 *
 * @copyright Copyright 2023 Antaris, Inc.
 *
 * 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.
 *        usage of TC TM Library APIs
 */

#include "stdio.h"
#include "stdlib.h"

/******************************************************************************/
/*                      Include headers of the component                      */
/******************************************************************************/

#include "ftds_decode.h"
#include "download_def.h"
#include "file_integrity_check.h"
#include "file_transfer.h"
#include "ft_timer.h"
#include "ftci_decode.h"
#include "ftsr.h"
#include "message_frame_def.h"
#include "notify_apps.h"
#include "upload_def.h"
#include "segmentation.h"
#include "ftfci.h"
#include "ftfci_decode.h"
#include "logger.h"
#include "ft_sender.h"
#include "ft_receiver.h"
#include "ft_debug.h"


/******************************************************************************/
/*                  Definition of local function like macros                  */
/******************************************************************************/

//#define FT_DEBUG
/*
 *  packet loss detcetion mechanism
 *  it will report every FTSR_SEQ_LOSS_REPORT_LIMIT
 *  If maximum loss count is reached it will send head change request
 *
 */


#define FTSR_SEQ_LOSS_REPORT_LIMIT 4

/* if missing sequence are not sent by sender, it will ask re transmission for every 20 seq are received */
#define PERIODIC_RESEND_RQST_CNT_LIMIT 20


#define STATIC_FTDS_RX_BUF_LENGTH 200

/******************************************************************************/
/*                       Definition of local variables                        */
/******************************************************************************/

static uint8_t 		ftds_busy = 0;
tc_tm_id_e_ft       tctm_id_inform_ftds = FT_TC_FTDS;


/******************************************************************************/
/*                      Definition of exported variables                      */
/******************************************************************************/

extern uint8_t 	ftds_ack_en;


/******************************************************************************/
/*                  Declaration of local function prototypes                  */
/******************************************************************************/

/**
 * @brief This function is used to decode the FTDS frame
 *
 *
 * @param[in]  void  : none
 *
 * @return none
 * @retval void : none
 */
static uint8_t ftds_decode(void);


/**
 * @brief This function is used to verify the CRC for downloaded bulkdata
 *
 *
 * @param[in]  void  : none
 *
 * @return none
 * @retval void : none
 */
static void check_integrity_bulkdata(void);

/**
 * @brief This function is used to verify the CRC for downloaded file
 *
 *
 * @param[in]  void  : none
 *
 * @return none
 * @retval void : none
 */
static void check_integrity(void);

/**
 * @brief This function will identify missing sequence during transmission
 *
 *
 * @param[in]  current_seq_num  : current sequence number
 *
 * @return status of sequnec loss detection
 * @retval it will return 0 if detection mechanism failed to log the missing sequence in drop list
 */
static uint8_t packet_loss_detection_recovery(uint32_t current_seq_num);

/**
 * @brief This function is used to add sequence ID in drop list
 *
 *
 * @param[in]  seq_num  : sequnec number to add in list
 *
 * @return status of updating list with new ID
 * @retval 1 indicates new ID added in drop list successfully
 */
static uint8_t save_lost_seq_id(uint32_t seq_num);


/**
* @brief This function will verify the received sequence ID is identical to lost sequence ID
*
*
* @param[in]  seq_num  : none
*
* @return status of the current sequence ID is re transmitted again
* @retval 1 indicates the sequence is available in drop list already, 0 indicates the ID is new
*/
static uint8_t get_missing_sequnec_status(void);

/**
 * @brief This function use to tell the buffer fill status
 *
 *
 * @param[in]  void  : none
 *
 * @return status of buffer space available
 * @retval return 1 if space available
 */
static uint8_t get_missing_sequnec_buffer_status(void);

/**
 * @brief This function will verify the received sequence ID is identical to lost sequence ID
 *
 *
 * @param[in]  seq_num  : none
 *
 * @return status of the current sequence ID is re transmitted again
 * @retval 1 indicates the sequence is available in drop list already, 0 indicates the ID is new
 */
static uint8_t check_lost_packet_is_recived(uint32_t seq_num);


/******************************************************************************/
/*                       Definition of local functions                        */
/******************************************************************************/

/**
 * @brief This function will verify the received sequence ID is identical to lost sequence ID
 *
 *
 */
static uint8_t check_lost_packet_is_recived(uint32_t seq_num)
{
	uint8_t ret_sts = 0;
	uint16_t u16cntr=0;
	while(u16cntr < MAX_PCKT_LOSS_DETECTION_CNTR)
	{
		if(seq_num == dwnld_rqst.data_loss.lost_packet_list[u16cntr])
		{
			ret_sts = 1;
			dwnld_rqst.data_loss.lost_packet_list[u16cntr] = 0;
			dwnld_rqst.data_loss.total_lost_segment_count--;
			break;
		}
		u16cntr++;
	}
	return ret_sts;
}

/**
 * @brief This function use to tell the buffer fill status
 *
 */
static uint8_t get_missing_sequnec_buffer_status(void)
{
	uint8_t ret =0;;
	if(dwnld_rqst.data_loss.total_lost_segment_count < MAX_PCKT_LOSS_DETECTION_CNTR)
	{
		ret = 1;
	}
	return ret;
}

/**
* @brief This function will verify the received sequence ID is identical to lost sequence ID
*
*/
static uint8_t get_missing_sequnec_status()
{
	uint8_t ret =0;;
	if(dwnld_rqst.data_loss.total_lost_segment_count == 0)
	{
		ret = 1;
	}
	return ret;
}

/**
 * @brief This function is used to add sequence ID in drop list
 *
 */
static uint8_t save_lost_seq_id(uint32_t seq_num)
{
	uint8_t ret_sts = 0;
	uint16_t u16cntr=0;
	if(dwnld_rqst.data_loss.total_lost_segment_count < MAX_PCKT_LOSS_DETECTION_CNTR)
	{
		while(u16cntr < MAX_PCKT_LOSS_DETECTION_CNTR)
		{
			if(0 == dwnld_rqst.data_loss.lost_packet_list[u16cntr])
			{
				ret_sts = 1;
				dwnld_rqst.data_loss.lost_packet_list[u16cntr] = seq_num;
				dwnld_rqst.data_loss.total_lost_segment_count++;
				/* set resend request flag , it will be cleared once ID communicated with sender */
				dwnld_rqst.data_loss.resend_rqst_flag = dwnld_rqst.data_loss.resend_rqst_flag | (1 << u16cntr);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
	            DEBUG_CPRINT(("saved lost id %lu at pos %d\n",seq_num,u16cntr));
#pragma GCC diagnostic pop
				break;
			}
			u16cntr++;
		}
	}
	else
	{
		/* maximum sequence ID carrier buffer is full; request sender side head position change request. */
		ret_sts = 0;
	}
	return ret_sts;
}

/**
 * @brief This function will identify missing sequence during transmission
 *
 * a. missed sequence ID
 * b. store missed ID in tracker buffer
 * c. trigger sequence ID notification to sender when missing id count is reached limit
 * d. trigger head change request to sender if tracker buffer not able to hold new lost sequence
 */

static uint8_t packet_loss_detection_recovery(uint32_t current_seq_num)
{
	uint16_t loss_count;
	uint16_t u16cnt;
	uint8_t ret_sts =1;
	static uint16_t data_loss_notify = 0;
	uint32_t copy_saved_seg;


	copy_saved_seg = dwnld_rqst.pckt_seq_head;

     if(current_seq_num > dwnld_rqst.pckt_seq_head)
     {
         if(current_seq_num == dwnld_rqst.pckt_seq_head+1)
         {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
             DEBUG_CPRINT(("FNL%lu\n",current_seq_num));
     		log_id_with_one_arg(LOG_INFO_L3, LOG_FTM, FNL_u32_,current_seq_num);
#pragma GCC diagnostic pop
             dwnld_rqst.pckt_seq_head = current_seq_num;
         }
         else
         {

        	 if(dwnld_rqst.seq_loss_detect_enable == SEQ_LOSS_DETECTION_OFF)
        	 {
                 dwnld_rqst.pckt_seq_head = current_seq_num;
                 ft_receiver_ping_rest();
				 return 1;
        	 }


             loss_count = (current_seq_num-1) - dwnld_rqst.pckt_seq_head;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
             DEBUG_CPRINT(("loss %lu to %lu t=%x \n",dwnld_rqst.pckt_seq_head+1,current_seq_num-1,loss_count));
#pragma GCC diagnostic pop
             ret_sts = get_missing_sequnec_buffer_status();
             if(ret_sts == 1)
             {
				 for(u16cnt=0; u16cnt < loss_count; u16cnt++)
				 {
					 /* save lost in sequence ID tracker buffer */
					 ret_sts = save_lost_seq_id(dwnld_rqst.pckt_seq_head+1+u16cnt);
					 if((ret_sts != 1) && (dwnld_rqst.data_loss.head_change_request !=1))
					 {
						 /* stop storing sequnece ID if seq tracker buf is full, then notify loss status to sender */
						 DEBUG_CPRINT(("high loss \n"));
                         log_id(LOG_INFO_L1, LOG_FTM, FTM_HIGH_LOSS);
						 //prepare_packet_loss_notify_frame(&dwnld_rqst, ftsr_head_change);
						 dwnld_rqst.data_loss.head_change_request = 1;
						 data_loss_notify = FTSR_SEQ_LOSS_REPORT_LIMIT;
						 ret_sts = 0;
						 break;
					 }
					 else if(ret_sts == 1)
					 {
						 copy_saved_seg = dwnld_rqst.pckt_seq_head+1+u16cnt;
					 }
					 else
					 {

					 }
					 data_loss_notify++;
				 }
			}
			else
			{
				 ret_sts = 0;
				 dwnld_rqst.data_loss.head_change_request = 1;
			}
             if(dwnld_rqst.data_loss.head_change_request == 1)
             {
            	 dwnld_rqst.pckt_seq_head = copy_saved_seg;
				 ft_fsm_change_state(FT_Report_Head_Change);
             }
             else
             {
            	 dwnld_rqst.pckt_seq_head = current_seq_num;
             }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
             DEBUG_CPRINT(("loss count %d head:%lu\n",data_loss_notify, dwnld_rqst.pckt_seq_head));
#pragma GCC diagnostic pop
         }
         if(data_loss_notify >= FTSR_SEQ_LOSS_REPORT_LIMIT)
         {
        	 data_loss_notify = 0;
        	 /* send data loss info to sender */
        	 //prepare_packet_loss_notify_frame(&dwnld_rqst, ftsr_missing_pckts);
        	 ftsr_set_resend_flag(&dwnld_rqst);
             DEBUG_CPRINT(("Report_Sequence_loss \n"));
        	 ft_fsm_change_state(FT_Report_Sequence_loss);
         }
     }
     else
     {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
         DEBUG_CPRINT(("rSnd%lu\n",current_seq_num));
#pragma GCC diagnostic pop
         ret_sts = check_lost_packet_is_recived(current_seq_num);
         if(ret_sts != 1)
         {
             DEBUG_CPRINT(("Duplicate ID rxd...\n"));
         }
     }
     return ret_sts;
}








/**
 * @brief This function is used to verify the CRC for downloaded file
 *
 */
static void check_integrity(void)
{
	uint32_t file_checksum;

#ifndef FT_OBC
	ft_file_close(dwnld_rqst.fptr,&dwnld_rqst.file_opened, dwnld_rqst.tx_mode);
	DEBUG_CPRINT(("\nFile %s is downloaded \n", dwnld_rqst.file_name));
	file_checksum = file_integrity_check((char*)dwnld_rqst.file_name);
#else
	file_checksum = calc_checksum_bulkdata(dwnld_rqst.mptr,dwnld_rqst.size);
#endif
	if (file_checksum == dwnld_rqst.checksum)
	{
		DEBUG_CPRINT(("File integrity success \n"));
		log_id(LOG_INFO_L1, LOG_FTM, FILE_INTEGRITY_SUCCESS);
		send_ftsr(ftsr_download_complete,0);
		dwnld_rqst.dwnld_cmp_sts = ftsr_download_complete;
		prepare_sts_and_notify_receiver_app(transfer_download_success);
	}
	else
	{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
		DEBUG_CPRINT(("File size rxd: %lu\n",dwnld_rqst.size));
		DEBUG_CPRINT(("File integrity failed CRC:0x%luX against 0x%luX\n",file_checksum,dwnld_rqst.checksum));
#pragma GCC diagnostic pop
		log_id(LOG_WARNING, LOG_FTM, INTEGRITY_FAIL);
		send_ftsr(ftsr_crc_fail,0);
		dwnld_rqst.dwnld_cmp_sts = ftsr_crc_fail;
		prepare_sts_and_notify_receiver_app(crc_error);
	}
	set_file_transfer_state(ft_process_na);


//	os_free(dwnld_rqst.file_name);

}


/**
 * @brief This function is used to verify the CRC for downloaded bulkdata
 *
 */
static void check_integrity_bulkdata(void)
{
	uint32_t data_checksum;
//	uint16_t u16cnt;



	DEBUG_CPRINT(("\nBulk data is downloaded \n"));

//	for (u16cnt = 0 ; u16cnt < dwnld_rqst.size; u16cnt++)
//	{
//		DEBUG_CPRINT(("%d,",dwnld_rqst.mptr[u16cnt]));
//	}
//	DEBUG_CPRINT(("\n"));



	data_checksum = calc_checksum_bulkdata(dwnld_rqst.mptr,dwnld_rqst.size);
	if (data_checksum == dwnld_rqst.checksum)
	{
		DEBUG_CPRINT(("Bulkdata integrity success \n"));
		send_ftsr(ftsr_download_complete,0);
		prepare_sts_and_notify_receiver_app(transfer_download_success);
	}
	else
	{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
		DEBUG_CPRINT(("Bulkdata size rxd: %lu\n",dwnld_rqst.size));
		DEBUG_CPRINT(("File integrity failed CRC:0x%luX against 0x%luX\n",data_checksum,dwnld_rqst.checksum));
#pragma GCC diagnostic pop
		send_ftsr(ftsr_crc_fail,0);
		prepare_sts_and_notify_receiver_app(crc_error);
	}
	set_file_transfer_state(ft_process_na);




}


/**
 * @brief This function is used to decode the FTDS frame
 *
 *
 */
static uint8_t ftds_decode()
{
	uint16_t u16cnt;
	uint8_t * loc_ptr;
	s_file_tx_data_pckt * ftds_loc;
	ftds_loc = (s_file_tx_data_pckt*) dwnld_rqst.ftds_payload_ptr;
	uint8_t frame_index_cntr;
	uint32_t file_position_calc;
	uint8_t ret_sts;
	uint8_t seq_type;
	uint8_t ses_id ;
#ifdef FT_OBC
	uint64_t obc_offset_addr;
#endif

    ft_debug_save_prev_frame_info(tctm_id_inform_ftds,0);
	e_ft_current_process ft_state;
	ft_state =  get_file_transfer_state();
	if(ft_state != ft_download)
	{
		/* it helps to close FTM if sender stopped transmission
		 * or sequence loss has detected, it doesn t required to receive FTDS
		 * if sequence loss has detected
		 */
		DEBUG_CPRINT(("FTM - No Service, FTDS discard\n"));
		log_id(LOG_INFO_L1, LOG_FTM, FTM_NO_SERVICE_cm_FTDS_DISCARD);
		ftds_busy = 0;
		return 0;
	}


	dwnld_rqst.pckt_seq_num = 0;
	ft_fsm_change_state(FT_Idle_State);

	ses_id = ft_rx_session_id_read();
	if(ftds_loc->ctrl.bit.file_tx_session_id != ses_id )
	{
		// send session id mismatch response to gs
		DEBUG_CPRINT(("Session ID mismatch, the sequence is discared RCV %d != STR %d\n", ftds_loc->ctrl.bit.file_tx_session_id,ses_id));
		send_ftsr(ftsr_invalid_session_id, 0);
		ftds_busy = 0;
		return 0;
	}

	frame_index_cntr = sizeof(s_file_tx_data_pckt);



	/* read sequence number from FTDS frame*/
	loc_ptr = (uint8_t*) &dwnld_rqst.pckt_seq_num;
	for (u16cnt = 0; u16cnt <= ftds_loc->ctrl.bit.seq_num_field_size_sel; u16cnt++)
	{
		*loc_ptr = dwnld_rqst.ftds_payload_ptr[frame_index_cntr];
		loc_ptr++;
		frame_index_cntr++;
	}

#ifdef FT_DEBUG
	DEBUG_CPRINT(("SEQ:%u ", dwnld_rqst.pckt_seq_num));
#endif

	seq_type = ftds_loc->ctrl.bit.seg_type;
        if (seq_type == 2)
	{
		if(dwnld_rqst.pckt_seq_num == 1)
		{
			DEBUG_CPRINT(("This File has only Single Sequence \n"));
		}
		else
		{
			DEBUG_CPRINT(("Last Sequence \n"));
		}
	}


	dwnld_rqst.packet_data_size = 0;

	/* read payload data length from FTDS frame*/
	loc_ptr = (uint8_t*) &dwnld_rqst.packet_data_size;
	for (u16cnt = 0; u16cnt <= ftds_loc->ctrl.bit.data_length_field_size_sel; u16cnt++)
	{
		*loc_ptr = dwnld_rqst.ftds_payload_ptr[frame_index_cntr];
		loc_ptr++;
		frame_index_cntr++;
	}

#ifdef FT_DEBUG
	DEBUG_CPRINT(("DL:%u ", dwnld_rqst.packet_data_size));
#endif
	ret_sts = packet_loss_detection_recovery(dwnld_rqst.pckt_seq_num);

	/* save data into file */
	if(ret_sts != 0)
	{

		ft_check_suspend_status();
		file_position_calc = (dwnld_rqst.pckt_seq_num - 1) * dwnld_rqst.seq_payload_len;
#ifdef FT_DEBUG
		DEBUG_CPRINT(("FP:%u\n", file_position_calc));
#endif
		if(dwnld_rqst.tx_mode == 0)
		{
#ifndef FT_OBC
			fseek(dwnld_rqst.fptr, file_position_calc, SEEK_SET);
			fwrite(&dwnld_rqst.ftds_payload_ptr[frame_index_cntr], dwnld_rqst.packet_data_size, 1, dwnld_rqst.fptr);
#else
			obc_offset_addr = dwnld_rqst.obc_mem_addr + file_position_calc;
			ret_sts = ft_ram_write_fp(obc_offset_addr,&dwnld_rqst.ftds_payload_ptr[frame_index_cntr], dwnld_rqst.packet_data_size);
			if(ret_sts != 1)
			{
				DEBUG_CPRINT(("Warning !!! FTM driver Busy\n"));
			}
#endif
		}
		else
		{
			for (u16cnt = 0; u16cnt < dwnld_rqst.packet_data_size; u16cnt++)
			{
				dwnld_rqst.mptr[file_position_calc + u16cnt] = dwnld_rqst.ftds_payload_ptr[frame_index_cntr+u16cnt];
			}
		}


#ifdef FT_DEBUG
		DEBUG_CPRINT(("Write success \n"));
#endif

		if ((seq_type == 2) || (dwnld_rqst.download_pending_by_loss == 1))
		{
			/*last packet */
			DEBUG_CPRINT(("Last packet or Pending packets receive\n"));
			ret_sts = get_missing_sequnec_status();
			if(ret_sts != 0)
			{
				DEBUG_CPRINT(("Integrity check\n"));
				if(dwnld_rqst.tx_mode == 0)
				{
#ifdef FT_OBC_FLASH_WRITE_DELAY
					ret_sts = 0;
					DEBUG_CPRINT(("FdrvBsy\n"));
					while(ret_sts != 1)
					{
						ret_sts = ft_payload_obc_busy_sts();
					}
					os_delay(1000);
#endif
					check_integrity();
				}
				else
				{
					check_integrity_bulkdata();
				}
				dwnld_rqst.receive_busy =0;
				ft_stop_timer();
				ft_service_disable();
			}
			else if(ftds_loc->ctrl.bit.seg_type == 2)
			{
				DEBUG_CPRINT(("Completion pending by seq loss..\n"));
                log_id(LOG_WARNING, LOG_FTM, FTT_COMPLETION_PENDING_BY_SEQ_LOSS);
				ftsr_set_resend_flag(&dwnld_rqst);
				ft_fsm_change_state(FT_Report_Sequence_loss);
				dwnld_rqst.download_pending_by_loss = 1;
			}

		}
		else
		{
				// do nothing
		}

		if(dwnld_rqst.data_loss.total_lost_segment_count != 0)
		{
			/* it will request lost sequence for every PERIODIC_RESEND_RQST_CNT_LIMIT */
			dwnld_rqst.data_loss.re_request_initiate_timer++;
			if(dwnld_rqst.data_loss.re_request_initiate_timer > PERIODIC_RESEND_RQST_CNT_LIMIT)
			{
				dwnld_rqst.data_loss.re_request_initiate_timer=0;
				ftsr_set_resend_flag(&dwnld_rqst);
				ft_fsm_change_state(FT_Report_Sequence_loss);
			}
		}
		else
		{
			dwnld_rqst.data_loss.re_request_initiate_timer=0;
		}
	}
	ft_reset_timer();
	ftds_busy = 0;
	ftfci_clear_continous_ping_cntr();
#if 0
	if(ftds_ack_en == 1)
	{
		send_ftsr(ftsr_ping,0);
	}
#endif
	return ret_sts;
}

/**
 * @brief This function is used to initialize all the params related to FTDS decode
 *
 *
 */
void ft_seq_loss_handle_init(void)
{
	uint16_t u16cnt;
	dwnld_rqst.data_loss.total_lost_segment_count=0;
	dwnld_rqst.data_loss.resend_rqst_flag = 0;
	dwnld_rqst.pckt_seq_head =0;
	dwnld_rqst.download_pending_by_loss =0;

	for(u16cnt=0; u16cnt < MAX_PCKT_LOSS_DETECTION_CNTR ; u16cnt++)
	{
		dwnld_rqst.data_loss.lost_packet_list[u16cnt] =0;;
	}
}


/******************************************************************************/
/*                      Definition of exported functions                      */
/******************************************************************************/


//* @brief This function return the head sequence ID for receiver side

uint32_t ft_get_receiver_head_seq_id(void)
{
	return dwnld_rqst.pckt_seq_head;
}



//* @brief This function is used to check the sequence loss count status

uint8_t ft_get_seq_loss_sts(void)
{
	uint8_t ret =0;
	if(dwnld_rqst.data_loss.total_lost_segment_count != 0)
	{
		ret =1;
	}
	return ret;
}


/**
 * @brief This API receives the FTDS frame data from TC-TM then decode FTDS frame
 *
 *
 */
void ft_receive_ftds(uint8_t *ftds_data, uint16_t ftds_data_length)
{

	if(ftds_busy != 1)
	{
		ftds_busy = 1;
		dwnld_rqst.ftds_payload_ptr = ftds_data;
		ftds_decode();

	}
	else
	{
		DEBUG_CPRINT(("\r\nFTDS-bsy"));
		log_id(LOG_INFO_L1, LOG_FTM, FTDS_BSY);
	}
}
