/**
 ******************************************************************************
 *
 * @file       ahrs_spi_comm.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      AHRS SPI communications.
 *
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include <pios.h>
#include "ahrs_spi_comm.h"
#include "ahrs_spi_program.h"

#ifdef IN_AHRS
#include <string.h>
#include "pios_debug.h"
#include "pios_spi.h"
#include "pios_irq.h"
#include "ahrs_spi_program_slave.h"
//#include "STM32103CB_AHRS.h"
#endif

/*transmit and receive packet magic numbers.
These numbers are chosen to be very unlikely to occur due to noise.
CRC8 does not always catch noise from cross-coupling between data lines.
*/
#ifdef IN_AHRS
#define TXMAGIC 0xA55AF0C3
#define RXMAGIC 0x3C5AA50F
#else
#define RXMAGIC 0xA55AF0C3
#define TXMAGIC 0x3C5AA50F
#endif

//packet types
typedef enum { COMMS_NULL, COMMS_OBJECT } COMMSCOMMAND;

//The maximum number of objects that can be updated in one cycle.
//Currently the link is capable of sending 3 packets per cycle but 2 is enough
#define MAX_UPDATE_OBJECTS 2

//Number of transmissions + 1 before we expect to see the data acknowledge
//This is controlled by the SPI hardware.
#define ACK_LATENCY 4

/** All data for one object
*/
typedef struct {
	uint8_t done;
	uint8_t index;
	AhrsSharedData object;
} ObjectPacketData;

/** One complete packet.
Other packet types are allowed for. The frame size will be the size of this
structure.
*/
typedef struct {
	uint32_t magicNumber;
	COMMSCOMMAND command;
	AhrsEndStatus status;
	union {			//allow for expansion to other packet types.
		ObjectPacketData objects[MAX_UPDATE_OBJECTS];
	};
	uint8_t dummy; //For some reason comms trashes the last byte
} CommsDataPacket;

static void FillObjectPacket();
static void CommsCallback(uint8_t crc_ok, uint8_t crc_val);
static void SetObjectDirty(const int idx);
static void HandleObjectPacket();
static void HandleRxPacket();
static void PollEvents();
#ifndef IN_AHRS
static void SendPacket(void);
static void AhrsUpdatedCb(AhrsObjHandle handle);
#endif

/** Receive data buffer
*/
static CommsDataPacket rxPacket;

/** Transmit data buffer
*/

static CommsDataPacket txPacket;
/** Objects that have changed and so should be transmitted
*/
static unsigned int dirtyObjects[MAX_AHRS_OBJECTS];

/** Objects that have been updated at startup
*/
static bool readyObjects[MAX_AHRS_OBJECTS];

/** List of event callbacks
*/
static AhrsEventCallback objCallbacks[MAX_AHRS_OBJECTS];

/** True for objects for which new data is received and callback needs to be called
*/
static bool callbackPending[MAX_AHRS_OBJECTS];

//More than this number of errors in a row will indicate the link is down
#define MAX_CRC_ERRORS 50
//At least this number of good frames are needed to indicate the link is up.
#define MIN_OK_FRAMES 50
//At least this number of empty objects are needed before the initial flood of events is over.
#define MIN_EMPTY_OBJECTS 10

static uint8_t linkOk = false;
static int okCount = MIN_OK_FRAMES;
static int emptyCount = MIN_EMPTY_OBJECTS;
static bool programReceive = false;

void AhrsInitComms(void)
{
	programReceive = false;
	AhrsInitHandles();
	memset(objCallbacks, 0, sizeof(AhrsEventCallback) * MAX_AHRS_OBJECTS);
	memset(callbackPending, 0, sizeof(bool) * MAX_AHRS_OBJECTS);
	memset(dirtyObjects, 0, sizeof(unsigned int) * MAX_AHRS_OBJECTS);
	memset(&txPacket, 0, sizeof(txPacket));
	memset(&rxPacket, 0, sizeof(rxPacket));
	memset(&readyObjects, 0, sizeof(bool) * MAX_AHRS_OBJECTS);
	txPacket.command = COMMS_NULL;
	rxPacket.command = COMMS_NULL;
}

static uint32_t opahrs_spi_id;
void AhrsConnect(uint32_t spi_id)
{
	/* Bind this comms layer to the appropriate SPI id */
	opahrs_spi_id = spi_id;
#ifndef IN_AHRS
	/* Comms already init in OP code */
	for (int ct = 0; ct < MAX_AHRS_OBJECTS; ct++) {
		AhrsObjHandle hdl = AhrsFromIndex(ct);
		if (hdl) {
			AhrsConnectCallBack(hdl, AhrsUpdatedCb);
		}
	}
#endif
}

