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.smartcardio.CardException;
import javax.swing.JButton;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;

import Acos.*;
import AcsCommon.*;
import Pcsc.*;
import Pcsc.ReaderEvent.TransmitApduEventArg;

import javax.swing.border.EtchedBorder;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;

import javax.swing.JRadioButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.ButtonGroup;
import javax.swing.JTextField;

public class ACOS3ReadWrite extends JFrame implements ReaderEvent.TransmitApduHandler {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private JFrame frame;
	private JComboBox<String> comboBoxReader;
	private JButton btnInitialize;
	private JButton btnConnect;
	private JLabel lblApduLogs;
	private JButton buttonClear;
	private JButton buttonReset;
	private JButton buttonQuit;
	private JTextArea textArea;
	private JButton btnFormat;
	private JButton btnRead;
	private JRadioButton rdbtnAA;
	private JRadioButton rdbtnBB;
	private JRadioButton rdbtnCC;
	private JButton btnWrite;
	private JPanel panelUserFile;

	private PcscReader readerControl = new PcscReader();
	private Acos3 acos3 = new Acos3(readerControl);

	private byte[] issuerCode = { 0x41, 0x43, 0x4F, 0x53, 0x54, 0x45, 0x53, 0x54 };
	private final ButtonGroup buttonGroup = new ButtonGroup();
	private JTextField textField;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ACOS3ReadWrite window = new ACOS3ReadWrite();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public ACOS3ReadWrite() {
		// 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, 771, 458);
		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, 13, 87, 16);
		frame.getContentPane().add(lblSelectReader);

		comboBoxReader = new JComboBox<String>();
		comboBoxReader.setFont(new Font("Verdana", Font.PLAIN, 12));
		comboBoxReader.setEnabled(false);
		comboBoxReader.setBounds(10, 37, 353, 20);
		frame.getContentPane().add(comboBoxReader);

		btnInitialize = new JButton("Initialize");
		btnInitialize.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnInitialize.setEnabled(true);
		btnInitialize.setBounds(10, 68, 140, 25);
		frame.getContentPane().add(btnInitialize);

		btnConnect = new JButton("Connect");
		btnConnect.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnConnect.setEnabled(false);
		btnConnect.setBounds(195, 68, 168, 25);
		frame.getContentPane().add(btnConnect);

		lblApduLogs = new JLabel("APDU Logs:");
		lblApduLogs.setFont(new Font("Verdana", Font.PLAIN, 12));
		lblApduLogs.setBounds(384, 13, 87, 16);
		frame.getContentPane().add(lblApduLogs);

		buttonClear = new JButton("Clear");
		buttonClear.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonClear.setBounds(383, 383, 106, 25);
		frame.getContentPane().add(buttonClear);

		buttonReset = new JButton("Reset");
		buttonReset.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonReset.setEnabled(false);
		buttonReset.setBounds(499, 383, 110, 25);
		frame.getContentPane().add(buttonReset);

		buttonQuit = new JButton("Quit");
		buttonQuit.setFont(new Font("Verdana", Font.PLAIN, 12));
		buttonQuit.setBounds(619, 383, 125, 25);
		frame.getContentPane().add(buttonQuit);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(384, 37, 361, 338);
		frame.getContentPane().add(scrollPane);

		textArea = new JTextArea();
		scrollPane.setViewportView(textArea);
		textArea.setText("");
		textArea.setLineWrap(true);

		btnFormat = new JButton("Format Card");
		btnFormat.setFont(new Font("Verdana", Font.PLAIN, 12));
		btnFormat.setEnabled(true);
		btnFormat.setBounds(195, 104, 168, 25);
		frame.getContentPane().add(btnFormat);

		JLabel lblDataIn = new JLabel("Data In");
		lblDataIn.setFont(new Font("Verdana", Font.PLAIN, 12));
		lblDataIn.setBounds(10, 359, 87, 16);
		frame.getContentPane().add(lblDataIn);

