// ###########################################################################
// #
// # OSCASerial - Implementation of OSCA RS232
// # Copyright (C) 2009-2012 Tony Friery [DialTone]
// #
// # ALL RIGHTS RESERVED
// #
// ###########################################################################

#pragma hdrstop

#include "OSCASerial.h"
#include "FormDiag.h"
#include "OSCABoard.h"

//---------------------------------------------------------------------------
void __fastcall TOSCASerial::QueueFile(UnicodeString FileName)
{
	if (fQueuedFile)
	{
		delete fQueuedFile;
		fQueuedFile = NULL;
	}

	fQueuedFile = new TMemoryStream();
	fQueuedFile->LoadFromFile(FileName);
	fQueuedFile->Position = 0;

	this->fTransferDirection 	= false;
	this->fTransferActive		= true;
	this->fTransferSize			= fQueuedFile->Size;
	this->fTransferTimeout		= 200000000;	// About 6 seconds

	// First inject file header
	StuffFileHeader(FileName);
}

void TOSCASerial::StuffFileHeader(UnicodeString FileName)
{
	char buffer[258];

	/*
		$00 - $0F: ASCII filename
		$10 - $11: Length of file (lo word)
		$12 - $13: Length of file (hi word)
		$14 - $1F: ASCII "Z80P.FHEADER" (12 chars)
		$20 - $FF: All must be zero
	*/

	memset(buffer, 0x00, sizeof(buffer));

	UnicodeString filteredFile = ExtractFileName(ExtractShortPathName(FileName));
	sprintf(buffer, "%s", AnsiString(filteredFile).c_str());
	buffer[0x10] = this->fTransferSize & 0xff;
	buffer[0x11] = (this->fTransferSize >> 8) & 0xff;
	buffer[0x12] = (this->fTransferSize >> 16) & 0xff;
	buffer[0x13] = (this->fTransferSize >> 24) & 0xff;
	sprintf(&buffer[0x14], "%s", "Z80P.FHEADER");

	if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
	{
		DiagForm->AddMessage(DIAG_RS232, L"Preparing to send File Header");
	}

	StuffBuffer(buffer);
}

UINT32 TOSCASerial::CalculateCRC(byte *buffer)
{
	// Calculate CRC for buffer
	UINT32 crcValue = 0xffff;

	for (int i = 0; i < 256; i++)
	{
		UINT32 value = (UINT32)buffer[i];
		value <<= 8;
		crcValue = value ^ crcValue;
		for (int j = 0; j < 8; j++)
		{
			crcValue += crcValue;

			if (crcValue > 0xffff)
			{
				crcValue &= 0xffff;
				crcValue ^= 0x1021;
			}
		}
	}

	return (crcValue & 0xffff);
}

void TOSCASerial::StuffBuffer(byte *buffer)
{
	UINT16 crcValue = CalculateCRC(buffer);

	buffer[256] = crcValue & 0xff;
	buffer[257] = (crcValue >> 8) & 0xff;

	if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
	{
		DiagForm->AddMessage(DIAG_RS232, UnicodeString::Format(
			L"Sending Packet with Checksum: 0x%4.4x",
			OPENARRAY(TVarRec, (crcValue))
		));
	}

	//TFileStream *fso = new TFileStream(L"serial.bin", fmCreate | fmOpenReadWrite);
	//fso->Write(buffer, 258);
	//delete fso;

	for (int i = 0; i < 258; i++)
	{
		this->fRXBuffer.push_back(buffer[i]);
	}
}

bool __fastcall TOSCASerial::TransferActive(void)
{
	return this->fTransferActive;
}

bool __fastcall TOSCASerial::TransmitPending(void)
{
	return (this->fTXBuffer.size() == 258);
}

bool __fastcall TOSCASerial::ReceivePending(void)
{
	return (!this->fRXBuffer.empty());
}

