/*---------------------------------------------------------------------------
        Project:          Ip400Spi

        File Name:        spitask.c

        Author:           VE6VH

        Creation Date:    Mar. 6, 2025

        Description:      {Definition here...)

                          This program 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 2 of the License, or
                          (at your option) any later version, provided this copyright notice
                          is included.

                          Copyright (c) 2024-25 Alberta Digital Radio Communications Society

---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include "types.h"
#include "spidefs.h"
#include "logger.h"
#include "dataq.h"

// locals
int spiDevFD;				// spi device file descriptor
int spiDevNum;				// device number

// transmittter states
enum {
	SPITXIDLE=0,				// idle
	SPITXFRAG					// next fragment
};

// receiver states
enum {
	SPIRXIDLE=0,				// idle
	SPIRXFRAG					// next fragment
};

uint8_t	SPITxState;				// transmitter state
uint8_t	SPIRxState;				// receiver state

// SPI transmit frame queue
FRAME_QUEUE	SPITxQueue;

// frame buffers
static SPI_BUFFER spiTxBuffer;
static SPI_BUFFER spiRxBuffer;

static union	{
	struct {
		struct spi_hdr_t	hdr;		// header
		uint8_t	buffer[PAYLOAD_MAX];
	} spiData;
	uint8_t	rawData[PAYLOAD_MAX + sizeof(struct spi_hdr_t)];
} rxFrameBuffer;

// frame validator
BOOL isIP400Frame(uint8_t *eye);

BOOL EnqueSPIFrame(void *spiFrame)
{
	SPI_DATA_FRAME *qFrame, *SrcFrame = (SPI_DATA_FRAME *)spiFrame;
	uint8_t *frameBuffer;

	// allocate an IP400 frame
	if((qFrame=malloc(sizeof(SPI_DATA_FRAME)))== NULL)
		return FALSE;
	memcpy(qFrame, SrcFrame, sizeof(SPI_DATA_FRAME));

	// alloc the data portion of the frame
	if((frameBuffer=malloc(SrcFrame->length)) == NULL)	{
		return FALSE;
	}

	memcpy(frameBuffer, (uint8_t *)SrcFrame->buffer, SrcFrame->length);
	qFrame->buffer = frameBuffer;
	qFrame->length = SrcFrame->length;

	if(!enqueFrame(&SPITxQueue, qFrame))
		return FALSE;

	return TRUE;
}

/*
 * Initialize the SPI task
 */
BOOL spiTaskInit(int spiDev)
{
	// open the SPI device
	if((spiDevFD = spi_open(spiDev)) == -1)	{
		logger(LOG_ERROR, "Unable to open SPI device %d\n", spiDev);
		return FALSE;
	}

	spiTxBuffer.spiData.hdr.status = NO_FRAME;
	spiRxBuffer.spiData.hdr.status = NO_FRAME;

	// init the tx queue
	SPITxQueue.q_forw = &SPITxQueue;
	SPITxQueue.q_back = &SPITxQueue;

	// init vars
	spiDevNum = spiDev;
	SPITxState = SPITXIDLE;
	SPIRxState = SPIRXIDLE;

	rxFrameBuffer.spiData.hdr.eye[0] = 'I';
	rxFrameBuffer.spiData.hdr.eye[1] = 'P';
	rxFrameBuffer.spiData.hdr.eye[2] = (400 >> 8) & 0xff;
	rxFrameBuffer.spiData.hdr.eye[3] = (400 & 0xff);

	return TRUE;
}

/*
 * Run the SPI functions.
 * Poll receiver for an inbound frame and read it. Add it to the frame queue.
 * Poll transmit queue for an outbound frame and send it.
 */
