/**
 * @file ARQ.java
 * 
 * @author Christos Choutouridis AEM:8997
 * @email  cchoutou@ece.auth.gr
 */
package net.hoo2.auth.vmodem;

/** @name imports */
/** @{ */
import java.util.*;
/** @} */

/**
 * @class ARQ
 * 
 * Class to used for the ACK-NACK sequence
 */
class ARQ {
   static final int ARQ_DURATION_DEFAULT  = 240;      /** Default duration for the sequence */
   static final int ARQ_BUFFER_SIZE       = 256;      /** ARQ buffer size */
   static final String ARQ_BEGIN          = "PSTART"; /** Begin pattern of the response */
   static final String ARQ_END            = "PSTOP";  /** End pattern of the response */

   static final int ARQ_SEQUENCE_BEGIN    = 31;    /** The response sequence string position */
   static final int ARQ_SEQUENCE_END      = 47;    /** The end of response sequence string position */
   static final int ARQ_CRC_BEGIN         = 49;    /** The response crc string position */
   static final int ARQ_CRC_END           = 52;    /** The end of response crc string position */

   private Com          com_;             /** Reference to communication module */
   private Log          log_;             /** Reference to logging module */
   private Transaction  transaction_;     /** A transaction object to used as a buffer for all requests */
   private int          duration_;        /** The desired duration for the session */
   private byte[]       ack_;             /** The desired ACK code for the session */
   private byte[]       nack_;            /** The desired NACK code for the session */

   /**
    * Basic constructor
    * @param com        The Com module to use
    * @param log        The Log module to use
    * @param ack        The desired ACK code
    * @param nack       The desired NACK code
    * @param duration   The desired duration for the session
    */
   ARQ (Com com, Log log, byte[] ack, byte[] nack, int duration) {
      com_        = com;
      log_        = log;
      duration_   = duration;
      transaction_= new Transaction(null, new byte[ARQ_BUFFER_SIZE]);
      ack_        = ack;
      nack_       = nack;
   }
   
   /**
    * Functionality to drain the response buffer for the welcome message
    * from the server
    * @param ack     The ack code that we will use (for printing, not sending)
    * @param nack    The nack code that we will use (for printing, not sending)
    */
   void caption (byte[] ack, byte[] nack) {
      String line;

      line = "Running ARQ with: " + new String(ack) + "/" + new String(nack);
      log_.write(line, true);

      transaction_ = com_.request (transaction_, null, null, null, false);
      line = new String(transaction_.getResponse());
      log_.out(line);
   }

   /**
    * Main transaction loop. It send requests to server, get the response
    * and log the procedure while doing it.
    */
   void run () {
      long     start;
      long     now;
      long     mark =0, tr =0, ts =0, tt =0;
      String   line;
      int      errors = 0;
      boolean  good = true;

      start = System.currentTimeMillis();
      do {
         // Check if the previous transaction was error-free
         if (good)
            transaction_ = com_.request(transaction_, ack_, ARQ_BEGIN.getBytes(), ARQ_END.getBytes(), false);
         else 
            transaction_ = com_.request(transaction_, nack_, ARQ_BEGIN.getBytes(), ARQ_END.getBytes(), false);
         good = (_crc_check(transaction_.response)) ? true : false;  // crc check the response
         
         // time calculations
         tr = transaction_.arrival - transaction_.departure;
         tt = 0;
         if ((errors == 0) && good) {
            tt = ts = tr;
         }
         if ((errors == 0) && !good) {
            mark = transaction_.departure;
            ts = tr;
         }
         if ((errors != 0) && good) {
            tt = transaction_.arrival - mark;
            ts += tr;
         }
         if ((errors !=0) && !good) {
            ts += tr;
         }
         errors = (good) ? 0 : errors+1;   // update the error strike
         
         line = new String(transaction_.code) + ": "
              + new String(transaction_.getResponse())
              + " Er: " + errors
              + " Tr= " + tr + " [msec]";
         if (errors !=0)
            line += " Ts= 0 [msec] Tt= 0 [msec]";
         else
            line += " Ts= " + ts + " [msec]" + " Tt= " + tt + " [msec]";

         log_.write(line);
         now = System.currentTimeMillis();
      } while (now - start < duration_*1000);
   }

   /** @name private helper API */
   /**@{ */

   /**
    * A CRC check functionality
    * @param data    The data to check for validity
    * @return        The check result
    */
   private boolean _crc_check (byte[] data) {
      byte[] seq = Arrays.copyOfRange(data, ARQ_SEQUENCE_BEGIN, ARQ_SEQUENCE_END);
      int crc    = Integer.valueOf(
                      new String(Arrays.copyOfRange(data, ARQ_CRC_BEGIN, ARQ_CRC_END))
                   );
      return (crc == _crc(seq)) ? true : false;
   }

   /**
    * A CRC calculation function. This primitive CRC calculator
    * does not use any known CRC polynomial. It just uses XOR
    * @param data    Reference to buffer
    * @return        The CRC result
    */
   private int _crc (byte[] data) {
      int calc =0;
      for (int i=0 ; i<data.length ; ++i)
         calc ^= data[i];
      return calc;
   }
   /**@} */
}
