////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Entrust Corporation.  All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////
package com.datacard.xpsprinter.sdk.examples;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import common_java.XPS_Java_SDK;
import common_java.XPS_Java_SDK.XpsDriverInteropLib;
import common_java.JavaPrint;
import common_java.PrinterStatusXML;
import common_java.Util;

public class Smartcard_Singlewire_Omnikey {

	private String mPrinterName;
	private Map<Byte, String> _statusCodeStrings;
	public static final int ChipConnection_Contact = 0;
	public static final int ChipConnection_Contactless = 1;

	public final int SCARD_SHARE_EXCLUSIVE = 1;
	public final int SCARD_SHARE_SHARED = 2;
	public final int SCARD_SHARE_DIRECT = 3;
	public final int SCARD_LEAVE_CARD = 0;
	public final int SCARD_RESET_CARD = 1;
	public final int SCARD_UNPOWER_CARD = 2;
	public final int SCARD_EJECT_CARD = 3;
	public final int SCARD_UNKNOWN = 0;
	public final int SCARD_ABSENT = 1;
	public final int SCARD_PRESENT = 2;
	public final int SCARD_SWALLOWED = 3;
	public final int SCARD_POWERED = 4;
	public final int SCARD_NEGOTIABLE = 5;
	public final int SCARD_SPECIFIC = 6;
	public final int SCARD_PROTOCOL_UNDEFINED = 0x00000000;
	public final int SCARD_PROTOCOL_T0 = 0x00000001;
	public final int SCARD_PROTOCOL_T1 = 0x00000002;
	public final int SCARD_PROTOCOL_RAW = 0x00010000;
	public final int SCARD_PROTOCOL_DEFAULT = 0x80000000;

	public static final int SCARD_ATTR_VENDOR_NAME = 65792;
	public static final int SCARD_ATTR_VENDOR_IFD_SERIAL_NO = 65795;
	public static final int SCARD_ATTR_VENDOR_IFD_TYPE = 65793;
	public static final int SCARD_ATTR_VENDOR_IFD_VERSION = 65794;

	public static final String SMARTCARD_STATE_ABSENT = "SCARD_ABSENT.put";
	public static final String SMARTCARD_STATE_NEGOTIABLE = "SCARD_NEGOTIABLE";
	public static final String SMARTCARD_STATE_POWERED = "SCARD_POWERED";
	public static final String SMARTCARD_STATE_PRESENT = "SCARD_PRESENT";
	public static final String SMARTCARD_STATE_SPECIFIC = "SCARD_SPECIFIC";
	public static final String SMARTCARD_STATE_SWALLOWED = "SCARD_SWALLOWED";
	public static final String SMARTCARD_STATE_UNKNOWN = "SCARD_UNKNOWN";

	public static final String SMARTCARD_PROTOCOL_CONTACTLESS = "SCARD_PROTOCOL_CL";
	public static final String SMARTCARD_PROTOCOL_RAW = "SCARD_PROTOCOL_RAW";
	public static final String SMARTCARD_PROTOCOL_T0 = "SCARD_PROTOCOL_T0";
	public static final String SMARTCARD_PROTOCOL_T0_OR_T1 = "SCARD_PROTOCOL_T0_OR_T1"; // printer returns this for
																						// contact
	public static final String SMARTCARD_PROTOCOL_T1 = "SCARD_PROTOCOL_T1";

	public static byte MiFare_KeyType_A = 0x00;
	public static byte MiFare_KeyType_B = 0x04;

	public static final int SCARD_S_SUCCESS = 0;
	public static final int S_OK = 0;

