/*
 * $Id: CalcTerminal.java,v 1.1 2004/06/09 13:37:07 martijno Exp $
 */

package terminal;

import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import javax.swing.*;

import opencard.core.event.*;
import opencard.core.service.*;
import opencard.core.terminal.*;
import opencard.opt.terminal.*;
import opencard.opt.util.*;

/**
 * Sample OCF terminal for the Calculator applet.
 *
 * @author Martijn Oostdijk (martijno@cs.kun.nl)
 *
 * @version $Revision: 1.1 $
 */
public class CalcTerminal extends JPanel implements ActionListener
{
   static final String TITLE = "Calculator";
   static final Font FONT = new Font("Monospaced",Font.BOLD,24);
   static final Dimension PREFERRED_SIZE = new Dimension(300,300);

   static final int DISPLAY_WIDTH = 20;
   static final String MSG_ERROR = "    -- error --     ";
   static final String MSG_DISABLED = " -- insert card --  ";
   static final String MSG_INVALID = " -- invalid card -- ";

   static final byte[] CALC_APPLET_AID = {0x3B,0x29,0x63,0x61,0x6C,0x63,0x01};
   static final byte CLA_ISO7861 = (byte)0x00;
   static final byte INS_SELECT = (byte)0xA4;
   static final short SW_NO_ERROR = (short)0x9000;
   static final short SW_APPLET_SELECT_FAILED = (short)0x6999;
   static final short SW_FILE_NOT_FOUND = (short)0x6A82;
   static final byte CLA_CALC = (byte)0xCC;

   JTextField display;
   JPanel keypad;

   CardTerminal terminal;
   PassThruCardService service;

   public CalcTerminal(JFrame parent) {
      buildGUI(parent);
      setEnabled(false);
      (new CardThread()).start();
   }

   void buildGUI(JFrame parent) {
      setLayout(new BorderLayout());
      display = new JTextField(DISPLAY_WIDTH);
      display.setHorizontalAlignment(JTextField.RIGHT);
      display.setEditable(false);
      display.setFont(FONT);
      display.setBackground(Color.darkGray);
      display.setForeground(Color.green);
      add(display,BorderLayout.NORTH);
      keypad = new JPanel(new GridLayout(5,5));
      key(null); key(null); key(null); key(null); key("C");
      key("7"); key("8"); key("9"); key(":"); key("ST");
      key("4"); key("5"); key("6"); key("x"); key("RM");
      key("1"); key("2"); key("3"); key("-"); key("M+");
      key("0"); key(null); key(null); key("+"); key("=");
      add(keypad,BorderLayout.CENTER);
      parent.addWindowListener(new CloseEventListener());
   }

   void key(String txt) {
      if (txt == null) {
         keypad.add(new JLabel());
      } else {
         JButton button = new JButton(txt);
         button.addActionListener(this);
         keypad.add(button);
      }
   }

   String getText() {
      return display.getText();
   }

   void setText(String txt) {
      display.setText(txt);
   }

   void setText(int n) {
      setText(Integer.toString(n));
   }

   void setMemory(boolean b) {
      String txt = getText();
      int l = txt.length();
      if (l < DISPLAY_WIDTH) {
         for (int i = 0; i < (DISPLAY_WIDTH - l); i++) {
            txt = " " + txt;
         }
         txt = (b ? "m" : " ") + txt;
         setText(txt);
      }
   }

   void setText(ResponseAPDU rapdu) {
      byte[] data = rapdu.data();
      if ((short)rapdu.sw() != SW_NO_ERROR || data == null || data.length < 5) {
         setText(MSG_ERROR);
      } else {
         setText((short)(((data[3] & 0x000000FF) << 8) | (data[4] & 0x000000FF)));
         setMemory(data[0] == 0x01);
      }
   }

   public void setEnabled(boolean b) {
      super.setEnabled(b);
      if (b) {
         setText(0);
      } else {
         setText(MSG_DISABLED);
      }
      Component[] keys = keypad.getComponents();
      for (int i = 0; i < keys.length; i++) {
         keys[i].setEnabled(b);
      }
   }

