/**
 * @file exo_io_al_crc_common.c
 *
 * @brief This file contains type defines of return status
 *
 * @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.
*/

#include "stdio.h"
#include "string.h"
#include "exo_io_al_crc_common.h"
#include "exo_hal_io_al_common.h"
#include "exo_ahw_al_common.h"
#include "exo_common.h"
#ifndef LINUX_TEMP_PORT
#include "exo_io_al_stm32f7xx_crc.h"
#else
#include "exo_io_al_linux_crc.h"
#endif
#include "exo_hal_common.h"
#include "exo_osal_common.h"
#include "exo_osal_rsrc_sync.h"



#define IO_CRC_MAX_SEM_TIMEOUT    (2*1000)


extern void *intf_inst_hdle_ptr[MAX_IO_INST_ID];
exo_io_crc_mode  crc_mode;

/**
 * @brief This function applies crc-32 algorithm for given data values
 *
 * @param[in]  Crc  : calculated value of previous iteration.
 * @param[in] Data   : input data value for current iteration to calculate crc
 *
 * @return calculated crc value
 * @retval crc value in hex
 */
static uint32_t ft_Crc32(uint32_t Crc, uint32_t Data);
/**
 * @brief This API will evaluate crc value for given input data buffer with length
 *
 * @param[in] data_buf  : Pointer to input data buffer.
 * @param[in] length    : input buffer size
 *
 * @return crc value for input buffer
 * @retval non zero value is valid calculated crc, value 0 indicates error
 */
static uint32_t software_crc_calculator(uint8_t *data_buf, uint32_t length, uint32_t *calc_CRC);
/*
 * @brief This API is use to calculate CRC.
 *
 * It supports both hardware CRC and software CRC
 *
 * @param[in]ioal_hcrc                - CRC handler
 * @param[in] crc_mode                - use to select hardware CRC (EXO_HARDWARE_CRC_MODE) or software CRC (EXO_SOFTWARE_CRC_MODE)
 * @param[in] input_data_buffer       - input buffer
 * @param[in]  input_data_buffer_size - input buffer size
 * @param[out] return : it returns calculated CRC value in uint32_t type.
 *
 */
static uint32_t io_hal_crc_calculate(ioal_crc_hdle *ioal_hcrc, exo_io_crc_mode crc_mode, uint8_t *input_data_buffer, uint32_t input_data_buffer_size);


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

/**
 * @brief This function applies crc-32 algorithm for given data values
 *
 */
static uint32_t ft_Crc32(uint32_t Crc, uint32_t Data)
{
	uint8_t index;

	Crc = Crc ^ Data;
	for (index = 0; index < 32; index++)
	{
		if (Crc & 0x80000000)
		{
			Crc = (Crc << 1) ^ CRC_POLYNOMIAL_FACTOR;	// Polynomial used in STM32
		}
		else
		{
			Crc = (Crc << 1);
		}
	}

	return (Crc);
}


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

/**
 * @brief This API will evaluate crc value for given input data buffer with length
 */
static uint32_t software_crc_calculator(uint8_t *data_buf, uint32_t length, uint32_t *calc_CRC)
{
	uint32_t read_buffer = 0xffffffff;
	uint32_t buf_inc;

	if(length < 4)
	{
		return 1;
	}
	// checking if the file exist or not
	if (data_buf == NULL)
	{
		return 1;
	}
	buf_inc = 0;
	*calc_CRC = 0xFFFFFFFF;
	while (length > 3)
	{
		memcpy(&read_buffer,&data_buf[buf_inc],4);
		*calc_CRC = ft_Crc32(*calc_CRC, read_buffer);
		length -= 4;
		buf_inc +=4;
	}

	return 0;
}

/*
 *  @brief This API is use to calculate CRC.
 *  It supports both hardware CRC and software CRC
 */
static uint32_t io_hal_crc_calculate(ioal_crc_hdle *ioal_hcrc, exo_io_crc_mode crc_mode, uint8_t *input_data_buffer, uint32_t input_data_buffer_size)
{
   uint32_t crc;
   hal_ret_sts sts = HAL_IO_INIT_INVALID_INSTANCE;

#ifdef LINUX_TEMP_PORT
   /*
    * Hardware CRC will not work in Linux.
    */
   crc_mode = EXO_SOFTWARE_CRC_MODE;
#endif

   if(crc_mode == EXO_HARDWARE_CRC_MODE)
   {
	   os_sem_take(ioal_hcrc->semaphore_hdle, IO_CRC_MAX_SEM_TIMEOUT);
	   sts = io_hal_common_crc_cal(ioal_hcrc, input_data_buffer, input_data_buffer_size, &crc);
	   os_sem_give(ioal_hcrc->semaphore_hdle);
   }
   else if (crc_mode == EXO_SOFTWARE_CRC_MODE )
   {
	   /*
	    * software crc will only work if length is 4 byte aligned
	    */
	   if(input_data_buffer_size % 4 != 0)
	   {
		   // input length is not 4 byte aligned value
	   }
	   else
	   {
		   sts = software_crc_calculator(input_data_buffer,input_data_buffer_size, &crc);
	   }
   }
   else
   {
		/*
		 * invalid mode
		 */
   }

   if(sts != HAL_SCS)
   {
	   /*
	    * crc caluclate error
	    */
	  crc = 0;
   }

   return crc;
}

/*
 *  @brief This API is use to Initialization hardware CRC.
 */
hal_ret_sts io_hal_crc_init(ioal_crc_hdle *ioal_hcrc, io_inst_id inst_id)
{
	hal_ret_sts sts = HAL_IO_INIT_INVALID_INSTANCE;
    switch(inst_id)
    {
    case IOAL_INST_CRC:
    	if(HAL_SCS == io_hal_common_crc_init(ioal_hcrc))
    	{
      	    os_sem_create_bin(&ioal_hcrc->semaphore_hdle,"HAL_CRC_SEM");
   			intf_inst_hdle_ptr[IOAL_INST_CRC] = ioal_hcrc;
		}
    	else
    	{
    		sts = HAL_IO_INIT_ERR;
    	}
    	break;
        default:
            sts = HAL_IO_INIT_INVALID_INSTANCE;
            break;
    }
      return sts;
}

/*
 *  @brief This API is use to call the hardware CRC.
 *  It supports both hardware CRC and software CRC
 */
uint32_t exo_io_hal_crc_calculate(exo_io_crc_mode crc_mode, uint8_t *input_data_buffer, uint32_t input_data_buffer_size)
{
	ioal_crc_hdle* hcrc = intf_inst_hdle_ptr[IOAL_INST_CRC];
	return io_hal_crc_calculate(hcrc, crc_mode, input_data_buffer, input_data_buffer_size);
}