void __fastcall TOSCASerial::WriteByte(byte Value)
{
	this->fTransferTimeout		= 200000000;	// About 6 seconds
	this->fTXBuffer.push_back(Value);
}

byte __fastcall TOSCASerial::ReadByte(void)
{
	byte retv = 0x00;
	this->fTransferTimeout		= 200000000;	// About 6 seconds

	if (!this->fRXBuffer.empty())
	{
		retv = this->fRXBuffer.front();
		this->fRXBuffer.pop_front();
	}

	return retv;
}

void __fastcall TOSCASerial::Tick(unsigned int NumCycles)
{
	if (this->fTransferActive)
	{
		// Check if buffers need refilling/emptying
		if (this->fTransferDirection)
		{
			// Receiving to PC
			if (this->fTXBuffer.size() == 258)
			{
				// Next packet received
				byte buffer[258];

				for (int i = 0; i < 258; i++)
				{
					buffer[i] = this->fTXBuffer[i];
				}

				UINT32 expectedCRC = CalculateCRC(buffer);
				UINT32 actualCRC = (256 * (UINT32)buffer[257]) | buffer[256];

				if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
				{
					DiagForm->AddMessage(DIAG_RS232, UnicodeString::Format(
						L"Received follow-on packet with Checksum 0x%4.4x (expected 0x%4.4x)",
						OPENARRAY(TVarRec, (actualCRC, expectedCRC))
					));
				}

				if (actualCRC == expectedCRC)
				{
					unsigned int bytesLeft = this->fTransferSize - this->fQueuedFile->Position;
					if (bytesLeft > 256)
					{
						bytesLeft = 256;
					}

					if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
					{
						DiagForm->AddMessage(DIAG_RS232, UnicodeString::Format(
							L"Packet contains %d useful data bytes",
							OPENARRAY(TVarRec, (bytesLeft))
						));
					}

					fQueuedFile->Write(buffer, bytesLeft);
					this->fRXBuffer.push_back('O');
					this->fRXBuffer.push_back('K');

					if (this->fTransferSize == this->fQueuedFile->Size)
					{
						// transfer complete
						if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
						{
							DiagForm->AddMessage(DIAG_RS232, L"Transfer Completed");
						}

						this->fOwner->TransferComplete(true, this->fTransferDirection);
						this->fTransferActive 		= false;
						this->fTransferDirection	= false;
						this->fTransferTimeout		= 0;

						if (!DirectoryExists(L"incoming"))
						{
							CreateDir(L"incoming");
						}

						this->fQueuedFile->SaveToFile(L"incoming\\" + this->fTransferFilename);
						delete this->fQueuedFile;
						this->fQueuedFile = NULL;
						return;
					}
				}
				else
				{
					// Error - Cause Timeout
					DiagForm->AddMessage(DIAG_RS232, L"Invalid checksum");
					this->fRXBuffer.push_back('X');
					this->fRXBuffer.push_back('X');
					this->fTransferTimeout = 0;
				}

				this->fTXBuffer.clear();
			}
		}
		else
		{
			// Sending to OSCA
			if (this->fTXBuffer.size() == 2)
			{
				if ((this->fTXBuffer[0] == 'O') && (this->fTXBuffer[1] == 'K'))
				{
					// Prepare next packet
					if (this->fQueuedFile->Position >= this->fQueuedFile->Size)
					{
						// transfer complete
						this->Reset();
						delete this->fQueuedFile;
						this->fQueuedFile = NULL;
						this->fOwner->TransferComplete(true, this->fTransferDirection);
						return;
					}

					byte buffer[258];
					memset(buffer, 0x00, sizeof(buffer));
					unsigned int sizeLeft = 256;
					if ((this->fQueuedFile->Size - this->fQueuedFile->Position) < 256)
					{
						sizeLeft = this->fQueuedFile->Size - this->fQueuedFile->Position + 1;
					}

					this->fQueuedFile->Read(buffer, sizeLeft);

					if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
					{
						DiagForm->AddMessage(DIAG_RS232, L"Sending File Packet");
					}

					StuffBuffer(buffer);
				}
				else if ((this->fTXBuffer[0] == 'W') && (this->fTXBuffer[1] == 'W'))
				{
					// Just wait for another ACK
				}
				else
				{
					// Cause timeout
					this->fTransferTimeout = 0;

					this->fOwner->TransferComplete(false, this->fTransferDirection);
					if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
					{
						DiagForm->AddMessage(DIAG_RS232, L"Received bad ack");
					}
				}

				this->fTXBuffer.clear();
			}
		}

		// Handle Timeout
		if (this->fTransferTimeout > NumCycles)
		{
			this->fTransferTimeout -= NumCycles;
		}
		else
		{
			// Timed Out
			if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
			{
				DiagForm->AddMessage(DIAG_RS232, L"Transfer Timeout");
			}
			this->Reset();

			if (this->fOwner)
			{
				this->fOwner->TransferComplete(false, this->fTransferDirection);
			}
		}
	}
	else if (this->fTXBuffer.size() == 258)
	{
		// We have a packet
		byte buffer[258];

		for (int i = 0; i < 258; i++)
		{
			buffer[i] = this->fTXBuffer[i];
		}


		UINT32 actualCRC = (256 * (UINT32)buffer[257]) | buffer[256];
		UINT32 expectedCRC = CalculateCRC(buffer);

		if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
		{
			DiagForm->AddMessage(DIAG_RS232, UnicodeString::Format(
				L"Received header packet with Checksum 0x%4.4x (expected 0x%4.4x)",
				OPENARRAY(TVarRec, (actualCRC, expectedCRC))
			));
		}

		if ((actualCRC == expectedCRC) &&	(!strncmp(&buffer[0x14], "Z80P.FHEADER", 12)))
		{
			UnicodeString fileName = L"";

			for (int i = 0; i < 16; i++)
			{
				if (buffer[i] != 0x00)
				{
					fileName += UnicodeString((char)fTXBuffer[i]);
				}
			}

			if (this->fOwner)
			{
				this->fOwner->TransferStarted(fileName);
			}

			this->fTransferFilename 	= fileName;
			this->fTransferDirection 	= true;
			this->fTransferActive 		= true;
			this->fTransferSize 			= buffer[0x10] + (0x100 * buffer[0x11]) + (0x10000 * buffer[0x12]) + (0x1000000 * buffer[0x13]);
			this->fTransferTimeout		= 200000000;	// About 6 seconds
			this->fQueuedFile				= new TMemoryStream();
			this->fRXBuffer.push_back('O');
			this->fRXBuffer.push_back('K');

			if ((DiagForm) && (DiagForm->IsActive(DIAG_RS232)))
			{
				DiagForm->AddMessage(DIAG_RS232, L"Valid file header received");
			}
		}
		else
		{
			DiagForm->AddMessage(DIAG_RS232, L"Invalid file header received");
			this->fRXBuffer.push_back('X');
			this->fRXBuffer.push_back('X');
		}

		this->fTXBuffer.clear();
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCASerial::Reset
//
//  PURPOSE:	Perform Power-On-Reset of the RS232 Module
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCASerial::Reset(void)
{
	this->fTXBuffer.clear();
	this->fRXBuffer.clear();
	this->fTransferActive 		= false;
	this->fTransferDirection	= false;
	this->fTransferTimeout		= 0;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCASerial::TOSCASerial [CONSTRUCTOR]
//
//  PURPOSE:	Constructor for the RS232 Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCASerial::TOSCASerial(TOSCABoard *owner) :
	fOwner(owner), fQueuedFile(NULL)
{
	// Perform initial Power-On Reset
	this->Reset();
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCASerial::~TOSCASerial [DESTRUCTOR]
//
//  PURPOSE:	Destructor for the RS232 Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCASerial::~TOSCASerial()
{
}

//---------------------------------------------------------------------------

#pragma package(smart_init)

