const int bufferLength = 1200;
const unsigned long recvTimeOut = 1000;  // 1 sec
const unsigned long restartTimer = 10000;  // 10 sec

byte BufferToSend[bufferLength];
int sendIndex;
int receiveIndex;
enum progState {INITIALIZING, TXRX, TXCOMPLETE, REPORTING, FINISHED};
enum completeStatus {PERFECT, RECVMISMATCH, RECVTIMEOUT};
enum recvStatus {NOPROBLEM, FAIL, RECVCOMPLETE};
enum sendStatus {SENDCOMPLETE, SENDNOTFINISHED};
int progState = INITIALIZING;
int completeStatus;
unsigned long txComplateStart;
unsigned long completeStartTime;
int restartTimerDisplayed;

void setup() {
  Serial.begin(74880);
  for (int i = 0; i < bufferLength; i++) {
    BufferToSend[i] = i % 256;
  }
}

void loop() {
  switch (progState) {
    case INITIALIZING:
      chkReceive();
      chkReceive();
      sendIndex = 0;
      receiveIndex = 0;
      Serial.println(F("Please wait..."));
      Serial.flush();
      while (Serial.available() > 0) { //empty receive buffer
        Serial.read();
      }
      progState = TXRX;
      break;
    case TXRX:
      if (chkSend() == SENDCOMPLETE) {
        txComplateStart = millis();
        progState = TXCOMPLETE;
      } else if (chkReceive() == FAIL) {
        completeStatus = RECVMISMATCH;
        progState = REPORTING;
      }
      break;
    case TXCOMPLETE:
      if (millis() - txComplateStart > recvTimeOut) {
        completeStatus = RECVTIMEOUT;
        progState = REPORTING;
      }
      if (chkReceive() == RECVCOMPLETE) {
        completeStatus = PERFECT;
        progState = REPORTING;
      }
      break;
    case REPORTING:
      reporting();
      progState = FINISHED;
      completeStartTime = millis();
      restartTimerDisplayed = 0;
      break;
    case FINISHED:
      int restartIn = (restartTimer - millis() + completeStartTime) / 1000 + 1;
      if (restartIn < 0) {
        progState = INITIALIZING;
      } else if (restartIn != restartTimerDisplayed) {
        Serial.print(F("Restart in "));
        Serial.print(restartIn);
        if (restartIn > 1) {
          Serial.println(F(" secs."));
        } else {
          Serial.println(F(" sec."));
        }
        restartTimerDisplayed = restartIn;
      }
      break;
  } // end switch (progState) {
}

void reporting() {
  Serial.flush();
  Serial.println(F(""));
  Serial.println(F(""));
  Serial.print(F("Transmitted "));
  Serial.print(sendIndex);
  Serial.println(F(" byte(s)."));
  switch (completeStatus) {
    case PERFECT:
      Serial.println(F("Perfect! No Errors."));
      break;
    case RECVMISMATCH:
      Serial.print(F("Error in receiving in byte number "));
      Serial.println(receiveIndex);
      break;
    case RECVTIMEOUT:
      Serial.print(F("Incomplete data received. Only received "));
      Serial.print(receiveIndex);
      Serial.print(F(" bytes. Expected "));
      Serial.print(bufferLength);
      Serial.println(F(" bytes."));
      break;
  }
  Serial.println(F(""));
}


int chkReceive() {
  if (receiveIndex == bufferLength)
    return RECVCOMPLETE;
  if (Serial.available() > 0) {
    byte incomingByte = Serial.read();
    if (incomingByte != BufferToSend[receiveIndex]) {
      Serial.print(F("Error byte received 0x"));
      Serial.println(incomingByte, HEX);
      Serial.print(F("Expected byte 0x"));
      Serial.println(BufferToSend[receiveIndex], HEX);
      return FAIL;
    } else {
      receiveIndex++;
      return NOPROBLEM;
    }
  }
  return NOPROBLEM;
}

bool chkSend() {
  if (sendIndex < bufferLength) {
    if (Serial.availableForWrite() > 0) {
      Serial.write(BufferToSend[sendIndex]);
      sendIndex++;
    }
    return SENDNOTFINISHED;
  } else {
    return SENDCOMPLETE;
  }
}