#include "mungal4.h"
#pragma hdrstop

#include "l4plasma.h"
#include "..\munga\time.h"

//########################################################################
//############################# PlasmaDisplay ############################
//########################################################################
PlasmaDisplay::PlasmaDisplay(
	//Win32 Serial Support: ADB 02/24/2007
	//Word port,
	const char* port
	//Word int_num
):
	Video8BitBuffered(plasmaWidth, plasmaHeight),
      forceAll(False)
{
	//------------------------------------
	// Create serial interface
	//------------------------------------
	//Win32 Serial Support: ADB 02/24/2007
	//pc_serial = new PCSerial(PCS_9600, PCS_N81, port, int_num);
	pc_serial = new PCSerial(PCS_9600, PCS_N81, port);
	if (pc_serial != NULL)
	{
		Register_Object(pc_serial);
		pc_serial->FlowControlEnable(False);
		//------------------------------------
		// Turn off the cursor
		//------------------------------------
		static char
			cursorOffString[] = { 27 /*ESC*/, 'G', (char) 0x00 };

		pc_serial->SendString(cursorOffString, 3);
	}
	//------------------------------------
	// Clear update flag array
	//------------------------------------
	currentTransferLine = 0;
	updateInProgress = False;

	for(int i=0; i<plasmaHeight; ++i)
	{
		updateFlag[i] = 0;
	}

	this->DrawFilledRectangle(0, 0xFF, GraphicsDisplay::Replace, 0, 0, this->width - 1, this->height - 1);
	this->Update(True);

	Check_Fpu();
}

PlasmaDisplay::~PlasmaDisplay()
{
	if (pc_serial != NULL)
	{
		//---------------------------------------------------------------------
		// Flush data to plasma display
		//---------------------------------------------------------------------
		this->DrawFilledRectangle(0, 0xFF, GraphicsDisplay::Replace, 0, 0, this->width - 1, this->height - 1);
		this->Update(True);

		WaitForUpdate();

		// This odd sequence of allocation, setting, and adding is necessary:
		// if you try to combine them, the compiler gets confused as to which
		// method to invoke for 'Now()'.
		Time
			then;
		then = Now();
		then += 5.0f;
		while (Now() < then)
		{
			//---------------------------------------------------
			// Drop out of loop when ALL data has been sent
			//---------------------------------------------------
			if (pc_serial->CanDraw() <= 0)                  
			{
				if (!pc_serial->IsActive())
				{
					break;
				}
			}
		}
		//---------------------------------------------------------------------
		// Delete the serial interface
		//---------------------------------------------------------------------
		Unregister_Object(pc_serial);
		delete pc_serial;
	}
	Check_Fpu();
}

Logical
	PlasmaDisplay::TestInstance() const
{
	return Video8BitBuffered::TestInstance(); 
}

void
	PlasmaDisplay::ShowInstance(char *indent)
{
	std::cout << indent << "Plasma:\n";

	char
		temp[80];

	Str_Copy(temp,indent, 80);
	Str_Cat(temp,"...", 80);

	Video8BitBuffered::ShowInstance(temp);
	Check_Fpu();
}


int
	sendLine(
		char *transfer_pointer,
		int line_number,
		Byte *source_pointer,
		int source_width
	)
{
	int
		x,
		index;
	Byte
		byte,
		byte_bit;
	//--------------------------------------------
	// Send header
	//--------------------------------------------
	transfer_pointer[0] = (char) 27;							// ascii ESC
	transfer_pointer[1] = 'P';									// graphics command
	transfer_pointer[2] = (char) 0;								// screen number
	transfer_pointer[3] = (char) line_number;				// Y position
	transfer_pointer[4] = (char) 0;								// X BYTE position
	transfer_pointer[5] = (char) ((source_width+7)>>3);	// width in bytes
	transfer_pointer[6] = (char) 1;

   transfer_pointer += 7;

	//pc_serial->SendString(graphics, 7);
	//--------------------------------------------
	// Send data
	//--------------------------------------------
	byte = 0;
	byte_bit = 0x80;
	index = 0;
	for(x=0; x<source_width; ++x)
	{
		//
		// Add bit to byte
		//
		if (*source_pointer++)
		{
			byte |= byte_bit;
		}
		//
		// Update bit position
		//
		byte_bit >>= 1;
		//
		// If entire byte collected, send it
		//
		if (byte_bit == 0)
		{
			Verify(index < sizeof(graphics));
			*transfer_pointer = byte;
         transfer_pointer++;
         index++;
			byte = 0;
			byte_bit = 0x80;
		}
	}
	//
	// End of source line: send remaining bits
	//
	if (byte_bit != 0x80)
	{
		transfer_pointer[index++] = byte;
	}
	//
	// Send graphics data
	//
	//pc_serial->SendString(graphics, index);
	Check_Fpu();

   return index + 7;
}