	//////////////////////////////////////////
	// toHex
	/////////////////////////////////////////////
	static String toHex(byte[] digest, int size) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(String.format("%1$02X ", digest[i]));
		}
		return sb.toString();
	}

	//////////////////////////////////////////
	// Smartcard_Singlewire_Omnikey - constructor
	/////////////////////////////////////////////
	public Smartcard_Singlewire_Omnikey(String aPrinterName) {
		mPrinterName = aPrinterName;
		LoadStatusCodeStrings();
	}

	//////////////////////////////////////////
	// DemoSmartCard()
	/////////////////////////////////////////////
	public boolean DemoSmartCard(String cardType, boolean bDoPrint, boolean pollCompletion) {

		final int S_OK = 0;
		byte returnXML[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int sizeOfReturnXML[] = new int[1];
		sizeOfReturnXML[0] = XPS_Java_SDK.BUFFSIZE;
		boolean bDisplayError = true;
		boolean bJobStarted = false;
		boolean bJobHasBeenCanceled = false;
		int printerJobID;

		PrinterStatusXML printerStatusXml = new PrinterStatusXML();
		String strHopperID = "1";
		String strCardEjectSide = "Default";
		boolean parkBack = false;
		String outputHopperLocation = "";

		int hResult = XpsDriverInteropLib.INSTANCE.StartJob2(mPrinterName, strHopperID, strCardEjectSide,
				outputHopperLocation, returnXML, sizeOfReturnXML);
		if (S_OK == hResult) {
			bJobStarted = true;
			sizeOfReturnXML[0] = XPS_Java_SDK.BUFFSIZE;
			if (S_OK == XpsDriverInteropLib.INSTANCE.SmartCardPark(mPrinterName, returnXML, sizeOfReturnXML,
					parkBack)) {

				// we have parked the card in the smartcard reader.
				printerStatusXml.Parse(returnXML, sizeOfReturnXML, false);
				printerJobID = printerStatusXml.GetPrintJobID();
				System.out.format("smartcard PARK succeed. PrintJobID: %s\n", printerJobID);
			}
		}

		try {
			PersonalizeSmartcard(cardType);

			if (XpsDriverInteropLib.INSTANCE.ResumeJob(mPrinterName, printerStatusXml.GetPrintJobID(),
					printerStatusXml.GetErrorCode(), returnXML, sizeOfReturnXML) == S_OK) {
				bDisplayError = false;
			}
		} catch (Exception e) {
			bDisplayError = true;
			e.printStackTrace();
		}

		if (bDisplayError) {
			printerStatusXml.Parse(returnXML, sizeOfReturnXML);
			System.out.format("Request failed. Reason: %s\n", printerStatusXml.GetErrorMessage());

			System.out.println("Cancelling operation.");
			XpsDriverInteropLib.INSTANCE.SendResponseToPrinter(mPrinterName, printerStatusXml.PRINTERACTION_CANCEL,
					printerStatusXml.GetPrintJobID(), printerStatusXml.GetErrorCode());
			bJobHasBeenCanceled = true;

		} else if (bDoPrint) {
			String blockFront = null;
			String blockBack = null;
			if (parkBack) {
				blockBack = "chip";
			} else {
				blockFront = "chip";
			}
			JavaPrint javaPrint = new JavaPrint(mPrinterName, true, // duplex
					false, // not portrait
					false, // no magStripe
					blockFront, // blocking front
					blockBack, // blocking back
					true); // print sample text
			javaPrint.PrintDemo();

			Util.WaitUntilJobSpooled(mPrinterName);
		}

		if (bJobStarted && !bJobHasBeenCanceled) {
			XpsDriverInteropLib.INSTANCE.EndJob(mPrinterName);
		}

		if (pollCompletion) {
			Util.PollForJobCompletion(mPrinterName, printerStatusXml.GetPrintJobID());
		}

		return (hResult == S_OK);
	}

	///////////////////////////////////////////////////////////
	// InterpretResponseCode()
	////////////////////////////////////////////////////////////
	void InterpretResponseCode(byte[] bytes, int nSize) {
		if (nSize < 2) {
			System.out.format("expecting a two (or more) byte response code");
		} else {
			if (bytes[nSize - 2] == (byte) 0x90 && bytes[nSize - 1] == (byte) 0x00) {
				System.out.format("response code = SUCCESS");
			} else if (bytes[nSize - 2] == (byte) 0x62 && bytes[nSize - 1] == (byte) 0x82) {
				System.out.format("response code = End of file reached");
			} else if (bytes[nSize - 2] == (byte) 0x67 && bytes[nSize - 1] == (byte) 0x00) {
				System.out.format("response code = Wrong Length");
			} else if (bytes[nSize - 2] == (byte) 0x65 && bytes[nSize - 1] == (byte) 0x81) {
				System.out.format("response code = Memory Failure");
			} else if (bytes[nSize - 2] == (byte) 0x6A && bytes[nSize - 1] == (byte) 0x81) {
				System.out.format("response code = Function not supported");
			} else if (bytes[nSize - 2] == (byte) 0x6B && bytes[nSize - 1] == (byte) 0x00) {
				System.out.format("response code = Wrong parameter");
			} else if (bytes[nSize - 2] == (byte) 0x6F && bytes[nSize - 1] == (byte) 0x00) {
				System.out.format("response code = Operation Failed");
			} else {
				System.out.format("response code = Unknown Response Code");
			}
		}
	}

	//////////////////////////////////////////
	// ReadCardID()
	/////////////////////////////////////////////
	public void ReadCardID() {
		System.out.format("\n");

		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0xCA, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
		int sendBytesBufferSize = 5;

		System.out.format("ReadCardID() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}
		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	////////////////////////////////////////////////////////////////////////////////
	// GetSmartCardAttributes()
	////////////////////////////////////////////////////////////////////////////////
	private int GetSmartCardAttributes(int attribute, byte attrBytesBuffer[], int attrBytesBufferSize[]) {
		int S_OK = 0;

		int scardResult = XpsDriverInteropLib.INSTANCE.SCardGetAttrib(mPrinterName, attribute, attrBytesBuffer,
				attrBytesBufferSize);
		int returnBytes = attrBytesBufferSize[0];
		if (scardResult == S_OK && returnBytes > XPS_Java_SDK.BUFFSIZE) {
			// buffer too small, try it again
			attrBytesBuffer = new byte[returnBytes];
			attrBytesBufferSize[0] = returnBytes;
			scardResult = XpsDriverInteropLib.INSTANCE.SCardGetAttrib(mPrinterName, attribute, attrBytesBuffer,
					attrBytesBufferSize);
		}
		return scardResult;
	}

	////////////////////////////////////////////////////////////////////////////////
	// ReadVendorInfo()
	//
	////////////////////////////////////////////////////////////////////////////////
	public void ReadVendorInfo() {
		System.out.format("\n\n");

		byte attrBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int attrBytesBufferSize[] = new int[1];
		attrBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;
		int sCardResult = GetSmartCardAttributes(SCARD_ATTR_VENDOR_NAME, attrBytesBuffer, attrBytesBufferSize);
		if (sCardResult == S_OK) {
			System.out.format("SCARD_ATTR_VENDOR_NAME := %s\n",
					PrinterStatusXML.cStringToJavaString(attrBytesBuffer, attrBytesBufferSize[0]));
		} else {
			System.out.format("SCARD_ATTR_VENDOR_NAME := ***error*** %d\n", sCardResult);
		}

		attrBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;
		sCardResult = GetSmartCardAttributes(SCARD_ATTR_VENDOR_IFD_SERIAL_NO, attrBytesBuffer, attrBytesBufferSize);
		if (sCardResult == S_OK) {
			System.out.format("SCARD_ATTR_VENDOR_IFD_SERIAL_NO := %s\n",
					PrinterStatusXML.cStringToJavaString(attrBytesBuffer, attrBytesBufferSize[0]));
		} else {
			System.out.format("SCARD_ATTR_VENDOR_IFD_SERIAL_NO attribute := ***error***%d\n", sCardResult);
		}

		attrBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;
		sCardResult = GetSmartCardAttributes(SCARD_ATTR_VENDOR_IFD_TYPE, attrBytesBuffer, attrBytesBufferSize);
		if (sCardResult == S_OK) {
			System.out.format("SCARD_ATTR_VENDOR_IFD_TYPE := %s\n",
					PrinterStatusXML.cStringToJavaString(attrBytesBuffer, attrBytesBufferSize[0]));
		}
//		else {
//			System.out.format("SCARD_ATTR_VENDOR_IFD_TYPE attribute :=***error*** %d\n", sCardResult);
//		}

		attrBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;
		sCardResult = GetSmartCardAttributes(SCARD_ATTR_VENDOR_IFD_VERSION, attrBytesBuffer, attrBytesBufferSize);
		if (sCardResult == S_OK) {

			int byteCount = attrBytesBufferSize[0];
			short minorVersion = byteCount > 0 ? attrBytesBuffer[0] : 0;
			short majorVersion = byteCount > 1 ? attrBytesBuffer[1] : 0;
			short buildNumber = 0;
			if (byteCount > 3) {
				buildNumber = (short) ((attrBytesBuffer[2] << 8) + attrBytesBuffer[3]);

			}
			System.out.format("SCARD_ATTR_VENDOR_IFD_VERSION \n major:= %d\n minor:= %d\n build:= %d\n", majorVersion,
					minorVersion, buildNumber);
		} else {
			System.out.format("SCARD_ATTR_VENDOR_IFD_VERSION attribute :=***error***%d\n", sCardResult);
		}

	}

	////////////////////////////////////////////////////////////////
	// HidProxParseH10301
	//
	// H10301 PACS data is 26 bits (in 4 bytes of data)
	// - 1 bit Parity, 8 bit Facility code, 16 bit CardID, 1 bit Parity, 7 "0's" for
	//////////////////////////////////////////////////////////////// padding
	///////////////////////////////////////////////////////////////

	void HidProxParseH10301(long nPacs) {
		// H10301 formatting = Parity bit + 8 bit Facility code + 16 bit CardID + parity
		// bit + 7 "0's"
		long cardID = nPacs & (long) 0x07fff80;
		cardID = cardID >> 7;
		long facilityCode = nPacs & (long) 0x7f800000;
		facilityCode = facilityCode >> 23;
		System.out.format("\nH10301 facility code = %d, cardID = %d\n", facilityCode, cardID);
	}

	////////////////////////////////////////////////////////////////
	// HidProxParseH10302AndH10304
	//
	// H10302 PACS data is 37 bits (in 5 bytes of data)
	// - 1 bit Parity, NO Facility code, 35 bit CardID, 1 bit Parity, 3 "0's" for
	//  padding
	// H10304 PACS data is 37 bits (in 5 bytes of data)
	// - 1 bit Parity, 16 bit Facility code, 19 bit CardID, 1 bit Parity, 3 "0's"
	// for padding
	// FYI - if there are 12 PACS bytes, this code does not know if H10302 or H10304
	// format was used.
	///////////////////////////////////////////////////////////////
	void HidProxParseH10302AndH10304(long nPacs) {
		// H10302 formatting = Parity bit + NO Facility code + 35 bit CardID + parity
		// bit + 3 "0's"
		long cardID = nPacs & 0x07ffffffff0L;
		cardID = cardID >> 4;
		System.out.format("\nH10302 facility code = No Facility Code, cardID = %d\n", cardID);

		// H10304 formatting = "Parity bit + 16 bit Facility code + 19 bit CardID +
		// parity bit + 3 '0's"
		cardID = nPacs & 0x07ffff0L;
		cardID = cardID >> 4;
		long facilityCode = nPacs & 0x07fff800000L;
		facilityCode = facilityCode >> 23;
		System.out.format("H10304 facility code = %d, cardID = %d\n", facilityCode, cardID);
	}

	////////////////////////////////////////////////////////////////
	// HidProxParsePacs
	//
	// PACS data contains:
	// - first 5 bytes are "standard" PACS header (i.e. 9D 07 03 05 06)
	// - 4 or 5 bytes of PACS data
	// - last 2 bytes (status code) need to be 0x90 0x00 (i.e. success)
	// FYI - need at 11 or 12 bytes, or an exception is thrown
	///////////////////////////////////////////////////////////////
	// void HidProxParsePacs(const std::vector <byte> inputBytes)
	void HidProxParsePacs(byte inputBytes[]) throws Exception {

		String exceptionText;
		int arrayLength = inputBytes.length;
		byte successBytes[] = { (byte) 0x90, (byte) 0x00 };

		// convert array of bytes into an unsigned long int
		if (arrayLength != 11 && arrayLength != 12) {
			exceptionText = "HidProxParsePacs() Invalid number of PACS bytes";
			throw new Exception(exceptionText);

		}
		if (inputBytes[arrayLength - 2] != successBytes[0] && inputBytes[arrayLength - 1] != successBytes[1]) {
			exceptionText = "HidProxParsePacs() invalid PACS information";
			throw new Exception(exceptionText);
		}

		if (arrayLength == 11) {
			// assuming H10301 format
			// need bytes 5 - 8.
			// unsigned long int nLongPacs = 0;
			byte[] tempArray = new byte[4];
			long nLongPacs = 0;
			for (int i = 5; i < 9; i++) {
				// fill "tempArray"
				tempArray[i - 5] = inputBytes[i];
				// shift nPacs to the left 8 bits
				nLongPacs = nLongPacs << 8;

				// FYI - sign extension causes problems in Java
				nLongPacs |= (0xFF & inputBytes[i]);
			}

			// print nLongPacs value in hex
			System.out.format("\nPACS (bytes) = %s\n", toHex(tempArray, tempArray.length));

			System.out.format("PACS (unsigned integer) = %d\n", nLongPacs);
			HidProxParseH10301(nLongPacs);

		} else {
//			// Test code - change bytes 5 - 9 to the following
//			// 1011 0011 1000 1111 0000 1111 1000 0011 1111 0000
//			// This will yield the following results
//			// H10302 - No facility code, cardid = 35 bits = 13840218175 (decimal)
//			// H10304 - Facility code = 16 bits, CardID = 19 bits
//			//  011 0011 1000 1111 0 - Facility code = 26398 (decimal)
//			//                      000 1111 1000 0011 1111 - CardID = 63551 (decimal)
//			//inputBytes[5] = 0xB3;        // 1011 0011 = 0xB3
//			//inputBytes[6] = 0x8F;        // 1000 1111 = 0x8F
//			//inputBytes[7] = 0x0F;        // 0000 1111 = 0x0F
//			//inputBytes[8] = 0x83;        // 1000 0011 = 0x83
//			//inputBytes[9] = 0xF0;        // 1111 0000 = 0xF0

			// assuming H10302 or H10304 formatting
			// need bytes 5 - 9.
			byte[] tempArray = new byte[5];
			long nLongPacs = 0;
			for (int i = 5; i < 10; i++) {
				// fill "tempArray"
				tempArray[i - 5] = inputBytes[i];
				// shift nPacs to the left 8 bits
				nLongPacs = nLongPacs << 8;

				// FYI - sign extension causes problems in Java
				nLongPacs |= (0xFF & inputBytes[i]);
			}

			// print nLongLongPacs value in hex
			System.out.format("\nPACS (bytes) = %s\n", toHex(tempArray, tempArray.length));

			System.out.format("PACS (unsigned integer) = %d\n", nLongPacs);
			HidProxParseH10302AndH10304(nLongPacs);
		}
	}

	////////////////////////////////////////////////////////////////
	// HidProxReadPacs
	///////////////////////////////////////////////////////////////
	// const std::vector <byte> HidProxReadPacs(dxp01sdk::SCard& scard)
	public byte[] HidProxReadPacs() {
		System.out.format("\n\n");

		// byte HidProxReadPacsCommandBytes[] = {0xFF, 0x70, 0x07, 0x6B, 0x07, 0xA0,
		// 0x05, 0xA1, 0x03, 0x80, 0x01, 0x04, 0x00};
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0x70, (byte) 0x07, (byte) 0x6B, (byte) 0x07, (byte) 0xA0,
				(byte) 0x05, (byte) 0xA1, (byte) 0x03, (byte) 0x80, (byte) 0x01, (byte) 0x04, (byte) 0x00 };

		int sendBytesBufferSize = 13;

		System.out.format("HidProxReadPacs() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			byte errorBuffer[] = new byte[1];
			errorBuffer[0] = 0;
			return errorBuffer;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// Original array is of size "XPS_Java_SDK.BUFFSIZE"
		// copy only the bytes that are used to a new array
		byte[] result = new byte[receivedBytesBufferSize[0]];
		System.arraycopy(receivedBytesBuffer, 0, result, 0, receivedBytesBufferSize[0]);
		// print status codes
		InterpretResponseCode(result, result.length);

		// return receivedBytesBuffer;
		return result;
	}

	///////////////////////////////////////////////////////////////////
	// MifareLoadKey()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void MifareLoadKey() {

		// const byte MifareLoadKeyCommandBytes[] = {0xFF, 0x82, 0x00, 0x01, 0x06, 0xFF,
		// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0x82, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0xFF,
				(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };

		int sendBytesBufferSize = 11;

		System.out.format("\nMifareLoadKey() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}
		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// MifareAuthenticate()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void MifareAuthenticate() {

		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x05, (byte) 0x01,
				(byte) 0x00, (byte) 0x01, (byte) 0x60, (byte) 0x01 };

		int sendBytesBufferSize = 10;

		System.out.format("\n\nMifareAuthenticate() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}
		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// MifareReadBlock()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void MifareReadBlock() {
		System.out.println("\n");

		// byte MifareReadBlockCommandBytes[] = {0xFF, 0xB0, 0x00, 0x02, 0x10}; // read
		// 16 bytes
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0xB0, (byte) 0x00, (byte) 0x02, (byte) 0x10 };

		int sendBytesBufferSize = 5;

		System.out.format("MifareReadBlock() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// MifareWriteBlock()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void MifareWriteBlock() {
		String timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(System.currentTimeMillis());
		byte testDataBytes[] = timeStamp.getBytes();
		System.out.format("\n\nwriting - %s \n", timeStamp);

		// FYI - timestamp is 19 bytes long
		// - only want to send 16 bytes

		byte MifareWriteBlockCommandBytes[] = { (byte) 0xFF, (byte) 0xD6, (byte) 0x00, (byte) 0x02, (byte) 0x10 }; 
		int MifareWriteBlockBufferSize = 5;
		// write 16 bytes of Date/Time info to sector 0, block 21

		// Concatenate byte arrays
		byte[] sendBytesBuffer = new byte[MifareWriteBlockBufferSize + 16];
		System.arraycopy(MifareWriteBlockCommandBytes, 0, sendBytesBuffer, 0, MifareWriteBlockBufferSize);
		System.arraycopy(testDataBytes, 0, sendBytesBuffer, MifareWriteBlockBufferSize, 16);

		System.out.format("MifareWriteBlock() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBuffer.length));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer,
				sendBytesBuffer.length, receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	/////////////////////////////////////////////////
	// using sample from document
	///////////////////////////////////////////////
	void MifareReadWriteData() {
		MifareLoadKey();
		MifareAuthenticate();
		MifareReadBlock();
		MifareWriteBlock();
		MifareReadBlock();
	}

	///////////////////////////////////////////////////////////////////
	// IClassReadCardSN()
	//////////////////////////////////////////////////////////////////
	void IclassReadCardSN() {
		System.out.println("\n");

		// SN using block command
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0xB0, (byte) 0x00, (byte) 0x00, (byte) 0x08 };

		int sendBytesBufferSize = 5;

		System.out.format("IClassReadCardSN() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// IclassLoadKey()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void IclassLoadKey() {
		System.out.println("\n");

		// byte IclassLoadKeyCommandBytes[] = {0xFF, 0x82, 0x20, 0x28, 0x08, 0x00, 0x11,
		// 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
		// byte IclassLoadKeyCommandBytes[] = {0xFF, 0x82, 0x20, 0x28, 0x08, 0x00, 0x11,
		// 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0x82, (byte) 0x20, (byte) 0x28, (byte) 0x08, (byte) 0x00,
				(byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77 };

		int sendBytesBufferSize = 13;

		System.out.format("IclassLoadKey() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// IclassAuthenticate()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void IclassAuthenticate() {
		System.out.println("\n");

		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x05, (byte) 0x01,
				(byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x28 };

		int sendBytesBufferSize = 10;

		System.out.format("IclassAuthenticate() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// IclassReadBlock()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void IclassReadBlock() {
		System.out.println("\n");

		// byte IclassReadBlockCommandBytes[] = {0xFF, 0xB0, 0x00, 0x12, 0x08}; // read
		// 8 bytes
		byte sendBytesBuffer[] = { (byte) 0xFF, (byte) 0xB0, (byte) 0x00, (byte) 0x12, (byte) 0x08 };

		int sendBytesBufferSize = 5;

		System.out.format("IclassReadBlock() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	///////////////////////////////////////////////////////////////////
	// IclassWritelock()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	//////////////////////////////////////////////////////////////////
	void IclassWriteBlock() {
		String timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(System.currentTimeMillis());
		byte testDataBytes[] = timeStamp.getBytes();
		System.out.format("\n\nwriting - %s \n", timeStamp);

		// FYI - timestamp is 19 bytes long
		// - only want to send 8 bytes
		// byte IclassWriteBlockCommandBytes[] = { 0xFF, 0xD6, 0x00, 0x12, 0x08}; //
		// write 8 bytes
		byte IclassWriteBlockCommandBytes[] = { (byte) 0xFF, (byte) 0xD6, (byte) 0x00, (byte) 0x12, (byte) 0x08 };
		int WriteBlockBufferSize = 5;

		// Concatenate byte arrays
		byte[] sendBytesBuffer = new byte[WriteBlockBufferSize + 8];
		System.arraycopy(IclassWriteBlockCommandBytes, 0, sendBytesBuffer, 0, WriteBlockBufferSize);
		System.arraycopy(testDataBytes, 0, sendBytesBuffer, WriteBlockBufferSize, 8);

		System.out.format("IclassWriteBlock() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBuffer.length));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer,
				sendBytesBuffer.length, receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		InterpretResponseCode(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	/////////////////////////////////////////////////
	// IclassReadWriteData()
	// - using sample from "Omnikey 5x27CK Software Developer Guide
	///////////////////////////////////////////////
	void IclassReadWriteData() {
		System.out.println("\n");
		IclassLoadKey();
		IclassAuthenticate();
		IclassReadBlock();
		IclassWriteBlock();
		IclassReadBlock();
	}

	//////////////////////////////////////////
	// PersonalizeSmartcard()
	/////////////////////////////////////////////
	public void PersonalizeSmartcard(String cardType) throws Exception {

		int sizeOfReturnXML[] = new int[1];
		sizeOfReturnXML[0] = XPS_Java_SDK.BUFFSIZE;

		// simulate smartcard personalization:
		int protocol[] = new int[1];
		protocol[0] = SCARD_PROTOCOL_UNDEFINED;

		// Connect to chips
		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardConnect(mPrinterName,
				Smartcard_Singlewire_Omnikey.ChipConnection_Contactless, protocol);

		System.out.format("SCardConnect return %d, protocol %d\n", sCardResult, protocol[0]);

		DisplayProtocols(protocol[0]); // optional

		try {
			// SteveB - is calling this method "early" causing problems
			SmartCardControl();

			GetSCardStatus(protocol);

			// ReadCardID(scard);
			ReadCardID();

			// ReadVendorInfo(scard);
			ReadVendorInfo();

			String cstrMifare = "Mifare";
			String cstrIClass = "iClass";
			String cstrHidProx = "HidProx";

			if (cardType.equalsIgnoreCase(cstrMifare) == true) {
				MifareReadWriteData();
			} else if (cardType.equalsIgnoreCase(cstrIClass) == true) {
				IclassReadCardSN();
				IclassReadWriteData();
			} else if (cardType.equalsIgnoreCase(cstrHidProx) == true) {
				byte[] pacsBytes = HidProxReadPacs();
				System.out.format("\n\nHidProxReadPacs() returned %d bytes\n", pacsBytes.length);
				System.out.format("PACS (bytes) = %s\n", toHex(pacsBytes, pacsBytes.length));
				HidProxParsePacs(pacsBytes);
			} else {
//                std::cout << std::endl;
//                exceptionText << "'" << CT2A(cardType.GetString()) << "'" << "is not a valid card type." ;
//                throw std::runtime_error(exceptionText.str());
				System.out.format("\nis not a valid card type\n");
			}

		} catch (Exception e) {
			e.printStackTrace();
			sCardResult = XpsDriverInteropLib.INSTANCE.SCardDisConnect(mPrinterName, SCARD_LEAVE_CARD);
			throw e; // rethrow the exception
		}

		// Disconnect SCard
		sCardResult = XpsDriverInteropLib.INSTANCE.SCardDisConnect(mPrinterName, SCARD_LEAVE_CARD);
		System.out.format("\nSCardDisConnect return %d\n", sCardResult);

	}

	//////////////////////////////////////////
	//
	/////////////////////////////////////////////
	public void DisplayProtocols(int protocol) {
		System.out.format("    protocols:\n");

		if (SCARD_PROTOCOL_UNDEFINED == protocol) {
			System.out.format("    SMARTCARD_PROTOCOL_RAW\n\n");
			return;
		}

		if ((SCARD_PROTOCOL_T0 & protocol) != 0) {
			System.out.format("    SMARTCARD_PROTOCOL_T0\n");
		}

		if ((SCARD_PROTOCOL_T1 & protocol) != 0) {
			System.out.format("    SCARD_PROTOCOL_T1\n");
		}

		if ((SCARD_PROTOCOL_RAW & protocol) != 0) {
			System.out.format("    SCARD_PROTOCOL_RAW\n");
		}

		if ((SCARD_PROTOCOL_DEFAULT & protocol) != 0) {
			System.out.format("    SMARTCARD_PROTOCOL_RAW\n");
		}
		System.out.format("\n");
	}

	////////////////////////////////////////////////////////////////////////////////
	// StringFromState()
	////////////////////////////////////////////////////////////////////////////////
	public String StringFromState(int state) throws Exception {
		switch (state) {
		case SCARD_UNKNOWN:
			return SMARTCARD_STATE_UNKNOWN;
		case SCARD_ABSENT:
			return SMARTCARD_STATE_ABSENT;
		case SCARD_PRESENT:
			return SMARTCARD_STATE_PRESENT;
		case SCARD_SWALLOWED:
			return SMARTCARD_STATE_SWALLOWED;
		case SCARD_POWERED:
			return SMARTCARD_STATE_POWERED;
		case SCARD_NEGOTIABLE:
			return SMARTCARD_STATE_NEGOTIABLE;
		case SCARD_SPECIFIC:
			return SMARTCARD_STATE_SPECIFIC;
		default:
			throw new Exception("StringFromState() - undefined state");
		}
	}

	/////////////////////////////////////////////////////////////////////////////
	// GetSCardStatus()
	//
	// issue the SCardStatus() call. this is important, because the SCardConnect()
	// call always succeeds - as long as the card is staged.
	//
	// the SCardConnect() call returns the Answer-To-Reset bytes (ATR).
	////////////////////////////////////////////////////////////////////////////////
	void GetSCardStatus(int[] protocol) throws Exception {
		System.out.format("\n");

		int states[] = new int[10];
		int numberStatesReturned[] = new int[1];
		numberStatesReturned[0] = 10 * 4; // DWORD is 4 bytes

		byte returnATRBytes[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int numATRBytesReturned[] = new int[1];
		numATRBytesReturned[0] = XPS_Java_SDK.BUFFSIZE;

		// get SCard status
		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardStatus(mPrinterName, states, numberStatesReturned, protocol,
				returnATRBytes, numATRBytesReturned);

		if (sCardResult != 0) {
			System.out.format("SCardStatus := %d\n", sCardResult);
			return;
		}

		System.out.format("SCardStatus numstates= %d, protocol:=%d, numATR:=%d, ATR:=%s\n", numberStatesReturned[0] / 4,
				protocol[0], numATRBytesReturned[0], toHex(returnATRBytes, numATRBytesReturned[0]));

		if (numberStatesReturned[0] / 4 > 10 || numATRBytesReturned[0] > XPS_Java_SDK.BUFFSIZE) {
			// try to get status again
			states = new int[numberStatesReturned[0]];
			returnATRBytes = new byte[numATRBytesReturned[0]];
			sCardResult = XpsDriverInteropLib.INSTANCE.SCardStatus(mPrinterName, states, numberStatesReturned, protocol,
					returnATRBytes, numATRBytesReturned);
			System.out.format("SCardStatus numstates= %d, protocol:=%d, numATR:=%d, ATR:=%s\n",
					numberStatesReturned[0] / 4, protocol[0], numATRBytesReturned[0],
					toHex(returnATRBytes, numATRBytesReturned[0]));
		}

		// display state information
		for (int i = 0; i < numberStatesReturned[0] / 4; i++) {

			if (states[i] == SCARD_ABSENT) {
				System.out.format("SCardStatus() state[%d] =SCARD_ABSENT\n", i);
				return;
			}
			System.out.format("SCardStatus() state[%d] = %d, %s\n", i, states[i], StringFromState(states[i]));
		}
	}

	////////////////////////////////////////////////////////////////////////////////
	// DisplayChipInfo()
	//
	// get mifare status. see pg. 23, "CCID_Protocol_spec_120224_.pdf"
	////////////////////////////////////////////////////////////////////////////////
	public void DisplayChipInfo() {
		System.out.format("\n");

		// see pg 23, "CCID_Protocol_spec_120224_.pdf"
		// byte stream needed to retrieve chip info = FE 17 FE FE 00
		byte sendBytesBuffer[] = { (byte) 0xFE, (byte) 0x17, (byte) 0xFE, (byte) 0xFE, (byte) 0x00 };
		int sendBytesBufferSize = 5;

		System.out.format("DisplayChipInfo() bytes sent = 0xFE, 0x17, 0xFE, 0xFE, 0x00\n");

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}
		// print status codes
		StatusCodeBytesToString(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	//////////////////////////////////////////
	// LoadKeys()
	/////////////////////////////////////////////
	void LoadKeys(byte sector, byte keyType) {
		System.out.format("\n");

		// see pg 23, "CCID_Protocol_spec_120224_.pdf"
		// LoadKeysCommand() - Bytes[] = {0xFD, 0x2F, keyType, sector, 0x06};
		// MifareTestKey bytes[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

		byte sendBytesBuffer[] = { (byte) 0xFD, (byte) 0x2F, keyType, sector, (byte) 0x06, (byte) 0xFF, (byte) 0xFF,
				(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
		int sendBytesBufferSize = 11;
		System.out.format("LoadKeys() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		StatusCodeBytesToString(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

	//////////////////////////////////////////
	// ErrorStringFromStatusByte()
	/////////////////////////////////////////////
	public String ErrorStringFromStatusByte(byte status) {
		if (_statusCodeStrings.get(status) != null) {
			return _statusCodeStrings.get(status);
		}
		String errorString = String.format("undefined status code: %02X", status);
		return errorString;
	}

	///////////////////////////////////////////////////
	// StatusCodeBytesToString()
	///////////////////////////////////////////////////
	public void StatusCodeBytesToString(byte inBytes[], int len) {
		if (len < 3) {
			System.out.format("%s\n", "not enough data for status code commandBytes.");
		}

		String str = String.format("status code = %s, SW1 SW2: %02X %02X", ErrorStringFromStatusByte(inBytes[0]),
				inBytes[len - 2], inBytes[len - 1]);
		System.out.format("%s\n", str);
	}

	///////////////////////////////////////////////////
	// ReadData()
	///////////////////////////////////////////////////
	void ReadData(byte keyType, byte sector, byte block) throws Exception {
		System.out.format("\n");

		// see pg 12, "CCID_Protocol_spec_120224_.pdf"
		// ReadBlock - commandBytes[] = {0xFD, 0x35, keyType, sector, 0x01, block};
		byte sendBytesBuffer[] = { (byte) 0xFD, (byte) 0x35, keyType, sector, 0x01, block };
		int sendBytesBufferSize = 6;
		System.out.format("ReadData() bytes sent = %s\n", toHex(sendBytesBuffer, sendBytesBufferSize));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, sendBytesBuffer, sendBytesBufferSize,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		if (receivedBytesBufferSize[0] == 0) {
			throw new Exception("ReadData() - no bytes returned");
		}

		if (receivedBytesBufferSize[0] >= 3) {
			// print status codes
			StatusCodeBytesToString(receivedBytesBuffer, receivedBytesBufferSize[0]);
		} else {
			throw new Exception("ReadData() - insufficient status bytes returned");
		}

		if (receivedBytesBufferSize[0] > 3) {
			// convert byte data to a string
			byte[] slice = Arrays.copyOfRange(receivedBytesBuffer, 1, receivedBytesBufferSize[0] - 2);
			String s = new String(slice, StandardCharsets.UTF_8);
			System.out.format("received: %s from sector = %d, block = %d\n", s, (int) sector, (int) block);
		} else {
			throw new Exception("ReadData() - insufficient data bytes returned");
		}
	}

	///////////////////////////////////////////////////
	// WriteData()
	///////////////////////////////////////////////////
	void WriteData(byte keyType, byte sector, byte block, byte testDataBytes[]) {
		System.out.format("\n");

		// see pg 13, "CCID_Protocol_spec_120224_.pdf"
		// WriteBlock - commandBytes[] = {0xFD, 0x37, keyType, sector, 0x11, block};
		byte sendBytesBuffer[] = { (byte) 0xFD, (byte) 0x37, keyType, sector, 0x11, block };
		int sendBytesBufferSize = 6;

		// Concatenate byte arrays
		byte[] writeBuffer = new byte[sendBytesBufferSize + testDataBytes.length];
		System.arraycopy(sendBytesBuffer, 0, writeBuffer, 0, sendBytesBufferSize);
		System.arraycopy(testDataBytes, 0, writeBuffer, sendBytesBufferSize, testDataBytes.length);

		System.out.format("WriteData()  %d bytes sent: %s\n", writeBuffer.length,
				toHex(writeBuffer, writeBuffer.length));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, writeBuffer, writeBuffer.length,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}

		// print status codes
		StatusCodeBytesToString(receivedBytesBuffer, receivedBytesBufferSize[0]);
	}

////////////////////////////////////////////////////////////////////////////////
//SmartCardControl()
////////////////////////////////////////////////////////////////////////////////
	void SmartCardControl() {
		System.out.format("\n");

//	     create a byte std::vector for the upcoming SCardControl() method.
//	     particular bytes should function with this type of contactless chip:
//	          HID Prox
//	          HID iClass
//	          Mifare 1K Classic
//
//	    The control codes below are used to turn on / off  the field of the omnikey reader.
//	    The final byte is 0x00 for protocol contact(rf off) and 0x01 for contactless(rf on).
//	    It has been tested using contact connection with
//	      FW rev R4.1.2-3
//
//	    byte controlCode[] = { 0xff, 0x9a, 0x04, 0x00 };  // contact
//
//	    Make contact connection using SCARD_PROTOCOL_T0

		// this control code has been tested on contactless using SCARD_PROTOCOL_CL
		// FW rev R4.1.2-3
		byte writeBuffer[] = { (byte) 0xff, (byte) 0x9a, (byte) 0x04, (byte) 0x01 }; // contactless

		System.out.format("SmartCardControl() - bytes sent: %s\n", toHex(writeBuffer, writeBuffer.length));

		// bytes received
		byte receivedBytesBuffer[] = new byte[XPS_Java_SDK.BUFFSIZE];
		int receivedBytesBufferSize[] = new int[1];
		receivedBytesBufferSize[0] = XPS_Java_SDK.BUFFSIZE;

		int sCardResult = XpsDriverInteropLib.INSTANCE.SCardTransmit(mPrinterName, writeBuffer, writeBuffer.length,
				receivedBytesBuffer, receivedBytesBufferSize);

		if (sCardResult != 0) {
			System.out.format("SCardTransmit failed. sCardResult = %d\n", sCardResult);
			return;
		}

		if (sCardResult == 0 && receivedBytesBufferSize[0] > 0) {
			System.out.format(" %d bytes received: %s\n", receivedBytesBufferSize[0],
					toHex(receivedBytesBuffer, receivedBytesBufferSize[0]));
		}
	}

	//////////////////////////////////////////
	// LoadStatusCodeStrings()
	/////////////////////////////////////////////
	public void LoadStatusCodeStrings() {
		// see pg 45, "CCID_Protocol_spec_120224_.pdf"
		_statusCodeStrings = new HashMap<Byte, String>();

		_statusCodeStrings.put((byte) 0x00, "OK");
		_statusCodeStrings.put((byte) 0x02, "NO TAG ERROR");
		_statusCodeStrings.put((byte) 0x03, "CRC ERROR");
		_statusCodeStrings.put((byte) 0x04, "EMPTY (NO IC CARD ERROR)");
		_statusCodeStrings.put((byte) 0x05, "AUTHENTICATION ERROR or NO POWER");
		_statusCodeStrings.put((byte) 0x06, "PARITY ERROR");
		_statusCodeStrings.put((byte) 0x07, "CODE ERROR");
		_statusCodeStrings.put((byte) 0x08, "SERIAL NUMBER ERROR");
		_statusCodeStrings.put((byte) 0x09, "KEY ERROR");
		_statusCodeStrings.put((byte) 0x0A, "NOT AUTHENTICATION ERROR");
		_statusCodeStrings.put((byte) 0x0B, "BIT COUNT ERROR");
		_statusCodeStrings.put((byte) 0x0C, "BYTE COUNT ERROR");
		_statusCodeStrings.put((byte) 0x0E, "TRANSFER ERROR");
		_statusCodeStrings.put((byte) 0x0F, "WRITE ERROR");
		_statusCodeStrings.put((byte) 0x10, "INCREMENT ERROR");
		_statusCodeStrings.put((byte) 0x11, "DECREMENT ERROR");
		_statusCodeStrings.put((byte) 0x12, "READ ERROR");
		_statusCodeStrings.put((byte) 0x13, "OVERFLOW ERROR");
		_statusCodeStrings.put((byte) 0x14, "POLLING ERROR");
		_statusCodeStrings.put((byte) 0x15, "FRAMING ERROR");
		_statusCodeStrings.put((byte) 0x16, "ACCESS ERROR");
		_statusCodeStrings.put((byte) 0x17, "UNKNOWN COMMAND ERROR");
		_statusCodeStrings.put((byte) 0x18, "ANTICOLLISION ERROR");
		_statusCodeStrings.put((byte) 0x19, "INITIALIZATION(RESET) ERROR");
		_statusCodeStrings.put((byte) 0x1A, "INTERFACE ERROR");
		_statusCodeStrings.put((byte) 0x1B, "ACCESS TIMEOUT ERROR");
		_statusCodeStrings.put((byte) 0x1C, "NO BITWISE ANTICOLLISION ERROR");
		_statusCodeStrings.put((byte) 0x1D, "FILE ERROR");
		_statusCodeStrings.put((byte) 0x20, "INVAILD BLOCK ERROR");
		_statusCodeStrings.put((byte) 0x21, "ACK COUNT ERROR");
		_statusCodeStrings.put((byte) 0x22, "NACK DESELECT ERROR");
		_statusCodeStrings.put((byte) 0x23, "NACK COUNT ERROR");
		_statusCodeStrings.put((byte) 0x24, "SAME FRAME COUNT ERROR");
		_statusCodeStrings.put((byte) 0x31, "RCV BUFFER TOO SMALL ERROR");
		_statusCodeStrings.put((byte) 0x32, "RCV BUFFER OVERFLOW ERROR");
		_statusCodeStrings.put((byte) 0x33, "RF ERROR");
		_statusCodeStrings.put((byte) 0x34, "PROTOCOL ERROR");
		_statusCodeStrings.put((byte) 0x35, "USER BUFFER FULL ERROR");
		_statusCodeStrings.put((byte) 0x36, "BUADRATE NOT SUPPORTED");
		_statusCodeStrings.put((byte) 0x37, "INVAILD FORMAT ERROR");
		_statusCodeStrings.put((byte) 0x38, "LRC ERROR");
		_statusCodeStrings.put((byte) 0x39, "FRAMERR");
		_statusCodeStrings.put((byte) 0x3C, "WRONG PARAMETER VALUE");
		_statusCodeStrings.put((byte) 0x3D, "INVAILD PARAMETER ERROR");
		_statusCodeStrings.put((byte) 0x3E, "UNSUPPORTED PARAMETER");
		_statusCodeStrings.put((byte) 0x3F, "UNSUPPORTED COMMAND");
		_statusCodeStrings.put((byte) 0x40, "INTERFACE NOT ENABLED");
		_statusCodeStrings.put((byte) 0x41, "ACK SUPPOSED");
		_statusCodeStrings.put((byte) 0x42, "NACK RECEVIED");
		_statusCodeStrings.put((byte) 0x43, "BLOCKNR NOT EQUAL");
		_statusCodeStrings.put((byte) 0x44, "TARGET _SET_TOX");
		_statusCodeStrings.put((byte) 0x45, "TARGET_RESET_TOX");
		_statusCodeStrings.put((byte) 0x46, "TARGET_DESELECTED");
		_statusCodeStrings.put((byte) 0x47, "TARGET_RELEASED");
		_statusCodeStrings.put((byte) 0x48, "ID_ALREADY_IN_USE");
		_statusCodeStrings.put((byte) 0x49, "INSTANCE_ALREADY_IN_USE");
		_statusCodeStrings.put((byte) 0x4A, "ID_NOT_IN_USE");
		_statusCodeStrings.put((byte) 0x4B, "NO_ID_AVAILABLE");
		_statusCodeStrings.put((byte) 0x4C, "MI_JOINER_TEMP_ERROR or OTHER_ERROR");
		_statusCodeStrings.put((byte) 0x4D, "INVALID _STATE");
		_statusCodeStrings.put((byte) 0x64, "NOTYET_IMPLEMENTED");
		_statusCodeStrings.put((byte) 0x6D, "FIFO ERROR");
		_statusCodeStrings.put((byte) 0x72, "WRONG SELECT COUNT");
		_statusCodeStrings.put((byte) 0x7B, "WRONG_VALUE");
		_statusCodeStrings.put((byte) 0x7C, "VALERR");
		_statusCodeStrings.put((byte) 0x7E, "RE_INIT");
		_statusCodeStrings.put((byte) 0x7F, "NO_INIT");
	}
}