int32_t AhrsSetData(AhrsObjHandle obj, const void *dataIn)
{
	if (obj == NULL || dataIn == NULL || obj->data == NULL) {
		return (-1);
	}
	if (memcmp(obj->data, dataIn, obj->size) == 0) {	//nothing to do, don't generate an event
		return (0);
	}
	memcpy(obj->data, dataIn, obj->size);
	SetObjectDirty(obj->index);
	return (0);
}

int32_t AhrsGetData(AhrsObjHandle obj, void *dataOut)
{

	if (obj == NULL || dataOut == NULL || obj->data == NULL) {
		return (-1);
	}
	memcpy(dataOut, obj->data, obj->size);
	return (0);
}

/** Mark an object to be sent
*/
void SetObjectDirty(const int idx)
{
	if (idx < 0 || idx >= MAX_AHRS_OBJECTS) {
		return;
	}
	dirtyObjects[idx] = ACK_LATENCY;
}
/** Work out what data needs to be sent.
	If an object was not sent it will be retried 4 frames later
*/
static void FillObjectPacket()
{
	txPacket.command = COMMS_OBJECT;
	txPacket.magicNumber = TXMAGIC;
	int idx = 0;
	for (int ct = 0; ct < MAX_UPDATE_OBJECTS; ct++) {
		txPacket.objects[ct].index = AHRS_NO_OBJECT;
		for (; idx < MAX_AHRS_OBJECTS; idx++) {
			if (dirtyObjects[idx] > 0) {
				if (dirtyObjects[idx] == ACK_LATENCY) {
					dirtyObjects[idx]--;
					txPacket.objects[ct].index = idx;
					AhrsObjHandle hdl = AhrsFromIndex(idx);
					if (hdl) {
						memcpy(&txPacket.objects[ct].object, hdl->data, hdl->size);
						break;
					}
				} else {
					dirtyObjects[idx]--;
					if (dirtyObjects[idx] == 0) {	//timed out
						dirtyObjects[idx] = ACK_LATENCY;
						txPacket.status.retries++;
					}
				}
			}
		}
	}
	for (; idx < MAX_AHRS_OBJECTS; idx++) {
		if (dirtyObjects[idx] > 0 && dirtyObjects[idx] != ACK_LATENCY) {
			dirtyObjects[idx]--;
			if (dirtyObjects[idx] == 0) {	//timed out
				dirtyObjects[idx] = ACK_LATENCY;
				txPacket.status.retries++;
			}
		}
	}
}

/** Process a received packet
*/
static void HandleRxPacket()
{
	switch (rxPacket.command) {
	case COMMS_NULL:
	    // Empty packet, nothing to do
		break;

	case COMMS_OBJECT:
		HandleObjectPacket();
		break;

	default:
		txPacket.status.invalidPacket++;
	}
}

/** Process a received UAVObject packet
*/
static void HandleObjectPacket()
{
	for (int ct = 0; ct < MAX_UPDATE_OBJECTS; ct++) {
	    uint8_t idx;

		// Flag objects that have been successfully received at the other end
		idx = rxPacket.objects[ct].done;
        txPacket.objects[ct].done = AHRS_NO_OBJECT;
		if (idx < MAX_AHRS_OBJECTS) {

			if (dirtyObjects[idx] == 1) {	//this ack is the correct one for the last send
				dirtyObjects[idx] = 0;
			}
		}

		// Handle received object if there is one in this packet
		idx = rxPacket.objects[ct].index;
		if (idx == AHRS_NO_OBJECT) {
			if (emptyCount > 0) {
				emptyCount--;
			}
			continue;
		}
		AhrsObjHandle obj = AhrsFromIndex(idx);
		if (obj) {
			memcpy(obj->data, &rxPacket.objects[ct].object, obj->size);
			txPacket.objects[ct].done = idx;
			callbackPending[idx] = true;    // New data available, call callback
			readyObjects[idx] = true;
		} else {
			txPacket.status.invalidPacket++;
		}
	}
#ifdef IN_AHRS
	FillObjectPacket();	//ready for the next frame
#endif
}

int32_t AhrsConnectCallBack(AhrsObjHandle obj, AhrsEventCallback cb)
{
	if (obj == NULL || obj->data == NULL) {
		return (-1);
	}
	objCallbacks[obj->index] = cb;
	return (0);
}

void AhrsGetStatus(AhrsCommStatus * status)
{
	status->remote = rxPacket.status;
	status->local = txPacket.status;
	status->linkOk = linkOk;
}


/** Function called after an SPI transfer
 */
