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

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

/**
 * @class AQR
 * 
 * Class to used for the ACK-NACK sequence
 */
class AQR {
   static final int AQ_DURATION_DEFAULT   = 240;      /**< Default duration for the sequence */
   static final int AQR_BUFFER_SIZE       = 256;      /**< AQR buffer size */
   static final String AQR_BEGIN          = "PSTART"; /**< Begin pattern of the response */
   static final String AQR_END            = "PSTOP";  /**< End pattern of the response */

   static final int AQR_SEQUENCE_BEGIN    = 31;    /**< The response sequence string position */
   static final int AQR_SEQUENCE_END      = 47;    /**< The end of response sequence string position */
   static final int AQR_CRC_BEGIN         = 49;    /**< The response crc string position */
   static final int AQR_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
    */
   AQR (Com com, Log log, byte[] ack, byte[] nack, int duration) {
      com_        = com;
      log_        = log;
      duration_   = duration;
      transaction_= new Transaction(null, new byte[AQR_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 AQR 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, delay =0;
      String   line;
      int      bads = 0;
      boolean  good = true;
      boolean  prev = true;

      start = System.currentTimeMillis();
      do {
         // Check if the previous transaction was error-free
         if (good)
            transaction_ = com_.request(transaction_, ack_, AQR_BEGIN.getBytes(), AQR_END.getBytes(), false);
         else 
            transaction_ = com_.request(transaction_, nack_, AQR_BEGIN.getBytes(), AQR_END.getBytes(), false);
         good = (_crc_check(transaction_.response)) ? true : false;  // crc check the response
         bads = (good) ? 0 : bads+1;   // count the error strike

         // delay calculation
         if (prev && !good)   mark = transaction_.departure;
         if (!prev && good)   delay = transaction_.arrival - mark;
         else                 delay = 0;
         prev = good;

         line = new String(transaction_.code) + ": "
              + new String(transaction_.getResponse())
              + " Er: " + bads
              + " Tr= " + (transaction_.arrival - transaction_.departure) + " [msec]";
         if (delay != 0)  line += "  Tt= " + delay + " [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, AQR_SEQUENCE_BEGIN, AQR_SEQUENCE_END);
      int crc    = Integer.valueOf(
                   new String(Arrays.copyOfRange(data, AQR_CRC_BEGIN, AQR_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;
   }
   /**@} */
}