   class CardThread extends Thread implements CTListener
   {
      public void run() {
         try {
            SmartCard.start();
            CardTerminalRegistry terminalRegistry = CardTerminalRegistry.getRegistry();
            Enumeration terminals = terminalRegistry.getCardTerminals();
            terminal = (CardTerminal)terminals.nextElement();
            EventGenerator eventGenerator = EventGenerator.getGenerator();
            eventGenerator.addCTListener(this);
            cardSession(CardRequest.ANYCARD);
            while (true) {
               cardSession(CardRequest.NEWCARD);
            }
         } catch (Exception e) {
           setEnabled(false);
           setText(MSG_ERROR);
           System.out.println("ERROR: " + e.getMessage());
           e.printStackTrace();
         }
      }

      void cardSession(int requestType) {
         try {
            CardRequest request =
               new CardRequest(requestType,terminal,PassThruCardService.class);
            SmartCard card = SmartCard.waitForCard(request);
            service =
               (PassThruCardService)card.getCardService(PassThruCardService.class,true);
            short sw = selectApplet(CALC_APPLET_AID);
            switch(sw) {
               case SW_NO_ERROR:
                  setEnabled(true);
                  setText(sendKey((byte)'='));
                  break;
               case SW_APPLET_SELECT_FAILED:
                  setText(MSG_INVALID);
                  break;
               case SW_FILE_NOT_FOUND:
                  setText(MSG_INVALID);
                  break;
               default:
                  System.out.println("WARNING: select applet: "
                                     + Integer.toHexString(sw & 0x0000FFFF));
                  setText(MSG_INVALID);
            }
         } catch (Exception e) {
            setEnabled(false);
            setText(MSG_ERROR);
            System.out.println("ERROR: " + e.getMessage());
            e.printStackTrace();
         }
      }

      public void cardInserted(CardTerminalEvent cte) {
      }

      public void cardRemoved(CardTerminalEvent cte) {
         setEnabled(false);
      }
   }

   public void actionPerformed(ActionEvent ae) {
      try {
         Object src = ae.getSource();
         if (src instanceof JButton) {
            byte ins = (byte)((JButton)src).getText().charAt(0);
            setText(sendKey(ins));
         }
      } catch (Exception e) {
         System.out.println(MSG_ERROR);
      }
   }

   short selectApplet(byte[] aid) throws CardTerminalException {
      byte cla = CLA_ISO7861;
      byte ins = INS_SELECT;
      byte p1 = (byte)0x04;
      byte p2 = (byte)0x00;
      int le = 0; 
      byte[] data = aid;
      CommandAPDU capdu = new ISOCommandAPDU(cla,ins,p1,p2,data,le);
      ResponseAPDU rapdu = sendCommandAPDU(capdu);
      return (short)rapdu.sw();
   }

   ResponseAPDU sendKey(byte ins) throws CardTerminalException {
      CommandAPDU capdu = new ISOCommandAPDU(CLA_CALC,ins,(byte)0,(byte)0,5);
      return sendCommandAPDU(capdu);
   }

   ResponseAPDU sendCommandAPDU(CommandAPDU capdu) throws CardTerminalException {
      System.out.println(capdu.toString());
      ResponseAPDU rapdu = service.sendCommandAPDU(capdu);
      System.out.println(rapdu.toString());
      return rapdu;
   }

  class CloseEventListener extends WindowAdapter
  {
     public void windowClosing(WindowEvent we) {
        try {
            SmartCard.shutdown();
         } catch (Exception e) {
         } finally {
            System.exit(0);
         }
      }
   }

   public Dimension getPreferredSize() {
      return PREFERRED_SIZE;
   }

   public static void main(String[] arg) {
      JFrame frame = new JFrame(TITLE);
      Container c = frame.getContentPane();
      CalcTerminal panel = new CalcTerminal(frame);
      c.add(panel);
      frame.setResizable(false);
      frame.pack();
      frame.setVisible(true);
   }
}

