import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import java.awt.Font;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;

import Acos.Acos3;
import AcsCommon.Chain3DES;
import AcsCommon.Helper;
import Pcsc.CardSelector;
import Pcsc.PcscReader;
import Pcsc.ReaderEvent;
import Pcsc.ReaderEvent.TransmitApduEventArg;

import javax.swing.JRadioButton;
import javax.smartcardio.CardException;
import javax.swing.JButton;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.border.EtchedBorder;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;

import javax.swing.JTextField;
import java.awt.event.ActionListener;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.awt.event.ActionEvent;
import javax.swing.ButtonGroup;

public class ACOS3MutualAuthentication extends JFrame implements ReaderEvent.TransmitApduHandler {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private JFrame frame;
	private JTextField textFieldCardKey;
	private JTextField textFieldTerminalKey;
	private JLabel lblTerminal;
	private JComboBox<String> comboBoxReader;
	private JRadioButton rdbtnDES;
	private JRadioButton rdbtn3DES;
	private JButton btnInitialize;
	private JButton btnConnect;
	private JButton buttonClear;
	private JButton buttonReset;
	private JButton buttonQuit;
	private JTextArea textArea;
	private JButton btnFormatCard;
	private JButton btnExecute;
	private JPanel panelKeyTemplate;
	private JPanel panelOption;
	private final ButtonGroup buttonGroup = new ButtonGroup();

	private PcscReader readerControl = new PcscReader();
	private Acos3 acos3 = new Acos3(readerControl);
	private CardSelector cardSelector = new CardSelector(readerControl);
	private byte[] issuerCode = { 0x41, 0x43, 0x4F, 0x53, 0x54, 0x45, 0x53, 0x54 };
	private Charset characterSet = Charset.forName("US-ASCII");

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ACOS3MutualAuthentication window = new ACOS3MutualAuthentication();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public ACOS3MutualAuthentication() {
		// Instantiate an event handler object
		readerControl.setEventHandler(new ReaderEvent());
		// Register the event handler implementation of this class
		readerControl.getEventHandler().addEventListener(this);
		initialize();
		resetControls();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 715, 514);
		frame.setResizable(false);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(null);

		JLabel lblSelectReader = new JLabel("Select Reader");
		lblSelectReader.setFont(new Font("Verdana", Font.PLAIN, 12));
		lblSelectReader.setBounds(10, 11, 87, 16);
		frame.getContentPane().add(lblSelectReader);

		comboBoxReader = new JComboBox<String>();
		comboBoxReader.setFont(new Font("Verdana", Font.PLAIN, 12));
		comboBoxReader.setEnabled(false);
		comboBoxReader.setBounds(10, 35, 335, 20);
		frame.getContentPane().add(comboBoxReader);

