#include <process.h>
#include "ModemSerial.h"
//#include "Checksum.h"

#define LOGTOFILE 0

ModemSerial::ModemSerial(const char* pcDevice, int baudRate)
    : ModemFork("ModemSerial")
{
    // Initialize the device.
    _device = new SerialDevice(pcDevice);
	_device->clearPort();
    _iBaudRate = baudRate;

    // Note-  if you CHANGE the line format, also CHANGE getBytesPerSecondChild()
    _device->setLineFormat( _iBaudRate, 8, 1, "NONE" );
    _device->raw();
    int b = _device->blocking();

	_logFile = NULL;

    _tTimeOfLastReceive = 0;
    _tTimeOfLastSend = 0;

    _pcStartCode = new char[3];
    _pcStartCode[0] = (char)0x55;
    _pcStartCode[1] = (char)0xAA;
    _pcStartCode[2] = (char)0x00;
    _iStartCodeLength = strlen(_pcStartCode);

    _pcLeftovers = new char[_iStartCodeLength];
    _iLeftoversLength = 0;
}

ModemSerial::~ModemSerial()
{
	if (_logFile) fclose(_logFile);
    delete _device;
	delete [] _pcStartCode;
	delete [] _pcLeftovers;
}

int ModemSerial::iWriteToModem(char* buffer, int nBytes)
{
    int i_return = 0;
    /*
      Send start code (to help sync)
      Next 4 bytes are an int, the size of the following data blob.
      Next N bytes are a checksum.
      Next M bytes are the data.
      Send the data blob.
		Log it
    */

    try
	{
	  //	int iChecksum = Checksum::checksum32CRC(buffer, nBytes);
		int iChecksum = checksum(buffer, nBytes);
		//char * txbuf = new char(nBytes + 10);
		char txbuf[256];

                // Send start code! (2 bytes)
		memcpy(&(txbuf[0]), _pcStartCode, _iStartCodeLength); 
		// Next 4 bytes are an int, the size of the following data blob
		memcpy(&(txbuf[2]), (const char*)&nBytes, sizeof(int)); 
		// Next 4 bytes are a checksum.
		memcpy(&(txbuf[6]), (const char*)&iChecksum, sizeof(int)); 
		// Next M bytes are the data.
		memcpy(&(txbuf[10]), buffer, nBytes); 

	    i_return = _device->writeFlush(txbuf, nBytes + 10);

	    _tTimeOfLastSend = time(0);

		if (LOGTOFILE) {
			// Log the same
			if (!_logFile) {
				char buf[32];
				sprintf(buf,"modemActivity%ld.log",time(NULL));
				_logFile = fopen(buf,"a");
				Assert(_logFile);
			}
			int i = fprintf(_logFile,"%ld(TX):  ", _tTimeOfLastSend);
			fwrite(txbuf, nBytes + 10, 1, _logFile);
			fprintf(_logFile,"\n");
			fflush(_logFile);
		}

	 return nBytes;
	}
    catch (Exception /*e*/)
	{
	    Assert(false);
	    return 0;
	}
}

int ModemSerial::iReadFromModem(char* buffer, int iMaxBytes)
{
    /*
      Send start code (to help sync)
      Next 4 bytes are an int, the size of the following data blob.
      Next N bytes are a checksum.
      Next M bytes are the data.
      Read the data blob.
      Verify that it was the correct length.
      Verify that it checksummed.
    */
	int i_return = 0;

	// Is there any data to be had?
	if (_device->nRecvdBytes() >= _iStartCodeLength + sizeof(int) + sizeof(int)) {

		// Find the start code.
		if (!bFindEndOfStartCodeChild()) {
			_device->clearPort();
			_iLeftoversLength = 0;
			// Failed!
			return 0;
		}

		// Next 4 bytes are an int, the size of the following data blob.
		int nBytes = ERROR;
		i_return = iReadFromDeviceChild((char*)&nBytes, sizeof (int));
		if (i_return < sizeof (int) ) return 0;
		if ( nBytes > iMaxBytes || nBytes < 0 || nBytes > iMaxBytes ) return 0;  // the size is corrupted...

		// Next N bytes are a checksum.
		int iChecksum = 0;
		i_return = iReadFromDeviceChild((char*)&iChecksum, sizeof(int));
		if ( i_return < sizeof (int) ) return 0;

		// Next M bytes are the data.
		i_return = iReadFromDeviceChild(buffer, nBytes);
		if ( i_return < nBytes || i_return < 0 ) return 0;

		// Check checksum
		// if (iChecksum != Checksum::checksum32CRC(buffer, nBytes) ) {
		if (iChecksum != checksum(buffer, nBytes) ) {
			// Bad checksum
			return 0;
		} else {
			// Full success!!!
			_tTimeOfLastReceive = time(0);
			return nBytes;
		}
	} else {
		// No data yet.
		return 0;
	}
}


