#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <i86.h>
#include <string.h>
#include <time.h>

#include "System.h"
#include "Syslog.h"
#include "LcXmodem.h"

LcXmodem::LcXmodem(SerialDevice *device) :
  _nBytes(0), _seqCount(0), _device(device)
{
}

LcXmodem::~LcXmodem()
{
}

void LcXmodem::resetSeqCount()
{
  _seqCount = 0;
}

/////////////////////////////////////////////////////////////////////////////
// Sends datafile in 30-byte slices reformatted for ARGOS data transmission.
// Reformatting is done to increase integrity (using checksums) and 
// to ease reassembly by receiver (using sequence numbers).
// Each 30 byte-slice from the source data file is reformatted into a
// 32-byte packet:
//  byte  0: sequence # (0-255)
//  byte  1: byte 0 of data
//  byte  2: byte 1 of data
//    :        :
//  byte 30: byte 29 of data
//  byte 31: checksum (sum of bytes 0-30)
// The 32-byte packet is then placed into a result buffer (10KB) and the
// buffer transmitted to the buoy using the "normal" send function.
//
int LcXmodem::send30Xmodem(BYTE *senddata, long numbytes)
{
  Boolean debug = False;

  if (numbytes <= 0)
    return OKAY;

  unsigned char result[10240];

  while (numbytes > 0)
  {
    // Initialize the result buffer with blank spaces as filler
    //
    memset(result, ' ', sizeof(result));

    // Pack the result buffer with packs consisting of 30 bytes data,
    // 1 sequence byte, and one checksum byte.
    //
    int num30packs = min((numbytes/30 + 1), 320);
    dprintf("send30Xmodem():sending %ld bytes, %d packs now\n",
            numbytes, num30packs);
    for (int p = 0; p < num30packs; p++)
    {
      // Initialize the current pack
      //
      unsigned char *currentPack = result+(p*32);   // Destination pointer
      unsigned char *currentData = senddata+(p*30); // Data source pointer

      // Determine how many bytes to copy from source data accounting
      // for non-multiples of 30. Copy the data and calculate/insert
      // the checksum. The input variable numbytes is decremented
      // as each byte is copied from the source.
      //
      int bytesToCopy = min(30, numbytes);
      char checksum = currentPack[0] = _seqCount++;
      currentPack++;
      dprintf("Seq# %d, bytes packing %d, remaining %d",
              _seqCount-1, bytesToCopy, numbytes);
      for (int i = 0; i < bytesToCopy; i++, numbytes--)
      {
        checksum += *currentData;
        *currentPack++ = *currentData++;
      }

      // For those packs where the source data does not fill up
      // the result pack (normally the last one from the source),
      // calc the checksum with the remaining blanks in the pack.
      //
      for (int j = bytesToCopy; j < 30; j++)
      {
        checksum += *currentPack++;
      }
      *currentPack = checksum;
    }
    dprintf("Sending %d reformatted bytes", num30packs*32);
    if (OKAY != sendXmodem(result, num30packs*32))
      return XMODEM_ERROR;
  }
  return OKAY;
}

//////////////////////////////////////////////////////////////////////////
// pass in data array plus number of bytes to be sent
// we will send the number of bytes to the receiving program
// num bytes in last packet might be smaller than others