		panelOption = new JPanel();
		panelOption.setLayout(null);
		panelOption.setEnabled(false);
		panelOption.setBorder(
				new TitledBorder(null, "Security Option", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panelOption.setBounds(10, 66, 132, 97);
		frame.getContentPane().add(panelOption);

		rdbtnDES = new JRadioButton("DES");
		buttonGroup.add(rdbtnDES);
		rdbtnDES.setBounds(40, 21, 75, 23);
		rdbtnDES.setSelected(true);
		panelOption.add(rdbtnDES);

		rdbtn3DES = new JRadioButton("3DES");
		buttonGroup.add(rdbtn3DES);
		rdbtn3DES.setBounds(40, 56, 75, 23);
		panelOption.add(rdbtn3DES);

		btnInitialize = new JButton("Initialize");
		btnInitialize.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnInitialize.setEnabled(true);
		btnInitialize.setBounds(177, 102, 168, 25);
		frame.getContentPane().add(btnInitialize);

		btnConnect = new JButton("Connect");
		btnConnect.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnConnect.setEnabled(false);
		btnConnect.setBounds(177, 138, 168, 25);
		frame.getContentPane().add(btnConnect);

		JLabel lblApduLogs = new JLabel("APDU Logs:");
		lblApduLogs.setFont(new Font("Verdana", Font.PLAIN, 12));
		lblApduLogs.setBounds(355, 11, 87, 16);
		frame.getContentPane().add(lblApduLogs);

		buttonClear = new JButton("Clear");
		buttonClear.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonClear.setBounds(354, 439, 105, 25);
		frame.getContentPane().add(buttonClear);

		buttonReset = new JButton("Reset");
		buttonReset.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonReset.setEnabled(false);
		buttonReset.setBounds(469, 439, 105, 25);
		frame.getContentPane().add(buttonReset);

		buttonQuit = new JButton("Quit");
		buttonQuit.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonQuit.setBounds(584, 439, 105, 25);
		frame.getContentPane().add(buttonQuit);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(355, 33, 334, 395);
		frame.getContentPane().add(scrollPane);

		textArea = new JTextArea();
		scrollPane.setViewportView(textArea);
		textArea.setText("");
		textArea.setLineWrap(true);

		panelKeyTemplate = new JPanel();
		panelKeyTemplate.setLayout(null);
		panelKeyTemplate.setEnabled(false);
		panelKeyTemplate.setBorder(new TitledBorder(
				new EtchedBorder(EtchedBorder.LOWERED, new Color(255, 255, 255), new Color(160, 160, 160)),
				"Key Template", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
		panelKeyTemplate.setBounds(10, 174, 335, 217);
		frame.getContentPane().add(panelKeyTemplate);

		btnFormatCard = new JButton("Format Card");
		btnFormatCard.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnFormatCard.setEnabled(true);
		btnFormatCard.setBounds(157, 181, 168, 25);
		panelKeyTemplate.add(btnFormatCard);

		JLabel lblCard = new JLabel("Card Key");
		lblCard.setBounds(10, 41, 122, 14);
		panelKeyTemplate.add(lblCard);

		textFieldCardKey = new JTextField();
		textFieldCardKey.setBounds(10, 66, 315, 20);
		panelKeyTemplate.add(textFieldCardKey);
		textFieldCardKey.setColumns(10);

		textFieldTerminalKey = new JTextField();
		textFieldTerminalKey.setColumns(10);
		textFieldTerminalKey.setBounds(10, 122, 315, 20);
		panelKeyTemplate.add(textFieldTerminalKey);

		lblTerminal = new JLabel("Terminal Key");
		lblTerminal.setBounds(10, 97, 122, 14);
		panelKeyTemplate.add(lblTerminal);

		btnExecute = new JButton("Execute Mutual Authentication");
		btnExecute.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnExecute.setEnabled(true);
		btnExecute.setBounds(10, 402, 335, 62);
		frame.getContentPane().add(btnExecute);

		btnInitialize.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String[] readerList;
				comboBoxReader.setEnabled(true);
				comboBoxReader.removeAllItems();

				try {
					readerList = readerControl.listTerminals();

					for (int i = 0; i < readerList.length; i++)
						comboBoxReader.addItem(readerList[i]);

					if (readerList.length > 1)
						comboBoxReader.setSelectedIndex(1);

					btnConnect.setEnabled(true);
				} catch (CardException cardException) {
					displayErrorMessage(Pcsc.PcscProvider.getScardErrorMessage(cardException));
				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		btnConnect.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					String cardName;
					if (readerControl.isConnectionActive())
						readerControl.disconnect();

					readerControl.connect(comboBoxReader.getSelectedIndex(), "*");

					displayMessage("\r\nSuccessfully connected to "
							+ comboBoxReader.getItemAt(comboBoxReader.getSelectedIndex()));

					cardName = cardSelector.getCardNameOnRid(readerControl.getAtr());

					if (cardName == "ACOS3") {
						displayMessage("\r\nChip Type: ACOS3");
					} else {
						displayErrorMessage("Card not supported. Please use ACOS3 card.\r\n");
						return;
					}

					btnInitialize.setEnabled(false);
					btnConnect.setEnabled(false);
					buttonReset.setEnabled(true);
					comboBoxReader.setEnabled(false);

					setEnabled(panelOption, true);
					setEnabled(panelKeyTemplate, true);

				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		buttonClear.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				textArea.setText("");
			}
		});

		buttonReset.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					if (readerControl.isConnectionActive())
						readerControl.disconnect();

					resetControls();
				} catch (CardException cardException) {
					displayErrorMessage(Pcsc.PcscProvider.getScardErrorMessage(cardException));
				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage());
				}
			}
		});

		buttonQuit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					if (readerControl.isConnectionActive())
						readerControl.disconnect();

					System.gc();
					frame.dispose();
				} catch (CardException cardException) {
					displayErrorMessage(Pcsc.PcscProvider.getScardErrorMessage(cardException));
				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage());
				}
			}
		});

		btnFormatCard.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				byte[] aCardKey, aTerminalKey;

				aCardKey = textFieldCardKey.getText().getBytes(characterSet);
				aTerminalKey = textFieldTerminalKey.getText().getBytes(characterSet);

				if (aCardKey.length == 0) {
					displayErrorMessage("Please key-in numeric value for Debit Key.");
					return;
				}
				if (aTerminalKey.length == 0) {
					displayErrorMessage("Please key-in numeric value for Credit Key.");
					return;
				}

				if (rdbtnDES.isSelected()) {
					if (aCardKey.length < 8 || aTerminalKey.length < 8) {
						displayErrorMessage("Invalid input length. Length must be 8.");
						return;
					}
				} else if (rdbtn3DES.isSelected()) {
					if (aCardKey.length < 16 || aTerminalKey.length < 16) {
						displayErrorMessage("Invalid input length. Length must be 16.");
						return;
					}
				}

				try {
					// Send IC Code
					displayMessage("\nSubmit Code - IC");
					acos3.submitCard(Acos3.CODE_TYPE.IC, issuerCode);

					// Clear card
					displayMessage("\nClear Card");
					acos3.clearCard();

					// Send IC Code
					displayMessage("\nSubmit Code - IC");
					acos3.submitCard(Acos3.CODE_TYPE.IC, issuerCode);

					// Select FF 02
					displayMessage("\nSelect File");
					acos3.selectFile(new byte[] { (byte) 0xFF, (byte) 0x02 });

					/*
					 * Write to FF 02 This will create 3 User files, no Option registers and
					 * Security Option registers defined, Personalization bit is not set
					 */
					displayMessage("\nWrite Record");
					if (rdbtnDES.isSelected())
						acos3.writeRecord((byte) 0x00, (byte) 0x00,
								new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x30, (byte) 0x00 });
					else if (rdbtn3DES.isSelected())
						acos3.writeRecord((byte) 0x00, (byte) 0x00,
								new byte[] { (byte) 0x02, (byte) 0x00, (byte) 0x30, (byte) 0x00 });
					displayMessage("FF 02 is updated\n");

					// Send IC Code
					displayMessage("\nSubmit Code - IC");
					acos3.submitCard(Acos3.CODE_TYPE.IC, issuerCode);

					// Select user file
					displayMessage("\nSelect File");
					acos3.selectFile(new byte[] { (byte) 0xFF, (byte) 0x03 });

					if (rdbtnDES.isSelected()) {

						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x02, (byte) 0x00, aCardKey);
						displayMessage("Card key is updated");

						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x03, (byte) 0x00, aTerminalKey);
						displayMessage("Terminal key is updated");

					} else if (rdbtn3DES.isSelected()) {

						byte[] data = new byte[8];
						System.arraycopy(aCardKey, 0, data, 0, 8);
						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x02, (byte) 0x00, data);
						displayMessage("Left Half of Debit key is updated");

						System.arraycopy(aCardKey, 8, data, 0, 8);
						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x0C, (byte) 0x00, data);
						displayMessage("Right Half of Debit key is updated");

						System.arraycopy(aTerminalKey, 0, data, 0, 8);
						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x03, (byte) 0x00, data);
						displayMessage("Left Half of Credit key is updated");

						System.arraycopy(aTerminalKey, 8, data, 0, 8);
						displayMessage("\nWrite Record");
						acos3.writeRecord((byte) 0x0D, (byte) 0x00, data);
						displayMessage("Right Half of Credit key is updated");

					}

					setEnabled(panelKeyTemplate, false);
					btnExecute.setEnabled(true);

				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		btnExecute.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				byte[] aTemp = new byte[8];
				byte[] aCardRandomNum = new byte[8];
				byte[] aEncryptedKey = new byte[8];
				byte[] aTerminalRandomNum = new byte[8];
				byte[] aTerminalResponse = new byte[8];
				byte[] aCardKey, aTerminalKey, aSessionKey, aReverseKey, aCardResponse;

				aCardKey = textFieldCardKey.getText().getBytes(characterSet);
				aTerminalKey = textFieldTerminalKey.getText().getBytes(characterSet);

				try {
					displayMessage("\nStart Session");
					aCardRandomNum = acos3.startSession();

					// Encrypt the Card Random Number with Terminal Key
					if (rdbtnDES.isSelected()) {
						System.arraycopy(aCardRandomNum, 0, aTemp, 0, 8);
						Chain3DES.des(aTemp, aTerminalKey);
						System.arraycopy(aTemp, 0, aEncryptedKey, 0, 8);
					} else if (rdbtn3DES.isSelected()) {
						aTemp = Chain3DES.tripleDes(aCardRandomNum, aTerminalKey);
						System.arraycopy(aTemp, 0, aEncryptedKey, 0, 8);
					}

					aCardResponse = acos3.authenticate(aEncryptedKey, aTerminalRandomNum);

					// Compute for Session Key
					if (rdbtnDES.isSelected()) {

						aSessionKey = new byte[8];

						System.arraycopy(aCardRandomNum, 0, aTemp, 0, 8);
						Chain3DES.des(aTemp, aCardKey);
						System.arraycopy(aTemp, 0, aEncryptedKey, 0, 8);

						// XOR the result with tRnd
						for (int index = 0; index < 8; index++)
							aEncryptedKey[index] = (byte) (aEncryptedKey[index] ^ (byte) aTerminalRandomNum[index]);

						Chain3DES.des(aEncryptedKey, aTerminalKey);
						System.arraycopy(aEncryptedKey, 0, aSessionKey, 0, 8);

						Chain3DES.des(aTerminalRandomNum, aSessionKey);
						System.arraycopy(aTerminalRandomNum, 0, aTerminalResponse, 0, 8);

					} else if (rdbtn3DES.isSelected()) {

						aSessionKey = new byte[16];
						aReverseKey = new byte[16];

						aTemp = Chain3DES.tripleDes(aCardRandomNum, aCardKey);
						System.arraycopy(aTemp, 0, aEncryptedKey, 0, 8);

						aTemp = Chain3DES.tripleDes(aEncryptedKey, aTerminalKey);
						System.arraycopy(aTemp, 0, aEncryptedKey, 0, 8);

						System.arraycopy(aEncryptedKey, 0, aSessionKey, 0, 8);
						System.arraycopy(aTerminalKey, 8, aReverseKey, 0, 8);
						System.arraycopy(aTerminalKey, 0, aReverseKey, 8, 8);

						aTemp = Chain3DES.tripleDes(aTerminalRandomNum, aReverseKey);
						System.arraycopy(aTemp, 0, aSessionKey, 8, 8);

						aTemp = Chain3DES.tripleDes(aTerminalRandomNum, aSessionKey);
						System.arraycopy(aTemp, 0, aTerminalResponse, 0, 8);
					}

					displayMessage("\nCard Response:\n" + Helper.byteAsString(aCardResponse, true));
					displayMessage("\nTerminal Response:\n" + Helper.byteAsString(aTerminalResponse, true));

					if (Arrays.equals(aCardResponse, aTerminalResponse))
						displayMessage("\nMutual Authentication successful");
					else
						displayMessage("\nMutual Authentication failed");

				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		rdbtnDES.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JRadioButton cb = (JRadioButton) event.getSource();
				textFieldCardKey.setDocument(new JTextFieldLimit(8));
				textFieldTerminalKey.setDocument(new JTextFieldLimit(8));

				if (cb.isSelected()) {
					if (textFieldCardKey.getText().length() >= 8) {
						textFieldCardKey.setText("");
					}
					if (textFieldTerminalKey.getText().length() >= 8) {
						textFieldTerminalKey.setText("");
					}
					rdbtn3DES.setSelected(false);
				}
			}
		});

		rdbtn3DES.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JRadioButton cb = (JRadioButton) event.getSource();
				textFieldCardKey.setDocument(new JTextFieldLimit(16));
				textFieldTerminalKey.setDocument(new JTextFieldLimit(16));

				if (cb.isSelected()) {
					if (textFieldCardKey.getText().length() >= 16) {
						textFieldCardKey.setText("");
					}
					if (textFieldTerminalKey.getText().length() >= 16) {
						textFieldTerminalKey.setText("");
					}
					rdbtnDES.setSelected(false);
				}
			}
		});
	}

	void resetControls() {
		textArea.setText("");
		btnInitialize.setEnabled(true);
		btnConnect.setEnabled(false);
		btnExecute.setEnabled(false);
		buttonReset.setEnabled(false);
		comboBoxReader.removeAllItems();
		comboBoxReader.setEnabled(false);

		setEnabled(panelOption, false);
		setEnabled(panelKeyTemplate, false);

		textFieldCardKey.setText("");
		textFieldTerminalKey.setText("");

		displayMessage("Program Ready");
	}

	void setEnabled(Component component, boolean enable) {
		component.setEnabled(enable);
		// Check for instance of container.
		if (component instanceof Container) {
			// Continue setEnabled if there are more components.
			Component components[] = ((Container) component).getComponents();
			for (int i = 0; i < components.length; i++) {
				if (components[i] != null) {
					setEnabled(components[i], enable); // <- Here is the recursive call.
				}
			}
		}
	}

	void displayMessage(String message) {
		textArea.append(message + "\r\n");
	}

	void displayErrorMessage(String message) {
		JOptionPane.showMessageDialog(null, message, "Failed", JOptionPane.ERROR_MESSAGE);
		displayMessage(message);
	}

	@Override
	public void onSendCommand(TransmitApduEventArg event) {
		// TODO Auto-generated method stub
		displayMessage("<< " + event.getAsString(true));
	}

	@Override
	public void onReceiveCommand(TransmitApduEventArg event) {
		// TODO Auto-generated method stub
		displayMessage(">> " + event.getAsString(true));
	}

}