		panelUserFile = new JPanel();
		panelUserFile.setLayout(null);
		panelUserFile.setBorder(new TitledBorder(
				new EtchedBorder(EtchedBorder.LOWERED, new Color(255, 255, 255), new Color(160, 160, 160)), "User File",
				TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
		panelUserFile.setBounds(10, 140, 353, 208);
		frame.getContentPane().add(panelUserFile);

		btnRead = new JButton("Read");
		btnRead.setBounds(184, 44, 159, 41);
		panelUserFile.add(btnRead);

		rdbtnAA = new JRadioButton("AA 11");
		buttonGroup.add(rdbtnAA);
		rdbtnAA.setBounds(21, 44, 109, 23);
		panelUserFile.add(rdbtnAA);

		rdbtnBB = new JRadioButton("BB 22");
		buttonGroup.add(rdbtnBB);
		rdbtnBB.setBounds(21, 81, 109, 23);
		panelUserFile.add(rdbtnBB);

		rdbtnCC = new JRadioButton("CC 33");
		buttonGroup.add(rdbtnCC);
		rdbtnCC.setBounds(21, 118, 109, 23);
		panelUserFile.add(rdbtnCC);

		rdbtnAA.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JRadioButton cb = (JRadioButton) event.getSource();
				textField.setDocument(new JTextFieldLimit(10));
				if (cb.isSelected())
					if (textField.getText().length() >= 10) {
						textField.setText("");
						return;
					}
			}
		});

		rdbtnBB.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JRadioButton cb = (JRadioButton) event.getSource();
				textField.setDocument(new JTextFieldLimit(16));
				if (cb.isSelected())
					if (textField.getText().length() >= 16) {
						textField.setText("");
						return;
					}
			}
		});

		rdbtnCC.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JRadioButton cb = (JRadioButton) event.getSource();
				textField.setDocument(new JTextFieldLimit(32));
				if (cb.isSelected())
					if (textField.getText().length() >= 32) {
						textField.setText("");
						return;
					}
			}
		});

		btnWrite = new JButton("Write");
		btnWrite.setBounds(184, 96, 159, 41);
		panelUserFile.add(btnWrite);

		textField = new JTextField();
		textField.setBounds(10, 386, 353, 20);
		frame.getContentPane().add(textField);
		textField.setColumns(10);

		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 {
					if (readerControl.isConnectionActive())
						readerControl.disconnect();

					readerControl.connect(comboBoxReader.getSelectedIndex(), "*");

					displayMessage("\r\nSuccessfully connected to "
							+ comboBoxReader.getItemAt(comboBoxReader.getSelectedIndex()));

					btnInitialize.setEnabled(false);
					btnConnect.setEnabled(false);
					buttonReset.setEnabled(true);
					comboBoxReader.setEnabled(false);
					btnFormat.setEnabled(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());
				}
			}
		});

		btnFormat.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				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");
					acos3.writeRecord((byte) 0x00, (byte) 0x00,
							new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x00 });
					displayMessage("FF 02 is updated\n");

					// Select FF 04
					displayMessage("\nSelect File");
					acos3.selectFile(new byte[] { (byte) 0xFF, (byte) 0x04 });

					// Send IC Code
					displayMessage("\nSubmit Code - IC");
					acos3.submitCard(Acos3.CODE_TYPE.IC, issuerCode);

					/*
					 * Write to FF 04 Write to first record of FF 04
					 */
					displayMessage("\nWrite Record");
					acos3.writeRecord((byte) 0x00, (byte) 0x00, new byte[] { (byte) 0x0A, (byte) 0x03, (byte) 0x00,
							(byte) 0x00, (byte) 0xAA, (byte) 0x11, (byte) 0x00 });
					displayMessage("User File AA 11 is defined");

					// Write to second record of FF 04
					displayMessage("\nWrite Record");
					acos3.writeRecord((byte) 0x01, (byte) 0x00, new byte[] { (byte) 0x10, (byte) 0x02, (byte) 0x00,
							(byte) 0x00, (byte) 0xBB, (byte) 0x22, (byte) 0x00 });
					displayMessage("User File BB 22 is defined");

					// write to third record of FF 04
					displayMessage("\nWrite Record");
					acos3.writeRecord((byte) 0x02, (byte) 0x00, new byte[] { (byte) 0x20, (byte) 0x04, (byte) 0x00,
							(byte) 0x00, (byte) 0xCC, (byte) 0x33, (byte) 0x00 });
					displayMessage("User File CC 33 is defined");

					setEnabled(panelUserFile, true);
					textField.setEnabled(true);
				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		btnRead.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				byte[] fileId = new byte[2];
				byte dataLength = 0x00;
				byte[] data;

				try {
					if (rdbtnAA.isSelected()) {
						fileId = new byte[] { (byte) 0xAA, (byte) 0x11 };
						dataLength = (byte) 0x0A;
					} else if (rdbtnBB.isSelected()) {
						fileId = new byte[] { (byte) 0xBB, (byte) 0x22 };
						dataLength = (byte) 0x10;
					} else if (rdbtnCC.isSelected()) {
						fileId = new byte[] { (byte) 0xCC, (byte) 0x33 };
						dataLength = (byte) 0x20;
					}

					// Select user file
					displayMessage("\nSelect File");
					acos3.selectFile(fileId);

					// read first record of user file selected
					displayMessage("\nRead Record");
					data = acos3.readRecord((byte) 0x00, (byte) 0x00, dataLength);

					textField.setText(Helper.byteArrayToString(data, data.length));
				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});

		btnWrite.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				byte[] fileId = new byte[2];
				int expectedLength = 0;
				String temporaryString = "";
				byte[] temporaryArray = new byte[56];
				int index = 0;

				try {
					// Validate input
					if (textField.getText().equals("")) {
						displayErrorMessage("Please key-in data to write.");
						textField.requestFocus();
						return;
					}

					if (rdbtnAA.isSelected()) {
						fileId = new byte[] { (byte) 0xAA, (byte) 0x11 };
						expectedLength = 10;
					} else if (rdbtnBB.isSelected()) {
						fileId = new byte[] { (byte) 0xBB, (byte) 0x22 };
						expectedLength = 16;
					} else if (rdbtnCC.isSelected()) {
						fileId = new byte[] { (byte) 0xCC, (byte) 0x33 };
						expectedLength = 32;
					}

					// Select user file
					displayMessage("\nSelect File");
					acos3.selectFile(fileId);

					temporaryString = textField.getText();
					temporaryArray = new byte[expectedLength];

					while (index < textField.getText().length()) {
						temporaryArray[index] = temporaryString.getBytes()[index];
						index++;
					}
					while (index < expectedLength) {
						temporaryArray[index] = (byte) 0x00;
						index++;
					}

					displayMessage("\nWrite Record");
					acos3.writeRecord((byte) 0x00, (byte) 0x00, temporaryArray);

					displayMessage("Data read from textbox is written to card");
					textField.setText("");

				} catch (Exception exception) {
					displayErrorMessage(exception.getMessage() + "\r\n");
				}
			}
		});
	}

	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 resetControls() {
		textArea.setText("");
		btnInitialize.setEnabled(true);
		btnConnect.setEnabled(false);
		buttonReset.setEnabled(false);
		comboBoxReader.removeAllItems();
		comboBoxReader.setEnabled(false);

		btnFormat.setEnabled(false);
		setEnabled(panelUserFile, false);
		textField.setEnabled(false);

		displayMessage("Program Ready");
	}

	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));

	}
}