int LcXmodem::sendXmodem(BYTE *senddata, long numbytes)
{
   Boolean debug = False;

   int  attempts, packetsize, size_of_last_packet;
   int  j, ret, errors, temp;
   int  packet_number, num_packets_to_send;
   BYTE packethi, packetlo;
   BYTE checksum, packnum;
   char b;
   BYTE *data = senddata;

   dprintf("sendXmodem() sending %ld bytes\n", numbytes);
   attempts = 0;
   packet_number = 1;
   errors = 0;
   
   num_packets_to_send = (int)(numbytes / PACKET_SIZE);
   packetsize = PACKET_SIZE;
   // is there an integral number of standard-size packets?
   temp = (int)(numbytes%PACKET_SIZE);
   if (temp != 0)
   {
      // a remainder - so last packet is smaller than normal
      size_of_last_packet = temp;
      ++num_packets_to_send;   // and there is one more packet
   }
   else
   {
      // last one is normal size if there are integral number
      // of num_packets_to_send
      size_of_last_packet = PACKET_SIZE;
   }

   // wait for NAK - eating other chars
   while (1)
   {
      ret = GetCharTimeout(&b, 20L);
      if (b == NAK)   // receiver is ready
      {
         break;
      }
      else
      {
         if (++errors > ERRORMAX)
         {
            dprintf("Receiver not sending NAKs\n");
            return XMODEM_ERROR;
         }
      }
   }

   dprintf("sendXmodem() plan: send %d packets of %d bytes\n",
	  num_packets_to_send, packetsize);
   while (1)
   {
      attempts = 0;
      while (1)
      {
         checksum = 0;
         if (packet_number == num_packets_to_send)  // if this is the last packet
            packetsize = size_of_last_packet;
         packethi = HIBYTE(packetsize);
         packetlo = LOBYTE(packetsize);
         packnum = 0xff&(int)(packet_number%256);
         SendChar(SOH);
         dprintf("sent SOH (%d) ", packetsize);
         // do not include SOH in checksum, but do include pack sent and size
         SendByte(packnum);
         checksum += packnum;
         SendByte(packethi);
         checksum += packethi;
         SendByte(packetlo);
         checksum += packetlo;
         for (j=0; j<packetsize; j++)
         {                                         
            SendByte(data[j]);
            checksum += data[j];
         }
         SendByte(checksum);
	 FlushPacket();
         ret = GetCharTimeout(&b, 10L);
         if (ret != OKAY)
            dprintf("Timeout getting ACK/NAK\n");
         else
            dprintf("packet %d\n", packet_number);
         if (b == ACK)
            break;
         if (++attempts >= ERRORMAX)
         {
            dprintf("no acknowledgment of packet %d, aborting\n", packet_number);
            return XMODEM_ERROR;
         }
      }
      data += packetsize;
      packet_number++;
      if (packet_number > num_packets_to_send)
         break;
   }

   // finally send EOT to signal we are finished sending data
   attempts = 0;
   while (1)
   {
      SendChar(EOT);
      FlushPacket();
      GetCharTimeout(&b, 10L);
      if (b == ACK)   // receiver acknowledged
         break;       // and quit
      attempts++;
      if (attempts >= RETRYMAX)
      {
         dprintf("no acknowledgment of end of file\n");
         return XMODEM_ERROR;
      }
   }

   return OKAY;
}

