package rfidreader;

import java.nio.ByteBuffer;
import java.util.Arrays;

import javax.smartcardio.ATR;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;

import anywheresoftware.b4a.BA;
import jGPIO.mycodes;

@BA.ShortName("RFIDCardInfo")
public class RFIDCardInfo {
	private final Card _card;
	// RID = Registered Application Provider Identifier
	private String RID = "";
	private byte Standard = 0;
	private int CardType = 0;
	private String RFU = "";
	private String SerialNumber = "";
	private String ATS = "";
	private boolean isvalid = false;
	/**
	 * Initialize RFIDCardInfo
	 * @param card Card Object
	 */
	public RFIDCardInfo(Card card) {
		this._card = card;
		if (_card != null) {
			if (mycodes.ValidString(_card.getProtocol())) {
				if (mycodes.IsByteArray_and_have_values(card.getATR().getBytes())) {
					isvalid = check_valid_atr(card.getATR());
					if (isvalid) {
						SerialNumber = get_serialnumber(card.getBasicChannel());
						if (CardType!= 1) {
							//TODO yang ketahuan , Mifare 1K gak support ini 
							ATS = get_ats(card.getBasicChannel());
						}
						
						
					}
				}
			}
		}
	}
	
	/**
	 * Check if this object already initialized and valid
	 * @return true if valid
	 */
	public boolean IsValid() {
		return isvalid;
	}
	
	/**
	 * Get Card Protocol
	 * @return empty string if invalid
	 */
	public String getCardProtocol() {
		if (isvalid) {
			return _card.getProtocol();
		}
		return "";
	}
	
	/**
	 * Get Card ATR bytes
	 * @return null if invalid
	 */
	public byte[] getCardATRBytes() {
		if (isvalid) {
			return _card.getATR().getBytes();
		}
		return null;
	}
	
	/**
	 * Get Card ATR in String
	 * @return empty string if invalid
	 */
	public String getCardATRString() {
		String result = "";
		if (isvalid) {
			byte[] atr = _card.getATR().getBytes();
			for(byte xx : atr) {
				result+=String.format("%02X", xx);
			}
			
		}
		return result;
	}
	
	// Source : https://downloads.acs.com.hk/drivers/en/API-ACR122U-2.02.pdf
	private boolean check_valid_atr(ATR atr) {
		if (atr != null) {
			byte[] atrbytes = atr.getBytes();
			try {
				if (mycodes.ToInt(atrbytes[0])==0x3B) {
					if ((mycodes.ToInt(atrbytes[1]) & 0x80)>0) {
						int N = mycodes.ToInt(atrbytes[1]) & 0x0F;
						if (atrbytes.length==(5+N)) {
							if (mycodes.ToInt(atrbytes[2])==0x80) {
								if (mycodes.ToInt(atrbytes[3])==1) {
									
									// mulai inti
									if (mycodes.ToInt(atrbytes[4])==0x80) {
										// PDF article 3.1.1
										
										if (mycodes.ToInt(atrbytes[5])==0x4F) {
											int length = mycodes.ToInt(atrbytes[6]);
											if (length>0) {
												
												byte[] content = Arrays.copyOfRange(atrbytes, 7, 7+length);
												
												byte[] rid_bytes = Arrays.copyOfRange(content, 0, 5);
												RID = mycodes.Bytes_toHex(rid_bytes, "");
												//printlog("RID = "+RID);
												Standard = content[5];
												//printlog("Standard = "+Standard);
												CardType = mycodes.To16bits(content[6], content[7]);
												
												
												//printlog("Card Name = "+ getCardName());
												
												byte[] rfu_bytes = Arrays.copyOfRange(content,  8, 8+4);
												RFU = mycodes.Bytes_toHex(rfu_bytes, "");
												//printlog("RFU = "+RFU);
												return true;
												
											} else printlog("check_valid_atr article 3.1.1 failed at length[6]");
										} else printlog("check_valid_atr article 3.1.1 failed at Application Identifier Presence Indicator");
									}
									
									
								} else printlog("check_valid_atr failed at TD2");
							} else printlog("check_valid_atr failed at TD1");
						} else printlog("check_valid_atr failed at length, N = "+N+", length="+atrbytes.length);
					} else printlog("check_valid_atr failed at T0");
				} else printlog("check_valid_atr failed at Initial Header");
			} catch(Exception e) {
				printlog("check_valid_atr exception = "+e.getMessage());
			}
		}
		return false;
	}
	
	public String getRFU() {
		return RFU;
	}
	
	public String getRID() {
		return RID;
	}
	
	public byte getStandard() {
		return Standard;
	}
	
