#include <stdint.h>
#include <string.h>
#include <sFWu.h>
#include <tml.h>
#include <tool.h>

#define DL_CMD				0x00
#define DL_RESET        	0xF0
#define DL_GETVERSION		0xF1	

#define MAX_FRAME_SIZE		1000
#define CHUNK_SIZE			(MAX_FRAME_SIZE - 4)

uint16_t BytesRead;
unsigned char pResponseBuffer[512];

static uint16_t sFWu_CalcCrc16(uint8_t* p, uint32_t dwLength)
{
	uint32_t i;
	uint16_t crc_new;
	uint16_t crc = 0xffffU;

	for (i = 0; i < dwLength; i++)
	{
		crc_new = (uint8_t)(crc >> 8) | (crc << 8);
		crc_new ^= p[i];
		crc_new ^= (uint8_t)(crc_new & 0xff) >> 4;
		crc_new ^= crc_new << 12;
		crc_new ^= (crc_new & 0xff) << 5;
		crc = crc_new;
	}
	return crc;
}

static void sFWu_Transceive(uint8_t *pTBuff, uint16_t TbuffLen, uint8_t *pRBuff, uint16_t RBuffSize, uint16_t *pBytesread)
{
	uint16_t BytesWritten = 0;
	tml_Send(pTBuff, TbuffLen, &BytesWritten);
    PRINT_BUF(">> ", pTBuff, BytesWritten);
	tml_Receive(pRBuff, RBuffSize, pBytesread, 5000);
    PRINT_BUF("<< ", pRBuff, *pBytesread);
}

uint8_t sFWu_Reset(void)
{
	uint16_t crc16 = 0x00;
	uint8_t Cmd[8] = { 0 };

	Cmd[0] = DL_CMD;
	Cmd[1] = 0x04;
	Cmd[2] = DL_RESET;
	crc16 = sFWu_CalcCrc16(Cmd, 0x06);
	Cmd[6] = (uint8_t)((crc16 & 0xff00) >> 0x08);
	Cmd[7] = (uint8_t)crc16;

	sFWu_Transceive(Cmd, sizeof(Cmd), pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
	if ((BytesRead > 0) || (pResponseBuffer[2] != 0)) return pResponseBuffer[2];

	return 0;
}

uint8_t sFWu_GetVersion(sFWu_version_t *pVer)
{
	uint16_t crc16 = 0x00;
	uint8_t Cmd[8] = { 0 };

	Cmd[0] = DL_CMD;
	Cmd[1] = 0x04;
	Cmd[2] = DL_GETVERSION;
	crc16 = sFWu_CalcCrc16(Cmd, 0x06);
	Cmd[6] = (uint8_t)((crc16 & 0xff00) >> 0x08);
	Cmd[7] = (uint8_t)crc16;

	sFWu_Transceive(Cmd, sizeof(Cmd), pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
	if ((BytesRead < 12) || (pResponseBuffer[2] != 0)) return pResponseBuffer[2];

	pVer->ROM = pResponseBuffer[4];
	pVer->MAJ = pResponseBuffer[11];
	pVer->MIN = pResponseBuffer[10];

	return 0;
}

uint8_t sFWu_Download(uint8_t *pFile, uint16_t fileSize)
{
	uint16_t index = 0;
	uint16_t frame_size;
	uint8_t pTempBuffer[MAX_FRAME_SIZE];
	uint16_t crc16 = 0x00;

	while (index < fileSize)
	{
		frame_size = (pFile[index] << 8) + pFile[index + 1];

		/* Is chunking required ? */
		if(frame_size > CHUNK_SIZE)
		{
			/* first chunck */
			index += 2; /* Skip frame size */
			pTempBuffer[0] = (uint8_t) (((MAX_FRAME_SIZE-4) & 0xff00) >> 0x08) | 0x04;
			pTempBuffer[1] = (uint8_t) (MAX_FRAME_SIZE-4);
			memcpy(pTempBuffer + 2, pFile + index, CHUNK_SIZE);
			crc16 = sFWu_CalcCrc16(pTempBuffer, CHUNK_SIZE + 2);
			pTempBuffer[CHUNK_SIZE + 2] = (uint8_t)((crc16 & 0xff00) >> 0x08);
			pTempBuffer[CHUNK_SIZE + 3] = (uint8_t)crc16;
			sFWu_Transceive(pTempBuffer, MAX_FRAME_SIZE, pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
			if ((BytesRead != 8) || (pResponseBuffer[2] != 0x2D))
			{
				return pResponseBuffer[2];
			}
			index += CHUNK_SIZE;
			frame_size -= CHUNK_SIZE;
			
			/* intermediate chunks */
			while (frame_size > CHUNK_SIZE)
			{
				pTempBuffer[0] = (uint8_t) (((MAX_FRAME_SIZE-4) & 0xff00) >> 0x08) | 0x04;
				pTempBuffer[1] = (uint8_t) (MAX_FRAME_SIZE-4);
				memcpy(pTempBuffer + 2, pFile + index, CHUNK_SIZE);
				crc16 = sFWu_CalcCrc16(pTempBuffer, CHUNK_SIZE + 2);
				pTempBuffer[CHUNK_SIZE + 2] = (uint8_t)((crc16 & 0xff00) >> 0x08);
				pTempBuffer[CHUNK_SIZE + 3] = (uint8_t)crc16;
				sFWu_Transceive(pTempBuffer, MAX_FRAME_SIZE, pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
				if ((BytesRead != 8) || (pResponseBuffer[2] != 0x2E))
				{
					return pResponseBuffer[2];
				}
				index += CHUNK_SIZE;
				frame_size -= CHUNK_SIZE;
			}

			/* last chunk */
			pTempBuffer[0] = (uint8_t) ((frame_size & 0xff00) >> 0x08);
			pTempBuffer[1] = (uint8_t) frame_size;
			memcpy(pTempBuffer + 2, pFile + index, frame_size);
			crc16 = sFWu_CalcCrc16(pTempBuffer, frame_size + 2);
			pTempBuffer[frame_size + 2] = (uint8_t)((crc16 & 0xff00) >> 0x08);
			pTempBuffer[frame_size + 3] = (uint8_t)crc16;
			sFWu_Transceive(pTempBuffer, frame_size + 4, pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
			if ((BytesRead != 8) || (pResponseBuffer[2] != 0))
			{
				return pResponseBuffer[2];
			}
			index += frame_size;
		}
		else
		{
			memcpy(pTempBuffer, pFile + index, frame_size + 2);
			crc16 = sFWu_CalcCrc16(pTempBuffer, frame_size + 2);
			pTempBuffer[frame_size + 2] = (uint8_t)((crc16 & 0xff00) >> 0x08);
			pTempBuffer[frame_size + 3] = (uint8_t)crc16;
			sFWu_Transceive(pTempBuffer, frame_size + 4, pResponseBuffer, sizeof(pResponseBuffer), &BytesRead);
			index += frame_size + 2;
			if ((BytesRead < 6) || (pResponseBuffer[2] != 0))
			{
					return pResponseBuffer[2];
			}
		}
	}
	return 0;
}