void spiTask(void)
{
	static SPI_DATA_FRAME *txFrame;
	static uint16_t txSegLength;
	static uint8_t *prxData;
	static uint16_t rxSegLen;

	memset(spiRxBuffer.rawData, 0, SPI_RAW_LEN);

	// do an exchange with the STM32
	int nxferred = spi_fdtransfer(spiDevNum, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN);
	if(nxferred == -1)	{
		logger(LOG_ERROR, "SPI transmit error %d: %s\n", nxferred, geterrno(nxferred));
	}

	/*
	 * Inbound frame. Reassemble fragments if needed...
	 */
	// we have to declare these here, as teh !#)$*& compiler will not allow declarations in statements
	uint8_t rstat = NO_FRAME;
	uint8_t fragStat;
	uint16_t offset;
	uint16_t frameLen=0, prevLen=0;

	switch(SPIRxState)	{

	case SPIRXIDLE:
		rstat = spiRxBuffer.spiData.hdr.status;

		// next validate the frame type
		if((rstat == NO_FRAME) || (rstat >= N_FRAGS))
			break;


		// copy the header fields
		// validate the eye of the frame
		if(!isIP400Frame(spiRxBuffer.spiData.hdr.eye))	{
			logger(LOG_DEBUG, "Bad frame: Stat %d len %d, failed eye test %02X:%02X:%02X:%02X\n",rstat,nxferred,
			spiRxBuffer.spiData.hdr.eye[0],
			spiRxBuffer.spiData.hdr.eye[1],
			spiRxBuffer.spiData.hdr.eye[2],
			spiRxBuffer.spiData.hdr.eye[3]);
			break;
		}

		// copy the header fields
		memcpy(&rxFrameBuffer.spiData.hdr, &spiRxBuffer.spiData.hdr, sizeof(struct spi_hdr_t));

		// copy data fields
		prxData =  rxFrameBuffer.spiData.buffer;
		rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo;
		memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen);

		if(rstat != SINGLE_FRAME)	{
			SPIRxState = SPIRXFRAG;
			logger(LOG_DEBUG, "Fragmented frame received\n");
		} else {
			rxFrameBuffer.spiData.hdr.status = SINGLE_FRAME;
			rxFrameBuffer.spiData.hdr.length_hi = (rxSegLen >> 8);
			rxFrameBuffer.spiData.hdr.length_lo = (rxSegLen & 0xFF);
			rxFrameBuffer.spiData.hdr.offset_hi = rxFrameBuffer.spiData.hdr.offset_lo = 0;
			send_udp_packet(rxFrameBuffer.rawData, rxSegLen+sizeof(struct spi_hdr_t));
			SPIRxState = SPIRXIDLE;
			spiRxBuffer.spiData.hdr.status = N_FRAGS;	// set an invalid frame type
		}
		break;

	case SPIRXFRAG:
		fragStat = spiRxBuffer.spiData.hdr.status;
		offset = ((uint16_t)spiTxBuffer.spiData.hdr.offset_hi << 8) + (uint16_t)spiTxBuffer.spiData.hdr.offset_lo;
		prxData = rxFrameBuffer.rawData + offset;
		rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo;
		memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen);

		if(fragStat == LAST_FRAGMENT)	{
			frameLen = offset + rxSegLen;
			spiRxBuffer.spiData.hdr.length_hi = (frameLen >> 8);
			spiRxBuffer.spiData.hdr.length_lo = (frameLen & 0xFF);
			// process frame for tx here...
			SPIRxState = SPIRXIDLE;					// placeholder
		}
		break;
	}

	/*
	 * process an outbound frame
	 * Fragment it if longer that 500 byte, s->length);s
	 */
	switch(SPITxState)	{

	case SPITXIDLE:
		if((txFrame=dequeFrame(&SPITxQueue)) == NULL)	{
			spiTxBuffer.spiData.hdr.status = NO_FRAME;
			break;
		}
		txSegLength = txFrame->length;
		spiTxBuffer.spiData.hdr.status = SINGLE_FRAME;

		// fragment the frame if needed
		if(txFrame->length > SPI_BUFFER_LEN)	{
			txSegLength = SPI_BUFFER_LEN;
			spiTxBuffer.spiData.hdr.status = FRAME_FRAGMENT;
			SPITxState = SPITXFRAG;
		}

		// send the frame and/or fragment
		memcpy(spiTxBuffer.rawData, txFrame->buffer, txSegLength);
		spiTxBuffer.spiData.hdr.offset_hi = spiTxBuffer.spiData.hdr.offset_lo = 0;

		// release memory if only a single frame
		if(spiTxBuffer.spiData.hdr.status == SINGLE_FRAME)
		{
			free(txFrame->buffer);
			free(txFrame);
		}
		break;

	case SPITXFRAG:
		offset = ((uint16_t)(spiTxBuffer.spiData.hdr.offset_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.offset_lo;
		prevLen = ((uint16_t)(spiTxBuffer.spiData.hdr.length_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.length_lo;
		offset += prevLen;
		txFrame->length -= prevLen;
		txSegLength = txFrame->length;
		if(txFrame->length > SPI_BUFFER_LEN)	{
			spiTxBuffer.spiData.hdr.status = FRAME_FRAGMENT;
			txSegLength = SPI_BUFFER_LEN;
		} else {
			spiTxBuffer.spiData.hdr.status = LAST_FRAGMENT;
			SPITxState = SPITXIDLE;
		}

		// send the next fragment
		void *fragAddr = txFrame->buffer + offset;
		memcpy(spiTxBuffer.spiData.buffer, fragAddr, txSegLength);
		spiTxBuffer.spiData.hdr.offset_hi = (offset >> 8);
		spiTxBuffer.spiData.hdr.offset_lo = (offset & 0xff);
		spiTxBuffer.spiData.hdr.length_hi = (txSegLength >> 8);
		spiTxBuffer.spiData.hdr.length_lo = (txSegLength & 0xFF);

		// done with frame
		if(spiTxBuffer.spiData.hdr.status == LAST_FRAGMENT)
		{
			free(txFrame->buffer);
			free(txFrame);
		}
		break;
	}

}

// validate the frame eye
BOOL isIP400Frame(uint8_t *eye)
{
	if((eye[0] != 'I') || (eye[1] != 'P'))
		return FALSE;

	if((eye[2] != '4') || (eye[3] != 'C'))
		return FALSE;

	return TRUE;

}