//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////
int LcXmodem::receiveXmodem(BYTE *recvdata, long *plBytesReceived)
{
   Boolean debug = False;

   int  i;
   int  packets_rcvd, errors, errorflag;
   int  packetsize;
   int  nRetVal;
   BYTE firstchar, this_packet, packethi, packetlo;
   BYTE checksum;
   BYTE b, chk = 99;
   BYTE *rcv = recvdata;

   *plBytesReceived = 0; // Clear number of bytes recvd

   errors = packets_rcvd = 0;
   firstchar = 0;

   // send out that we are ready to receive
   delay(2000);
   _nBytes = 0; SendChar(NAK); FlushPacket();
   // now wait for data packet and check the first byte for either end of
   // transmission or too many errors
   while (1)
   {
      errorflag = FALSE;
      while (1)
      {  // get sync char
         nRetVal = GetCharTimeout((char*)&firstchar, 10L);
         dprintf("Got first char %d", firstchar);
         if (firstchar == SOH || firstchar == EOT)
         {
            break;
         }
         else
         {
            errors++;  // add to errors if bad char or timeout
            Syslog::write("errors = %d\n", errors);
            if (errors >= ERRORMAX)
            {
               Syslog::write("LcXmodem::receive: Error waiting for first byte");
               return XMODEM_ERROR;
            }
         }
      }
      if (firstchar == EOT)
         break;  // sender is finished - exit main loop and wrap it up
      else if (firstchar == SOH)   // else receive a packet
      {
         Syslog::write("\npacket coming\n");
         // read packet number (starts at 1)
         checksum = 0;
         GetCharTimeout((char*)&this_packet, 10L);
         checksum += (BYTE)this_packet;
         GetCharTimeout((char*)&packethi, 10L);
         checksum += (BYTE)packethi;
         GetCharTimeout((char*)&packetlo, 10L);
         checksum += (BYTE)packetlo;
         packetsize = 256*(int)packethi + (int)packetlo;
         Syslog::write("receiving packet, size = %d\n", packetsize);
         for (i=0; i<packetsize; i++)
         {
            GetCharTimeout((char*)&b, 10L);
	    //if (b == 0xff) {
            //  Syslog::write(" LcXmodem::eating \"extra\" FF");
	    //  GetCharTimeout((char*)&b, 10L);
	    //}
            rcv[i] = b;
	    //Syslog::write("b[%d] is %d\n", i, b);
            checksum += b;
         }
         // finally get transmitted checksum
         GetCharTimeout((char*)&chk, 10L);
         // see if our checksum == transmitted checksum
         if (checksum == chk)
         {
            // yes, checksum OK - next check sequence
            if (this_packet == (BYTE)(packets_rcvd+1)%256) // rolls over at 255
            {
               // yes, packet order OK, so save the data and send ACK
               // but first nReset data location and check num bytes
               *plBytesReceived += packetsize;
               Syslog::write("Got packet %d\n", this_packet);
               errors = 0;
               packets_rcvd++;
               rcv += packetsize;
               _nBytes = 0; SendChar(ACK); FlushPacket();
               dprintf("put byte ACK after got packet\n");
            }
            else  // bad packet number
            {
               if (this_packet == (packets_rcvd & 0xff))
               {
                  // same packet as last time - do not use the data, but send
                  // ACK so sender will go on to next packet in normal way.
                  // add to error counter, but do not set error flag
                  errors++;
                  _nBytes = 0; SendChar(ACK); FlushPacket();
                  Syslog::write("received duplicate packet # %d\n", this_packet);
               }
               else
               {
                  errorflag = TRUE;
                  nRetVal= 0x02;
                  Syslog::write("LcXmodem::receive: Error - bad packet number");
                  dprintf("sync error - bad packet number\n");
               }
            }
         }
         else  // bad checksum
         {
            errorflag = TRUE;
            nRetVal= 0x03;
            Syslog::write("LcXmodem::receive: Bad checksum %02x\n", checksum);
            dprintf("checksum error, got <%02x>; expected <%02x>\n",
                   checksum, chk);
         }
      }
      if (errorflag)
      {
         errors++;
         Syslog::write("error # %d\n", errors);
	 // tell sender we did not get this packet
         _nBytes = 0; SendChar(NAK); FlushPacket();

         if (errors >= ERRORMAX)
         {
            *plBytesReceived = 0L;  // report that we got no data
            return XMODEM_ERROR;
         }
      }
   }  // end while loop on EOT and errors

   // only way to get here is if first char was EOT
   _nBytes = 0;
   SendChar(ACK);   // response OK to EOT
   FlushPacket();
   return OKAY;   // success
}
///////////////////////////////////////////////////////////

void LcXmodem::SendByte(BYTE b)
{
  _packetData[_nBytes++] = b;
}

void LcXmodem::SendChar(char b)
{
  _packetData[_nBytes++] = (BYTE)b;
}

void LcXmodem::FlushPacket()
{
  Boolean debug = False;

  dprintf("writing %d byte packet\n", _nBytes);
  _device->write((const char *)_packetData, _nBytes);
  _nBytes = 0;
}

int LcXmodem::GetCharTimeout(char *b, time_t timeout_sec)
{
  int nbytes;
  try {
    *b = 0;
    nbytes = _device->read(b, 1, timeout_sec*1000);
//    Syslog::write("LcXmodem::got %d bytes", nbytes);
  }
  catch (SerialDevice::TimedOut) {
    Syslog::write("LcXmodem::GetCharTimeout timeout");
  }
  return nbytes;
}
