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

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

//////////////////////////////////////////////////////////////////////////
// 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)
{
   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;

   printf("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, 4L);
      if (b == NAK)   // receiver is ready
      {
         break;
      }
      else
      {
         if (++errors > ERRORMAX)
         {
            printf("Receiver not sending NAKs\n");
            return XMODEM_ERROR;
         }
      }
   }

   printf("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);
         printf("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);
         ret = GetCharTimeout(&b, 5L);
         if (ret != OKAY)
            printf("Timeout getting ACK/NAK\n");
         else
            printf("packet %d\n", packet_number);
         if (b == ACK)
            break;
         if (++attempts >= ERRORMAX)
         {
            printf("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);
      GetCharTimeout(&b, 1L);
      if (b == ACK)   // receiver acknowledged
         break;       // and quit
      attempts++;
      if (attempts >= RETRYMAX)
      {
         printf("no acknowledgment of end of file\n");
         return XMODEM_ERROR;
      }
   }

   return OKAY;
}

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

//////////////////////////////////////////////////////////////////
int LcXmodem::receiveXmodem(BYTE *recvdata, long *plBytesReceived)
{
   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;

   errors = packets_rcvd = 0;
   firstchar = 0;

   // send out that we are ready to receive
   SendChar(NAK);
   // 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);
         //printf("Got first char %d", firstchar);
         if (firstchar == SOH || firstchar == EOT)
         {
            break;
         }
         else
         {
            errors++;  // add to errors if bad char or timeout
            printf("errors = %d\n", errors);
            if (errors >= ERRORMAX)
            {
               printf("Error waiting for first byte\n");
               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
      {
         printf("\npacket coming\n");
         // read packet number (starts at 1)
         checksum = 0;
         GetCharTimeout((char*)&this_packet, 1L);
         checksum += (BYTE)this_packet;
         GetCharTimeout((char*)&packethi, 1L);
         checksum += (BYTE)packethi;
         GetCharTimeout((char*)&packetlo, 1L);
         checksum += (BYTE)packetlo;
         packetsize = 256*(int)packethi + (int)packetlo;
         printf("receiving packet, size = %d\n", packetsize);
         for (i=0; i<packetsize; i++)
         {
            GetCharTimeout((char*)&b, 1L);
            rcv[i] = b;
            checksum += b;
         }
         // finally get transmitted checksum
         GetCharTimeout((char*)&chk, 1L);
         // 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;
               printf("Got packet %d\n", this_packet);
               errors = 0;
               packets_rcvd++;
               rcv += packetsize;
               SendChar(ACK);
               //printf("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++;
                  SendChar(ACK);
                  printf("received duplicate packet # %d\n", this_packet);
               }
               else
               {
                  errorflag = TRUE;
                  nRetVal= 0x02;
                  printf("sync error - bad packet number\n");
               }
            }
         }
         else  // bad checksum
         {
            errorflag = TRUE;
            nRetVal= 0x03;
            printf("checksum error, got <%02x>; expected <%02x>\n",
                   checksum, chk);
         }
      }
      if (errorflag)
      {
         errors++;
         printf("error # %d\n", errors);
         SendChar(NAK);  // tell sender we did not get this packet
         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
   SendChar(ACK);   // response OK to EOT
   return OKAY;   // success
}
///////////////////////////////////////////////////////////

void LcXmodem::SendByte(BYTE b)
{
  _device->write((char*)&b, 1);
}

void LcXmodem::SendChar(char b)
{
  _device->write(&b, 1);
}

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