void
	PlasmaDisplay::Scan(Logical forceAll)
{
	Word
		changed_line = (Word) forceAll,
		*changed_line_pointer = changedLine;
	char
		*update_pointer=updateFlag;
	int
		y;

	for(y=0; y<plasmaHeight; ++y,++update_pointer)
	{
		//---------------------------------------------------------
		// Scan for changed lines
		//---------------------------------------------------------
		changed_line += *changed_line_pointer;
		*changed_line_pointer++ = 0;

		if (changed_line)
		{
			*update_pointer = 1;

			updateInProgress = True;
		}
	}

	Check_Fpu();
}

Logical
	PlasmaDisplay::Update(Logical force_all)
{
   Time startSendLine,endSendLine,startWhile,endWhile,dataSendStart,dataSendEnd;
   Time start = Now();
   int __totalTransferLength;

   Time allStarts[100], allEnds[100];

	int
		transfer_count,
		total_count;
	Logical
		result = True;		// True == 'more to send, not done'

	if (pc_serial != NULL && pc_serial->CanDraw())
	{
		//-----------------------------------------
		// Accumulate changed lines
		//-----------------------------------------
		forceAll |= force_all;

		if (currentTransferLine == 0)
		{
			Scan(forceAll);
			forceAll = False;
		}
		//-----------------------------------------
		// Send only if the buffer is empty
		//-----------------------------------------
			total_count = plasmaHeight;
			transfer_count = 7;
         int transfer_length = 32 * transfer_count;
         char *transfer_data = new char[transfer_length];
         char *transfer_pointer = transfer_data;
         int total_transfer_count = transfer_count;
         int total_transfer_length = 0;

         startWhile = Now();

         int i = 0;

			while(transfer_count>0)
			{
				//-----------------------------------------
				// Transfer a line if it's ready
				//-----------------------------------------
            allStarts[i] = Now();
				if (updateFlag[currentTransferLine])
				{
					//-----------------------------------------
					// Decrement the 'transfer count'
					//-----------------------------------------
					--transfer_count;
					//-----------------------------------------
					// Clear the 'update' flag
					//-----------------------------------------
					updateFlag[currentTransferLine] = 0;
					//-----------------------------------------
					// Send the line data to the display
					//-----------------------------------------
               startSendLine = Now();
					int length = sendLine(transfer_pointer, currentTransferLine,
						pixelBuffer->Data.MapPointer +
							(pixelBuffer->Data.Size.x * currentTransferLine),
						pixelBuffer->Data.Size.x
					);
               endSendLine = Now();

               transfer_pointer += length;
               total_transfer_length += length;
				}


            allEnds[i] = Now();
				//-----------------------------------------
				// Increment currentTransferLine
				//-----------------------------------------
				if (++currentTransferLine >= plasmaHeight)
				{
					currentTransferLine = 0;
				}
				//-----------------------------------------
				// Reset to top if all lines scanned
				//-----------------------------------------
				if (--total_count < 0)
				{
					currentTransferLine = 0;	// reset to top line
					updateInProgress = False;
					result = False;				// False == 'all data sent'
					break;
				}

            i++;
			}

         endWhile = Now();

         dataSendStart = Now();
         if (total_transfer_length > 0)
         {
            this->pc_serial->SendString(transfer_data, total_transfer_length);
         }
         dataSendEnd = Now();

         __totalTransferLength = total_transfer_length;

         delete [] transfer_data;
	}
	Check_Fpu();

   Time end = Now();

   if (end.ticks - start.ticks > 100)
   {
      start = end;
   }

	return result;
}

void
	PlasmaDisplay::WaitForUpdate()
{
	Time
		end_time;

	end_time = Now();
	end_time += 5.0f;		// We'll wait this many seconds to update the display-

	while (updateInProgress)
	{
		Update(False);

		if (Now() >= end_time)
		{
			break;
		}
	}
	Check_Fpu();
}