	public String getCardName() {
		switch(CardType) {
		case 1 : return "Mifare 1K";
		case 2 : return "Mifare 4K";
		case 3 : return "Mifare Ultralight";
		case 0x26 : return "Mifare Mini";
		case 0xF004: return "Topaz and Jewel";
		case 0xF011 : return "Felica 212K";
		case 0xF012 : return "Felica 424K";
		default : return "Undefined ["+mycodes.ToHex(CardType)+"]";
		}
	}
	
	public String getSerialNumber() {
		return SerialNumber;
	}
	
	public String getATS() {
		return ATS;
	}
	
	/**
	 * Load Authentication Key to Reader
	 * PDF article 5.1
	 * @param keylocation 0 / 1
	 * @param key must 6 bytes, if null will default to FF FF FF FF FF FF
	 * @return false if failed
	 */
	public boolean Load_Authentication_Keys_To_Reader(byte keylocation, int[] key) {
		if (isvalid) {
			if (keylocation==0 || keylocation==1) {
				if (key==null) key = new int[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
				if (key.length!=6) key = new int[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
				byte[] cmd = mycodes.ConcatBytes(mycodes.ToByteArray(0xFF, 0x82, 0, keylocation, 6), mycodes.ToByteArray(key));
				printlog("Load_Authentication_Keys_To_Reader will send : "+mycodes.Bytes_toHex(cmd, " "));
				String result = Send(_card.getBasicChannel(), cmd);
				return result.endsWith("9000");
				
			} else printlog("Load_Authentication_Keys_To_Reader failed, invalid keylocation");
		} else printlog("Load_Authentication_Keys_To_Reader failed, invalid RFIDCardInfo");
		return false;
	}
	
	/**
	 * Need to authentication before reading data block
	 * Mifare 1K have 16 sectors, each sector have 4 blocks, each block is 16 bytes
	 * Mifare 4K have 40 sectors, each sector have 4 blocks, each block is 16 bytes
	 * Read PDF article 5.2
	 * If reading the same sector, no need to re-authenticate
	 * @param newformat if true, will use 10 bytes format. if false, will use old 6 bytes format
	 * @param blocknumber : Mifare 1K (00 - 3E), Mifare 4K (00 - FE)
	 * @param keytype : 0x60 (Type A) or 0x61 (Type B). if invalid, default to Type A
	 * @param keynumber : 0 or 1, if invalid default to 0
	 * @return true if authentication success
	 */
	public boolean Authentication_To_Card(boolean newformat, int blocknumber, byte keytype, byte keynumber) {
		if (keytype<0x60) keytype = 0x60;
		if (keytype>0x61) keytype = 0x60;
		if (keynumber<0) keynumber = 0;
		if (keynumber>1) keynumber = 0;
		if (blocknumber<0) blocknumber = 0;
		if (CardType==1 && blocknumber > 0x3E) blocknumber = 0x3E;	// Mifare 1K
		if (CardType==2 && blocknumber > 0xFE) blocknumber = 0xFE;	// Mifare 4K
		
		
		byte[] cmd = newformat ? mycodes.ToByteArray(0xFF, 0x88, 0, blocknumber, keytype, keynumber) : mycodes.ToByteArray(0xFF, 0x86, 0, 0, 5, 1, 0, blocknumber, keytype, keynumber);
		String result = Send(_card.getBasicChannel(), cmd);
		return result.endsWith("9000");
	}
	
	/**
	 * Read binary block 
	 * must authenticate byte block using Authentication_To_Card before reading
	 * if need to read byte block from the same sector, no need to re-authenticate
	 * Read PDF article 5.3
	 * @param blocknumber : Mifare 1K (0 - 3E), Mifare 4K (0 - FE). 
	 * @param bytestoread : Mifare 1K / 4K (0 - 16, invalid = 16), Mifare Ultralight (0 - 4, invalid = 4)
	 * @return bytes array if success, or null if failed
	 */
	public byte[] ReadBinaryBlock(int blocknumber, int bytestoread) {
		if (blocknumber<0) blocknumber = 0;
		if (CardType==1 && blocknumber > 0x3E) blocknumber = 0x3E;
		if (CardType==2 && blocknumber > 0xFE) blocknumber = 0xFE;
		
		// Mifare 1K / 4K
		if ((CardType==1 || CardType==2)) {
			if (bytestoread < 0) bytestoread = 16;
			if (bytestoread > 16) bytestoread = 16;
		}
		// Mifare Ultralight
		if (CardType==3) {
			if (bytestoread < 0) bytestoread = 4;
			if (bytestoread > 4) bytestoread = 4;
		}
		byte[] result = Send_ReturnBytes(_card.getBasicChannel(),mycodes.ToByteArray(0xFF, 0xB0, 0, blocknumber, bytestoread));
		if (result!=null) {
			int len = result.length;
			if (len==bytestoread + 2) {
				String resultcode = mycodes.Bytes_toHex("", result[len-2], result[len-1]);
				if (resultcode.compareTo("9000")==0) {
					return Arrays.copyOfRange(result, 0, bytestoread);
				} else printlog("ReadBinaryBlock Operation failed, code="+resultcode);
			} else printlog("ReadBinaryBlock result length = "+len+", must be "+(bytestoread+2));
		} else printlog("ReadBinaryBlock result is null");
		return null;
	}
	
	/**
	 * Write Data to Binary block
	 * Read PDF article 5.4
	 * @param blocknumber : Mifare 1K (0 - 3E), Mifare 4K (0 - FE)
	 * @param bytestowrite : Mifare 1K/4K (0 - 16), Mifare Ultralight (0 - 4) 
	 * @param datatowrite : array of bytes to write
	 * @return true if operation success
	 */
	public boolean WriteBinaryBlock(int blocknumber, int bytestowrite, int... datatowrite) {
		if (blocknumber<0) blocknumber = 0;
		if (CardType==1 && blocknumber > 0x3E) blocknumber = 0x3E;
		if (CardType==2 && blocknumber > 0xFE) blocknumber = 0xFE;
		if (CardType==1 || CardType==2) {
			//Mifare 1K / 4K
			if (bytestowrite < 0) bytestowrite = 16;
			if (bytestowrite > 16) bytestowrite = 16;
		}
		if (CardType==3) {
			// Mifare ultralight
			if (bytestowrite < 0) bytestowrite =4;
			if (bytestowrite > 4) bytestowrite = 4;
		}
		byte[] cmd = mycodes.ConcatBytes(mycodes.ToByteArray(0xFF, 0xD6, 0, blocknumber, bytestowrite), mycodes.ToByteArray(datatowrite) );
		printlog("WriteBinaryBlock will Send : "+mycodes.Bytes_toHex(" ", cmd));
		String result = Send(_card.getBasicChannel(), cmd );
		return result.endsWith("9000");
	}
	
	private void printlog(String msg) {
		System.out.println(msg);
	}
	
	private String get_serialnumber(CardChannel cc) {
		String result = Send(cc, mycodes.ToByteArray(0xFF, 0xCA, 0x00, 0x00, 0x04));
		if (result.endsWith("9000")) {
			return result.substring(0, result.length()-4);
		} else if (result.endsWith("6300")) {
			printlog("get_serialnumber operation failed");
		} else if (result.endsWith("6A81")) {
			printlog("get_serialnumber function not supported");
		}
		return "";
	}
	
	private String get_ats(CardChannel cc) {
		String result = Send(cc, mycodes.ToByteArray(0xFF, 0xCA, 0x01, 0x00, 0x04));
		if (result.endsWith("9000")) {
			return result.substring(0, result.length()-4);
		} else if (result.endsWith("6300")) {
			printlog("get_ats operation failed");
		} else if (result.endsWith("6A81")) {
			printlog("get_ats function not supported");
		}
		return "";
	}
	
	private String Send(CardChannel cc, byte... cmd) {
		String result = "";
		if (cc!=null) {
			if (cmd!=null && cmd.length>0) {
				byte[] baResp = new byte[258];
				ByteBuffer bufCmd = ByteBuffer.wrap(cmd);
				ByteBuffer bufResp = ByteBuffer.wrap(baResp);
				int output = 0;
				try {
					output = cc.transmit(bufCmd, bufResp);

				} catch(CardException e) {
					printlog("Send exception : "+e.getMessage());
				}

				if (output>0) {
					for (int i=0; i< output; i++) {
						result+= String.format("%02X", baResp[i]);
					}
				}	
			}

		}
		return result;

	}
	
	private byte[] Send_ReturnBytes(CardChannel cc, byte... cmd) {
		
		if (cc!=null) {
			if (cmd!=null && cmd.length>0) {
				byte[] baResp = new byte[258];
				ByteBuffer bufCmd = ByteBuffer.wrap(cmd);
				ByteBuffer bufResp = ByteBuffer.wrap(baResp);
				int output = 0;
				try {
					output = cc.transmit(bufCmd, bufResp);

				} catch(CardException e) {
					printlog("Send exception : "+e.getMessage());
				}

				if (output>0) {
					return Arrays.copyOfRange(baResp, 0, output);
				}	
			}

		}
		
		return null;
	}
	
}