static void CommsCallback(uint8_t crc_ok, uint8_t crc_val)
{
#ifndef IN_AHRS
	PIOS_SPI_RC_PinSet(opahrs_spi_id, 1);	//signal the end of the transfer
#endif
	txPacket.command = COMMS_NULL;	//we must send something so default to null

	// While the crc is ok, there is a magic value in the received data for extra security
	if (rxPacket.magicNumber != RXMAGIC) {
		crc_ok = false;
	}


	if (crc_ok) {
	    // The received data is OK, update link state and handle data
		if (!linkOk && okCount > 0) {
			okCount--;
			if (okCount == 0) {
				linkOk = true;
				okCount = MAX_CRC_ERRORS;
				emptyCount = MIN_EMPTY_OBJECTS;
			}
		}
		HandleRxPacket();
	} else {
	    // The received data is incorrect, update state
#ifdef IN_AHRS			//AHRS - do we neeed to enter program mode?
		if (memcmp(&rxPacket, SPI_PROGRAM_REQUEST, SPI_PROGRAM_REQUEST_LENGTH) == 0)
		{
			rxPacket.magicNumber = 0;
			programReceive = true; //flag it to be executed in program space
			return;
		}
#endif
		txPacket.status.crcErrors++;
		if (linkOk && okCount > 0) {
			okCount--;
			if (okCount == 0) {
				linkOk = false;
				okCount = MIN_OK_FRAMES;
			}
		}
	}
	rxPacket.magicNumber = 0;
#ifdef IN_AHRS
	/*queue next frame
	   If PIOS_SPI_TransferBlock() fails for any reason, comms will stop working.
	   In that case, AhrsPoll() should kick start things again.
	 */
	PIOS_SPI_TransferBlock(opahrs_spi_id, (uint8_t *) & txPacket, (uint8_t *) & rxPacket, sizeof(CommsDataPacket), &CommsCallback);
#endif
}

/** Call callbacks for object where new data is received
 */
static void PollEvents(void)
{
	for (int idx = 0; idx < MAX_AHRS_OBJECTS; idx++) {
		if (objCallbacks[idx]) {
			PIOS_IRQ_Disable();
			if (callbackPending[idx]) {
				callbackPending[idx] = false;
				PIOS_IRQ_Enable();
				objCallbacks[idx] (AhrsFromIndex(idx));
			} else {
				PIOS_IRQ_Enable();
			}
		}
	}
}

#ifdef IN_AHRS
void AhrsPoll()
{
	if(programReceive)
	{
		AhrsProgramReceive(opahrs_spi_id);
		programReceive = false;
	}
	PollEvents();
	if (PIOS_SPI_Busy(opahrs_spi_id) != 0) {	//Everything is working correctly
		return;
	}
	txPacket.status.kickStarts++;
//comms have broken down - try kick starting it.
	txPacket.command = COMMS_NULL;	//we must send something so default to null
	PIOS_SPI_TransferBlock(opahrs_spi_id, (uint8_t *) & txPacket, (uint8_t *) & rxPacket, sizeof(CommsDataPacket), &CommsCallback);
}

bool AhrsLinkReady(void)
{

	for (int ct = 0; ct < MAX_AHRS_OBJECTS; ct++) {
		if (!readyObjects[ct]) {
			return (false);
		}
	}
	return (linkOk);
}

#else

void AhrsSendObjects(void)
{
	static bool oldLink = false;

	PollEvents();

	if (oldLink != linkOk) {
		oldLink = linkOk;
		if (linkOk) {
			for (int ct = 0; ct < MAX_AHRS_OBJECTS; ct++) {
				AhrsObjHandle hdl = AhrsFromIndex(ct);
				if (!hdl) {	//paranoid - shouldn't ever happen
					continue;
				}
				AhrsSharedData data;
				UAVObjGetData(hdl->uavHandle, &data);
				AhrsSetData(hdl, &data);
				SetObjectDirty(ct);	//force even unchanged data to be sent
			}
		}
	}
	FillObjectPacket();
	SendPacket();
}

void SendPacket(void)
{
#ifndef IN_AHRS
	PIOS_SPI_RC_PinSet(opahrs_spi_id, 0);
#endif
	//no point checking if this failed. There isn't much we could do about it if it did fail
	PIOS_SPI_TransferBlock(opahrs_spi_id, (uint8_t *) & txPacket, (uint8_t *) & rxPacket, sizeof(CommsDataPacket), &CommsCallback);
}

static void AhrsUpdatedCb(AhrsObjHandle handle)
{
	UAVObjSetData(handle->uavHandle, handle->data);
	return;
}
#endif