bool ModemSerial::bHasConnectionChild()
{
    return true; //assume a connection.
}

int ModemSerial::getBytesPerSecondChild()
{
    return _iBaudRate / (8+1);
}

int ModemSerial::iExpectedRoundTripDuration() const
{
    // worst case
    return iSERIAL_MAX_TRANSIT_BUFFER_SIZE*2*8/_iBaudRate;
}

bool ModemSerial::bFindEndOfStartCodeChild()
{
    // Get a working buffer.
#define iWORKING_BUFFER_LENGTH 102
    Assert(iWORKING_BUFFER_LENGTH > _iStartCodeLength);
    char buffer[iWORKING_BUFFER_LENGTH];

    // Fill the working buffer with potential start code data!
    // We need at least _iStartCodeLength bytes to work with!
    Assert(_iLeftoversLength < _iStartCodeLength);
    memcpy(buffer, _pcLeftovers, _iLeftoversLength);

    // Fill the working buffer out to the full start code len
    int iReadLength = _iStartCodeLength - _iLeftoversLength;
    int iBytesSuccessfullyRead = iReadFromDeviceChild(buffer + _iLeftoversLength, iReadLength);
    if (iReadLength != iBytesSuccessfullyRead)
	{
	    Assert(iReadLength > iBytesSuccessfullyRead);

	    // This should never happen because we check to see how many bytes are waiting before
	    //  calling this func.
	    if (iBytesSuccessfullyRead < 0)
		{
		    // Device failed.
		    return false;
		}
	    else
		{
		    // Didn't read enough bytes.  Remember the ones that we did read.
		    _iLeftoversLength += iBytesSuccessfullyRead;
		    return false;
		}
	}

    // We're ready to start!
    char* pcStart = buffer;
    char* pcEnd = buffer + _iStartCodeLength;

    while(1)  // this will exist because we either return or increment pcStart.
	// incrementing pcStart causes pcEnd to increment, which will
	// eventually fail a test vs. length of working buffer.
	{
	    // Do we have _iStartCodeLength bytes available?
	    int nCompareBytes = pcEnd - pcStart;
	    if (nCompareBytes < _iStartCodeLength)
		{
		    // How many bytes do we need?
		    int nNeededBytes = _iStartCodeLength - nCompareBytes;

      // If we grab that many, will we overflow the working buffer?
		    if (pcEnd + nNeededBytes > buffer + iWORKING_BUFFER_LENGTH)
			{
			    // Yes, we will.  Don't bother, wait for the next try.
			    _iLeftoversLength = nCompareBytes;
			    memcpy(_pcLeftovers, pcStart, _iLeftoversLength);
			    return false;
			}

		    // Try to read the required number of bytes.
		    int nBytesRead = iReadFromDeviceChild(pcEnd, nNeededBytes);
		    pcEnd += nBytesRead;

      // Did we read the right amount?
		    Assert(nBytesRead <= nNeededBytes);
		    if (nNeededBytes > nBytesRead)
			{
			    // No!  We are missing some.  Stop scanning.
			    _iLeftoversLength = pcEnd - pcStart;
			    memcpy(_pcLeftovers, pcStart, _iLeftoversLength);
			    return false;
			}
		}

	    // We have exactly what we needed!  Test it!
	    // Do we have a match?
	    if (!memcmp(pcStart, _pcStartCode, _iStartCodeLength))
		{
		    // Yes!
		    _iLeftoversLength = 0;
		    return true;
		}
	    else
		{
		    // No!  Try again.
		    pcStart+=_iStartCodeLength;
		}
	}
}


int ModemSerial::iReadFromDeviceChild(char* buffer, int nBytes)
{
    // Calculate proper timeout.
    float fBytesPerSecond = getBytesPerSecondChild();
    float fSeconds = nBytes / fBytesPerSecond;

	int retVal = _device->readNChars(buffer, nBytes,100 + (fSeconds * 2000) );
	if (retVal > 0) {
		if (LOGTOFILE) {
			if (!_logFile) {
				char buf[32];
				sprintf(buf,"modemActivity%ld.log",time(NULL));
				_logFile = fopen(buf,"a");
				Assert(_logFile);
			}
			int i = fprintf(_logFile,"%ld(RX):  ", time(NULL));
			fwrite(buffer, sizeof(char), retVal, _logFile);
			fprintf(_logFile,"\n");
			fflush(_logFile);
		}
	}
    return retVal;
}

bool ModemSerial::bReadFromDeviceChild(char* buffer, int nBytes)
{
    return (nBytes == iReadFromDeviceChild(buffer, nBytes));
}

int ModemSerial::checksum(char* buf, int nbytes)
{
    unsigned char  byteVal, chk = 0;

    for (short byte = 0; byte < nbytes; byte++)
    {
      sscanf( (char *)( buf + byte ), "%c", &byteVal );
      chk += byteVal;
    }
    return chk;
}
