context: /*******************************************************************************
  
      Copyright 2009 Whole Foods Co-op
  
      This file is part of IT CORE.
  
      IT CORE is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 2 of the License, or
      (at your option) any later version.
  
      IT CORE is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      in the file license.txt along with IT CORE; if not, write to the Free Software
      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  *********************************************************************************/
  
  /*************************************************************
   * SerialPortHandler
   *     Abstract class to manage a serial port in a separate
   * thread. Allows top-level app to interact with multiple, 
   * different serial devices through one class interface.
   * 
   * Provides Stop() and SetParent(DelegateBrowserForm) functions.
   *
   * Subclasses must implement Read() and PageLoaded(Uri).
   * Read() is the main polling loop, if reading serial data is
   * required. PageLoaded(Uri) is called on every WebBrowser
   * load event and provides the Url that was just loaded.
   *
  *************************************************************/
  using System;
  using System.IO;
  using System.IO.Ports;
  using System.Threading;
  using CustomForms;
  
  using USBLayer;
  
  namespace SPH {
  
  public class SPH_SignAndPay_USB : SerialPortHandler {
  
      private static String MAGELLAN_OUTPUT_DIR = "ss-output/";
  
      private USBWrapper usb_port;
      private bool read_continues;
      private byte[] long_buffer;
      private int long_length;
      private int long_pos;
      private FileStream usb_fs;
      private int usb_report_size;
  
      /** change screen states automatically
          if false, screen only changes in response
          to POS commands
      */
      private bool auto_state_change = true;
  
      private const int LCD_X_RES = 320;
      private const int LCD_Y_RES = 240;
  
      private const int STATE_START_TRANSACTION = 1;
      private const int STATE_SELECT_CARD_TYPE = 2;
      private const int STATE_ENTER_PIN = 3;
      private const int STATE_WAIT_FOR_CASHIER = 4;
      private const int STATE_SELECT_EBT_TYPE = 5;
      private const int STATE_SELECT_CASHBACK = 6;
      private const int STATE_GET_SIGNATURE = 7;
      private const int STATE_MANUAL_PAN = 11;
      private const int STATE_MANUAL_EXP = 12;
      private const int STATE_MANUAL_CVV = 13;
  
      private const int BUTTON_CREDIT = 5;
      private const int BUTTON_DEBIT = 6;
      private const int BUTTON_EBT = 7;
      private const int BUTTON_GIFT = 8;
      private const int BUTTON_EBT_FOOD = 9;
      private const int BUTTON_EBT_CASH = 10;
      private const int BUTTON_000  = 0;
      private const int BUTTON_500  = 5;
      private const int BUTTON_1000 = 10;
      private const int BUTTON_2000 = 20;
      private const int BUTTON_3000 = 30;
      private const int BUTTON_4000 = 40;
      private const int BUTTON_SIG_ACCEPT = 1;
      private const int BUTTON_SIG_RESET = 2;
      private const int BUTTON_HARDWARE_BUTTON = 0xff;
  
      private const int DEFAULT_WAIT_TIMEOUT = 1000;
      private string last_message = "";
  
      private int current_state;
      private int ack_counter;
  
      private string usb_devicefile;
      private System.Object usb_lock;
  
      public SPH_SignAndPay_USB(string p) : base(p){ 
          read_continues = false;
          long_length = 0;
          long_pos = 0;
          ack_counter = 0;
          usb_fs = null;
          usb_lock = new System.Object();
          
          #if MONO
          usb_devicefile = p;
          #else
          int vid = 0xacd;
          int pid = 0x2310;
          usb_devicefile = string.Format("{0}&{1}",vid,pid);
          #endif
      }
  
      private void GetHandle(){
          usb_fs = null;
          #if MONO
          usb_port = new USBWrapper_Posix();
          usb_report_size = 64;
          #else
          usb_port = new USBWrapper_Win32();
          usb_report_size = 65;
          #endif
          while(usb_fs == null){
              usb_fs = usb_port.GetUSBHandle(usb_devicefile,usb_report_size);
              if (usb_fs == null){
                  if (this.verbose_mode > 0)
                      System.Console.WriteLine("No device");
                  System.Threading.Thread.Sleep(5000);
              }
              else { 
                  if (this.verbose_mode > 0)
                      System.Console.WriteLine("USB device found");
              }
              
          }
  
      }
  
      public override void Read(){ 
          GetHandle();
          SendReport(BuildCommand(LcdSetBacklightTimeout(0)));
          SendReport(BuildCommand(EnableAudio()));
          SetStateStart();
          #if MONO
          MonoRead();
          #else
          ReRead();
          #endif
      }
  
      private void RebootTerminal(){
          try {
              SendReport(BuildCommand(ResetDevice()));
          }
          catch (Exception ex){
              if (this.verbose_mode > 0){
                  System.Console.WriteLine("Reboot error:");
                  System.Console.WriteLine(ex);
              }
          }
          try {
              usb_fs.Dispose();
          }
          catch (Exception ex){
              if (this.verbose_mode > 0){
                  System.Console.WriteLine("Dispose error:");
                  System.Console.WriteLine(ex);
              }
          }
          try {
              usb_port.CloseUSBHandle();
          }
          catch (Exception ex){
              if (this.verbose_mode > 0){
                  System.Console.WriteLine("Dispose error:");
                  System.Console.WriteLine(ex);
              }
          }
          System.Threading.Thread.Sleep(DEFAULT_WAIT_TIMEOUT);
          GetHandle();
          SetStateStart();
          #if MONO
          //MonoRead();
          #else
          ReRead();
          #endif
      }
  
      private void SetStateStart(){
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdClearSignature()));
          SendReport(BuildCommand(LcdSetClipArea(0,0,1,1)));
          SendReport(BuildCommand(PinpadCancelGetPIN()));
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdTextFont(3,12,14)));
          SendReport(BuildCommand(LcdTextColor(0,0,0)));
          SendReport(BuildCommand(LcdTextBackgroundColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdTextBackgroundMode(false)));
          SendReport(BuildCommand(LcdDrawText("Swipe Card",75,100)));
  
          current_state = STATE_START_TRANSACTION;
      }
  
      private void SetStateReStart(){
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(PinpadCancelGetPIN()));
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdTextFont(3,12,14)));
          SendReport(BuildCommand(LcdTextColor(0,0,0)));
          SendReport(BuildCommand(LcdTextBackgroundColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdTextBackgroundMode(false)));
          SendReport(BuildCommand(LcdDrawText("Swipe Card Again",55,100)));
  
          current_state = STATE_START_TRANSACTION;
      }
  
      private void SetStateCardType(){
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdClearSignature()));
          SendReport(BuildCommand(LcdSetClipArea(0,0,1,1)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_CREDIT,"Credit",5,5,95,95)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_DEBIT,"Debit",224,5,314,95)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_EBT,"EBT",5,144,95,234)));
          //SendReport(BuildCommand(LcdCreateButton(BUTTON_GIFT,"Gift",224,144,314,234)));
          SendReport(BuildCommand(LcdStartCapture(4)));
  
          current_state = STATE_SELECT_CARD_TYPE;
      }
  
      private void SetStateEbtType(){
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdClearSignature()));
          SendReport(BuildCommand(LcdSetClipArea(0,0,1,1)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_EBT_FOOD,"Food Side",5,5,115,95)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_EBT_CASH,"Cash Side",204,5,314,95)));
          SendReport(BuildCommand(LcdStartCapture(4)));
  
          current_state = STATE_SELECT_EBT_TYPE;
      }
  
      private void SetStateCashBack(){
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdClearSignature()));
          SendReport(BuildCommand(LcdSetClipArea(0,0,1,1)));
          SendReport(BuildCommand(LcdTextFont(3,12,14)));
          SendReport(BuildCommand(LcdDrawText("Select Cash Back",60,5)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_000,"None",5,40,95,130)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_500,"5.00",113,40,208,130)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_1000,"10.00",224,40,314,130)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_2000,"20.00",5,144,95,234)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_3000,"30.00",113,144,208,234)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_4000,"40.00",224,144,314,234)));
          SendReport(BuildCommand(LcdStartCapture(4)));
  
          current_state = STATE_SELECT_CASHBACK;
      }
  
      private void SetStateGetPin(){
          SendReport(BuildCommand(LcdStopCapture()));
          ack_counter = 0;
  
          SendReport(BuildCommand(PinpadGetPIN()));
          current_state = STATE_ENTER_PIN;
      }
  
      private void SetStateWaitForCashier(){
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdTextFont(3,12,14)));
          SendReport(BuildCommand(LcdTextColor(0,0,0)));
          SendReport(BuildCommand(LcdTextBackgroundColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdTextBackgroundMode(false)));
          SendReport(BuildCommand(LcdDrawText("wait for cashier",75,100)));
  
          current_state = STATE_WAIT_FOR_CASHIER;
      }
  
      private void SetStateApproved(){
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdTextFont(3,12,14)));
          SendReport(BuildCommand(LcdTextColor(0,0,0)));
          SendReport(BuildCommand(LcdTextBackgroundColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdTextBackgroundMode(false)));
          SendReport(BuildCommand(LcdDrawText("approved",75,100)));
      }
  
      private void SetStateGetSignature(){
          SendReport(BuildCommand(LcdStopCapture()));
          SendReport(BuildCommand(LcdClearSignature()));
          SendReport(BuildCommand(LcdFillColor(0xff,0xff,0xff)));
          SendReport(BuildCommand(LcdFillRectangle(0,0,LCD_X_RES-1,LCD_Y_RES-1)));
  
          SendReport(BuildCommand(LcdSetClipArea(5,5,310,140,true,new byte[]{0,0,0})));
          SendReport(BuildCommand(LcdDrawText("please sign",105,150)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_SIG_ACCEPT,"Done",5,190,115,215)));
          SendReport(BuildCommand(LcdCreateButton(BUTTON_SIG_RESET,"Clear",204,190,314,215)));
  
          SendReport(BuildCommand(LcdStartCapture(5)));
  
          current_state = STATE_GET_SIGNATURE;
      }
  
      private void SetStateGetManualPan(){
          SendReport(BuildCommand(LcdStopCapture()));
          ack_counter = 0;
          SendReport(BuildCommand(ManualEntryPAN()));
          current_state = STATE_MANUAL_PAN;
      }
  
      private void SetStateGetManualExp(){
          ack_counter = 0;
          SendReport(BuildCommand(ManualEntryExp()));
          current_state = STATE_MANUAL_EXP;
      }
  
      private void SetStateGetManualCVV(){
          ack_counter = 0;
          SendReport(BuildCommand(ManualEntryCVV()));
          current_state = STATE_MANUAL_CVV;
      }
  
      private void HandleReadData(byte[] input){
          int msg_sum = 0;
          if (usb_report_size == 64){
              byte[] temp_in = new byte[65];
              temp_in[0] = 0;
              for (int i=0; i < input.Length; i++){
                  temp_in[i+1] = input[i];
                  msg_sum += input[i];
              }
              input = temp_in;
          }
  
          /* Data received, as bytes
          */
          if (this.verbose_mode > 1){
              System.Console.WriteLine("");
              System.Console.WriteLine("IN BYTES:");
              for(int i=0;i<input.Length;i++){
                  if (i>0 && i %16==0) System.Console.WriteLine("");
                  System.Console.Write("{0:x} ",input[i]);
              }
              System.Console.WriteLine("");
              System.Console.WriteLine("");
          }
  
          int report_length = input[1] & (0x80-1);
          /*
           * Bit 7 turned on means a multi-report message
           */
          if ( (input[1] & 0x80) != 0){
              read_continues = true;
          }
  
          byte[] data = null;
          if (report_length > 3 && (long_pos > 0 || input[2] == 0x02)){ // protcol messages
              int data_length = input[3] + (input[4] << 8);
  
              int d_start = 5;
              if (input[d_start] == 0x6 && data_length > 1){ 
                  d_start++; // ACK byte
                  data_length--;
              }
  
              /*
               * New multi-report message; init class members
               */
              if (read_continues && long_length == 0){
                  long_length = data_length;
                  long_pos = 0;
                  long_buffer = new byte[data_length];
                  if (data_length > report_length){
                      data_length = report_length-3;
                      // re-skip the ACK byte
                      if (d_start == 6) data_length--;
                  }
              }
              else if (read_continues){
                  // subsequent messages start immediately after
                  // report ID & length fields
                  d_start = 2;
              }
  
              if (data_length > report_length) data_length = report_length;
  
              data = new byte[data_length];
              for (int i=0; i<data_length && i+d_start<report_length+2;i++)
                  data[i] = input[i+d_start];
  
              /**
               * Append data from multi-report messages to the
               * class member byte buffer
               */
              if (read_continues){
                  // last message will contain checksum bytes and
                  // End Tx byte, so don't copy entire data array
                  int d_len = ((input[1]&0x80)!=0) ? data.Length : data.Length-3;
                  for(int i=0;i<d_len;i++){
                      long_buffer[long_pos++] = data[i];
                  }
              }
  
              if (this.verbose_mode > 1){
                  System.Console.Write("Received: ");
                  foreach(byte b in data)
                      System.Console.Write((char)b);
                  System.Console.WriteLine("");
              }
          }
          else if (report_length > 3){ // non-protcol messages
              data = new byte[report_length];
              for(int i=0; i<report_length && i+2<input.Length; i++){
                  data[i] = input[i+2];
              }
          }
  
          if ( (input[1] & 0x80) == 0){
              if (long_buffer != null){
                  if (this.verbose_mode > 0){
                      System.Console.Write("Big Msg: ");
                      foreach(byte b in long_buffer)
                          System.Console.Write((char)b);
                      System.Console.WriteLine("");
                  }
  
                  HandleDeviceMessage(long_buffer);
              }
              else {
                  HandleDeviceMessage(data);
              }
              read_continues = false;
              long_length = 0;
              long_pos = 0;
              long_buffer = null;
          }
      }
  
      /**
        Proper async version. Call ReRead at the end to
        start another async read.
      */
      private void ReadCallback(IAsyncResult iar){
          byte[] input = (byte[])iar.AsyncState;
          try {
              usb_fs.EndRead(iar);
              HandleReadData(input);        
          }
          catch (Exception ex){
              if (this.verbose_mode > 0)
                  System.Console.WriteLine(ex);
          }
  
          ReRead();
      }
  
      /**
        Synchronous version. Do not automatically start
        another read. The calling method will handle that.
      
        The wait on excpetion is important. Exceptions
        are generally the result of a write that occurs
        during a blocking read. Waiting a second lets any
        subsequent writes complete without more blocking.
      */
      private void MonoReadCallback(IAsyncResult iar){
          byte[] input = (byte[])iar.AsyncState;
          usb_fs.EndRead(iar);
          HandleReadData(input);        
          /* Revision: 7May13 - use locks instead
          try {
              usb_fs.EndRead(iar);
              HandleReadData(input);        
          }
          catch (Exception ex){
              if (this.verbose_mode > 0)
                  System.Console.WriteLine(ex);
              System.Threading.Thread.Sleep(DEFAULT_WAIT_TIMEOUT);
          }
          */
      }
  
      private void HandleDeviceMessage(byte[] msg){
          if (this.verbose_mode > 0)
              System.Console.Write("DMSG: {0}: ",current_state);
  
          //if (msg == null) return;
  
          if (this.verbose_mode > 0){
              foreach(byte b in msg)
                  System.Console.Write("{0:x} ",b);
              System.Console.WriteLine();
          }
          switch(current_state){
          case STATE_SELECT_CARD_TYPE:
              if (msg.Length == 4 && msg[0] == 0x7a){
                  SendReport(BuildCommand(DoBeep()));
                  if (msg[1] == BUTTON_CREDIT){
                      PushOutput("TERM:Credit");
                      if (auto_state_change)
                          SetStateWaitForCashier();
                  }
                  else if (msg[1] == BUTTON_DEBIT){
                      PushOutput("TERM:Debit");
                      if (auto_state_change)
                          SetStateCashBack();
                  }
                  else if (msg[1] == BUTTON_EBT){
                      // purposely autochanged. no message to pos
                      // until ebt sub-selection is made
                      SetStateEbtType();
                  }
                  else if (msg[1] == BUTTON_GIFT){
                      PushOutput("TERM:Gift");
                      if (auto_state_change)
                          SetStateWaitForCashier();    
                  }
                  else if (msg[1] == BUTTON_HARDWARE_BUTTON && msg[3] == 0x43){
                      SetStateStart();
                      PushOutput("TERMCLEARALL");
                  }
              }
              break;
          case STATE_SELECT_EBT_TYPE:
              if (msg.Length == 4 && msg[0] == 0x7a){
                  SendReport(BuildCommand(DoBeep()));
                  if (msg[1] == BUTTON_EBT_FOOD){
                      PushOutput("TERM:EbtFood");
                      if (auto_state_change)
                          SetStateGetPin();
                  }
                  else if (msg[1] == BUTTON_EBT_CASH){
                      PushOutput("TERM:EbtCash");
                      if (auto_state_change)
                          SetStateCashBack();
                  }
                  else if (msg[1] == BUTTON_HARDWARE_BUTTON && msg[3] == 0x43){
                      SetStateStart();
                      PushOutput("TERMCLEARALL");
                  }
              }
              break;
          case STATE_SELECT_CASHBACK:
              if (msg.Length == 4 && msg[0] == 0x7a){
                  SendReport(BuildCommand(DoBeep()));
                  if (msg[1] == BUTTON_HARDWARE_BUTTON && msg[3] == 0x43){
                      SetStateStart();
                      PushOutput("TERMCLEARALL");
                  }
                  else if(msg[1] != 0x6){
                      // 0x6 might be a serial protocol ACK
                      // timing issue means we got here too soon
                      // and should wait for next input
      
                      // Pressed green or yellow button
                      // Proceed to PIN entry but don't
                      // request 0xFF as cash back
                      if (msg[1] != BUTTON_HARDWARE_BUTTON)
                          PushOutput("TERMCB:"+msg[1]);
                      if (auto_state_change)
                          SetStateGetPin();
                  }
              }
              break;
          case STATE_ENTER_PIN:
              if (msg.Length == 3 && msg[0] == 0x15){
                  SetStateStart();
                  PushOutput("TERMCLEARALL");
              }
              else if (msg.Length == 36){
                  string pinhex = "";
                  foreach(byte b in msg)
                      pinhex += ((char)b);
                  PushOutput("PINCACHE:"+pinhex);
                  if (auto_state_change)
                      SetStateWaitForCashier();
              }
              break;
          case STATE_GET_SIGNATURE:
              if (msg.Length == 4 && msg[0] == 0x7a){
                  //SendReport(BuildCommand(DoBeep()));
                  if (msg[1] == BUTTON_SIG_RESET){
                      SendReport(BuildCommand(LcdClearSignature()));
                  }
                  else if (msg[1] == BUTTON_SIG_ACCEPT){
                      SendReport(BuildCommand(LcdGetBitmapSig()));
                  }
              }
              else if (msg.Length > 1024){
                  BitmapOutput(msg);
                  SetStateStart();
              }
              break;
          case STATE_MANUAL_PAN:
              if (msg.Length == 1 && msg[0] == 0x6){
                  ack_counter++;
                  if (this.verbose_mode > 0)
                      System.Console.WriteLine(ack_counter);
                  if (ack_counter == 1)
                      SetStateGetManualExp();
              }
  
gt:             else if (msg.Length == 3 && msg[0] == 0x15){