package devices;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference;

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

@BA.Events(values= {
		"log(msg as string)",
		"finddongle(success as boolean , harwareID as int, message as string)",
		"opendongle(success as boolean, hardwareID as int, SessionHandle as int, message as string)",
		"closedongle(success as boolean, SessionHandle as int, message as string)",
		"readuserdatazone(success as boolean, SessionHandle as int, data() as byte, message as string)",
		"writeuserdatazone(success as boolean, SessionHandle as int, message as string)",
		"readuserid(success as boolean, SessionHandle as int, UserID as int, message as string)",
		"writeuserid(success as boolean, SessionHandle as int, message as string)"
})
@BA.ShortName("JSecureDongleV2")
public class JSecureDongleV2 {
	private final int  SD_FIND = 1;//Find Dongle
	private final int  SD_FIND_NEXT = 2;//Find Next Dongle
	private final int  SD_OPEN = 3;//Open Dongle
	private final int  SD_CLOSE = 4;//Close Dongle
	private final int  SD_READ =  5	;	//Read Dongle
	private final int  SD_WRITE = 6	;	//Write Dongle
	private final int  SD_RANDOM = 7;//Generate Random Number
	private final int  SD_SEED =  8;//Generate Seed Code
	private final int  SD_WRITE_USERID=9;//Write User ID
	private final int  SD_READ_USERID=	10;//Read User ID
	private final int  SD_SET_MODULE=	11;//Set Module
	private final int  SD_CHECK_MODULE=12;//Check Module
	private final int  SD_WRITE_ARITHMETIC=13;//Write Algorithm
	private final int  SD_CALCULATE1=	14;//Calculate 1
	private final int  SD_CALCULATE2=	15;//Calculate 2
	private final int  SD_CALCULATE3=	16;//Calculate 3
	private final int  SD_CALCULATE4=	18;//Calculate 4
	private final int  SD_CALCULATE5=	19;//Calculate 5
	private final int  SD_DECREASE=	17;//Decrease Module Unit

	private final int  SD_SETPASSWORDID =0xf0;
	private final int  SD_AGENTBURN	=0xf3;
	private final int  SD_GETVERSION=0xf7;

	private final int  SD_SET_COUNTER=20;//Set Counter
	private final int  SD_GET_COUNTER=	21;//Get Counter
	private final int  SD_DEC_COUNTER=22;
	private final int  SD_SET_TIMER=23;//Set Timer
	private final int  SD_GET_TIMER=	24;//Get Timer
	private final int  SD_ADJUST_TIMER=25;              //Adjust System Clock
	private final int  SD_SET_TIMER_ITV=26;
	private final int  SD_GET_TIMER_ITV=27;
	private final int  SD_DEC_TIMER=28;
	private final int  SD_SET_RSAKEY_N=29;//Write N into RSA keypair 
	private final int  SD_SET_RSAKEY_D=30;//Write D into RSA keypair
	private final int  SD_UPDATE_GEN_HEADER=	        31;//Generate header of cipher-text file
	private final int  SD_UPDATE_GEN=32;//Generate cipher-text file
	private final int  SD_UPDATE_CHECK=33;//Check and Upgrade cipher-text file 
	private final int  SD_UPDATE=34;//Upgrade cipher-text file
	private final int  SD_UNPACK=        35;


	private final int  SD_FREEEPROM=	89;

	// private final int SD_TESTXU=	0xA1 //jw test cos

	//private final int  SD_GET_DES_KEY==41
	private final int  SD_SET_DES_KEY=	        41;//Set DES Key
	private final int  SD_DES_ENC=42;//DES Encryption
	private final int  SD_DES_DEC=43;//DES Decryption
	private final int  SD_RSA_ENC=44;//RSA Encryption=
	private final int  SD_RSA_DEC=45;//RSA Decryption

	private final int SD_READ_EX                 =46;
	private final int SD_WRITE_EX=47;

	private final int SD_SET_COUNTER_EX=0xA0;	//Set value to Count Unit (DWORD) 
	private final int SD_GET_COUNTER_EX=0xA1;	//Get value from Count Unit (DWORD) 
	private final int SD_SET_TIMER_EX=	0xA2;	//Set value to Time Unit (DWORD)
	private final int SD_GET_TIMER_EX=	0xA3;	//Get value from Time Unit (DWORD)
	private final int SD_ADJUST_TIMER_EX=0xA4;	//Adjust Timer in Dongle 

	private final int SD_UPDATE_GEN_EX=0xA6;	//Generate Update File

	private final int SD_UPDATE_EX=	0xA8;	//Update Dongle
	private final int SD_SET_UPDATE_KEY=0xA9;	//Set RSA Keys for Generate Update File
	private final int SD_ADD_UPDATE_HEADER=	        0xAA;	//Fill in the content of Update File Head
	private final int SD_ADD_UPDATE_CONTENT=	        0xAB;	//Fill in the content of Update File Body
	private final int SD_GET_TIME_DWORD=0xAC;	//Translate Time to DWORD value (the minutes from 2006.1.1) 

	private final int  SD_VERSION=        100;     //Get COS Version

	//add by bgc on 09/2006
	private final int DES_SINGLE_MODE=0;
	private final int DES_TRIPLE_MODE=1;

	private final int RSA_PRIVATE_KEY=0;
	private final int RSA_PUBLIC_KEY=1;

	private final int RSA_DONGLE_PADDING=0;
	private final int RSA_USER_PADDING=1;
	//end 

	private String GetErrorMessage(int code) {
		switch(code) {
		case ERR_SUCCESS : 
			return "Success";
		case ERR_NO_DONGLE :
			return "No Driver installed";
		case ERR_INVALID_PASSWORD :
			return "Found Dongle, but basic passwords (pass1, pass2) are wrong";
		case ERR_INVALID_PASSWORD_OR_ID :
			return "Wrong password or Hardware ID";
		case ERR_SETID :
			return "Error in Hardware ID setting";
		case ERR_INVALID_ADDR_OR_SIZE :
			return "Error in read/write address or length";
		case ERR_UNKNOWN_COMMAND :
			return "No such command";
		case ERR_NOTBELEVEL3 :
			return "Internal Error";
		case ERR_READ :
			return "Read Error";
		case ERR_WRITE :
			return "Write Error";
		case ERR_RANDOM :
			return "Random Number Error";
		case ERR_SEED :
			return "Seed Code Error";
		case ERR_CALCULATE :
			return "Error in Calculation";
		case ERR_NO_OPEN :
			return "Unopen dongle before operation";
		case ERR_OPEN_OVERFLOW :
			return "Too many open dongles (>16)";
		case ERR_NOMORE :
			return "No more dongles";
		case ERR_NEED_FIND :
			return "No 'Find' function called before using 'FindNext'";
		case ERR_DECREASE :
			return "Decrease Error";
		case ERR_AR_BADCOMMAND :
			return "Error in Arithmetic Instruction";
		case ERR_AR_UNKNOWN_OPCODE :
			return "Error in Arithmetic Operator";
		case ERR_AR_WRONGBEGIN :
			return "Constant cannot be used in the first arithmetic instruction";
		case ERR_AR_WRONG_END :
			return "Constant cannot be used in the last arithmetic instruction";
		case ERR_AR_VALUEOVERFLOW :
			return "The value of Constant cannot be greater than 63";
		case ERR_TOOMUCHTHREAD :
			return "The threads opened in the same process cannot be greater than 100";
		case ERR_SET_DES_KEY :
			return "Set DES Key Error";
		case ERR_DES_ENCRYPT :
			return "DES Encryption Error";
		case ERR_DES_DECRYPT :
			return "DES Decryption Error";
		case ERR_SET_RSAKEY_N :
			return "Error in writing N into RSA keypair";
		case ERR_SET_RSAKEY_D :
			return "Error in writing D into RSA keypair";
		case ERR_RSA_ENCRYPT :
			return "RSA Encryption Error";
		case ERR_RSA_DECRYPT :
			return "RSA Decryption Error";
		case ERR_INVALID_LENGTH :
			return "Invalid data length";
		case ERR_RECEIVE_NULL :
			return "Receive nothing";
		case ERR_UNKNOWN_SYSTEM :
			return "Unknown Operating System";
		case ERROR_UNINIT_TIME_UNIT :
			return "Time uint uninitialized";
		case ERR_UNKNOWN :
			return "Unknown Error";
		default :
			return "Misterious Error, code = "+String.valueOf(code);
		}
	}
	// Error Codes
	private final int ERR_SUCCESS =	0; //Success
	private final int ERR_NO_DONGLE = 3; // No driver installed
	private final int ERR_INVALID_PASSWORD = 4; //Found Dongle, but basic passwords (pass1, pass2) are wrong
	private final int ERR_INVALID_PASSWORD_OR_ID = 5; //Wrong password or Hardware ID
	private final int ERR_SETID=6; //Error in Hardware ID setting
	private final int ERR_INVALID_ADDR_OR_SIZE = 7; //Error in read/write address or length
	private final int ERR_UNKNOWN_COMMAND=8; //No such command
	private final int ERR_NOTBELEVEL3=	9; //Internal Error
	private final int ERR_READ=10; //Read Error
	private final int ERR_WRITE=11; //Write Error
	private final int ERR_RANDOM=12; //Random Number Error
	private final int ERR_SEED=13; //Seed Code Error
	private final int ERR_CALCULATE=14; //Error in Calculation
	private final int ERR_NO_OPEN=	15; //Unopen dongle before operation
	private final int ERR_OPEN_OVERFLOW=16; //Too many open dongles (>16)
	private final int ERR_NOMORE=17; //No more dongles
	private final int ERR_NEED_FIND=18; //No "Find" function called before using "FindNext"
	private final int ERR_DECREASE=19; //Decrease Error
	private final int ERR_AR_BADCOMMAND=20; //Error in Arithmetic Instruction
	private final int ERR_AR_UNKNOWN_OPCODE=21; //Error in Arithmetic Operator
	private final int ERR_AR_WRONGBEGIN=22; //Constant cannot be used in the first arithmetic instruction
	private final int ERR_AR_WRONG_END=23; //Constant cannot be used in the last arithmetic instruction
	private final int ERR_AR_VALUEOVERFLOW=24; //The value of Constant cannot be greater than 63
	private final int ERR_TOOMUCHTHREAD=25; //The threads opened in the same process cannot be greater than 100 

	private final int ERR_INVALID_SD=30; //Try to handle non-Securedongle 
	private final int ERR_INVALID_PARAMETER= 31 ;//Invalid parameter
	private final int ERR_INVALID_TIMEVALUE= 32; //Invalid Date-Time value

	//Set DES Key Error
	private final int ERR_SET_DES_KEY=	40;

	//DES Encryption Error 
	private final int ERR_DES_ENCRYPT=	41;

	//DES Decryption Error 	
	private final int ERR_DES_DECRYPT=	42;

	//Error in writing N into RSA keypair
	private final int ERR_SET_RSAKEY_N=43;

	//Error in writing D into RSA keypair
	private final int ERR_SET_RSAKEY_D=44;

	//RSA Encryption Error 	
	private final int ERR_RSA_ENCRYPT=	45;

	//RSA Decryption Error	
	private final int ERR_RSA_DECRYPT=	46;

	//Invalid data length
	private final int ERR_INVALID_LENGTH=47;

	private final int ERR_RECEIVE_NULL=0x100; //Receive nothing
	private final int ERR_UNKNOWN_SYSTEM=0x102; //Unknown Operating System
	private final int ERROR_UNINIT_TIME_UNIT = 0x103; //Time uint uninitialized
	private final int ERR_UNKNOWN=	0xffff; //Unknown Error

	private final int ERR_XUTEST=0xAA;
	
	native short SecureDongle(short function, ShortByReference handle, IntByReference lp1, IntByReference lp2, ShortByReference p1, ShortByReference p2, ShortByReference p3, ShortByReference p4, Memory buffer);

	static {
		try {
			Native.register(JSecureDongleV2.class,"SecureDongle");
			System.out.println("SecureDongle library loaded");	
		} catch(Exception e) {
			System.out.println("Unable to register SecureDongle Library");
		}
		
	}
	
	
	/**
	 * Secure Dongle session address
	 * 16 bit pointer
	 */
	ShortPointer handle;
	/**
	 * Parameter 1
	 * 16 bit pointer
	 */
	ShortPointer p1; 
	/**
	 * Parameter 2
	 * 16 bit pointer
	 */
	ShortPointer p2;
	/**
	 * Parameter 3
	 * 16 bit pointer
	 */
	ShortPointer p3; 
	/**
	 * Parameter 4
	 * 16 bit pointer
	 */
	ShortPointer p4;
	/**
	 * Return Code
	 * 16 bit value
	 */
	Short retcode;
	/**
	 * Long Parameter 1
	 * 32 bit pointer
	 */
	IntPointer lp1; 
	/**
	 * Long Parameter 2
	 * 32 bit pointer
	 */
	IntPointer lp2;
	/**
	 * Byte array buffer
	 * 1024 bytes 8 bit
	 */
	Memory buffer;
	
	/**
	 * Single thread executor untuk run function secure dongle
	 */
	private ExecutorService exec ;
	
	public JSecureDongleV2() {
		buffer = new Memory(1024);
		lp1 = new IntPointer();
		lp2 = new IntPointer();
		p1 = new ShortPointer();
		p2 = new ShortPointer();
		p3 = new ShortPointer();
		p4 = new ShortPointer();
		handle = new ShortPointer();
		exec = Executors.newSingleThreadExecutor();
	}
	
	private BA bax;
	private String event;
	private Object Me;
	
	
	private boolean need_log_event = false;
	private boolean need_finddongle_event = false;
	private boolean need_opendongle_event = false;
	private boolean need_closedongle_event = false;
	private boolean need_readuserdatazone_event = false;
	private boolean need_writeuserdatazone_event = false;
	private boolean need_readuserid_event = false;
	private boolean need_writeuserid_event = false;
	
	/**
	 * Initialize JSecureDongleV2
	 * @param eventname Eventname
	 */
	public void Initialize(BA ba, String eventname) {
		this.bax = ba;
		this.event = eventname;
		Me = this;
		check_events();
	}
	
	private void check_events() {
		if (bax!=null) {
			if (mycodes.ValidString(event)) {
				need_log_event = bax.subExists(event+"_log");
				need_finddongle_event = bax.subExists(event+"_finddongle");
				need_opendongle_event = bax.subExists(event+"_opendongle");
				need_closedongle_event =bax.subExists(event+"_closedongle");
				need_readuserdatazone_event = bax.subExists(event+"_readuserdatazone");
				need_writeuserdatazone_event = bax.subExists(event+"_writeuserdatazone");
				need_readuserid_event = bax.subExists(event+"_readuserid");
				need_writeuserid_event = bax.subExists(event+"_writeuserid");
			}
		}
	}
	
	/**
	 * Check if a specific SecureDongle is attached to USB port
	 * will raise event finddongle
	 * @param password1 Basic Password 1
	 * @param password2 Basic Password 2
	 * @param password3 Advanced Password 1 (optional)
	 * @param password4 Advanced Password 2 (optional)
	 */
	public void Find_SecureDongle(int password1, int password2, int password3, int password4) {
		p1.setValue(ShortPointer.ShortValue_FromInt(password1));
		p2.setValue(ShortPointer.ShortValue_FromInt(password2));
		p3.setValue(ShortPointer.ShortValue_FromInt(password3));
		p4.setValue(ShortPointer.ShortValue_FromInt(password4));
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_FIND), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_finddongle(
					retcode==0 ? true : false,
					retcode==0 ? lp1.getValue() : 0,
					GetErrorMessage(retcode)
					);
		});
	}
	
	/**
	 * To check if another specific SecureDongle is attached to USB port
	 * Must call Find_SecureDongle first before calling this function
	 * will raise event finddongle
	 * @param password1 Basic Password 1
	 * @param password2 Basic Password 2
	 * @param password3 Advanced Password 1 (optional)
	 * @param password4 Advanced Password 2 (optional)
	 */
	public void Find_Next_SecureDongle(int password1, int password2, int password3, int password4) {
		p1.setValue(ShortPointer.ShortValue_FromInt(password1));
		p2.setValue(ShortPointer.ShortValue_FromInt(password2));
		p3.setValue(ShortPointer.ShortValue_FromInt(password3));
		p4.setValue(ShortPointer.ShortValue_FromInt(password4));
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_FIND_NEXT), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_finddongle(
					retcode==0 ? true : false,
					retcode==0 ? lp1.getValue() : 0,
					GetErrorMessage(retcode)
					);
		});
	}
	
	/**
	 * Open SecureDongle with specific password or HardwareID
	 * will raise event opendongle
	 * @param password1 Basic Password 1
	 * @param password2 Basic Password 2
	 * @param password3 Advanced Password 1 (optional)
	 * @param password4 Advanced Password 2 (optional)
	 * @param HardwareID Hardware ID
	 */
	public void Open_SecureDongle(int password1, int password2, int password3, int password4, final int HardwareID) {
		p1.setValue(ShortPointer.ShortValue_FromInt(password1));
		p2.setValue(ShortPointer.ShortValue_FromInt(password2));
		p3.setValue(ShortPointer.ShortValue_FromInt(password3));
		p4.setValue(ShortPointer.ShortValue_FromInt(password4));
		lp1.setValue(HardwareID);
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_OPEN), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_opendongle(
					retcode==0 ? true : false,
					HardwareID,
					retcode==0 ? handle.toInt() : 0,
					GetErrorMessage(retcode)
					);
			
		});
	}
	
	/**
	 * Close SecureDongle with specific Handle
	 * will raise event closedongle
	 * @param Handle Session Handle
	 */
	public void Close_SecureDongle(final int Handle) {
		handle.setValue(ShortPointer.ShortValue_FromInt(Handle));
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_CLOSE), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_closedongle(
					retcode==0 ? true : false,
					Handle,
					GetErrorMessage(retcode)
					);
		});
		
	}
	
	/**
	 * Read content from User Data Zone
	 * will raise event readuserdatazone
	 * @param Handle Session Handle
	 * @param offset from zero (0)
	 * @param length max 1000 bytes
	 */
	public void Read_UserDataZone(final int Handle,final int offset, final int length) {
		
		handle.setValue(ShortPointer.ShortValue_FromInt(Handle));
		p1.setValue(ShortPointer.ShortValue_FromInt(offset));
		p2.setValue(ShortPointer.ShortValue_FromInt(length));
		buffer.clear();
		
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_READ), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_readuserdatazone(
					retcode==0 ? true : false,
					Handle,
					retcode==0 ? buffer.getByteArray(offset, length) : null,
					GetErrorMessage(retcode)
					);
		});
		
	}
	
	/**
	 * Write content to User Data Zone
	 * will raise event writeuserdatazone
	 * @param Handle Session Handle
	 * @param offset offset of User Data Zone
	 * @param length length of content to be written
	 * @param content content to write
	 */
	public void Write_UserDataZone(final int Handle, final int offset, final int length, final byte[] content) {
		handle.setValue(ShortPointer.ShortValue_FromInt(Handle));
		p1.setValue(ShortPointer.ShortValue_FromInt(offset));
		p2.setValue(ShortPointer.ShortValue_FromInt(length));
		buffer.clear();
		buffer.write(0, content, 0, length);
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_WRITE), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_writeuserdatazone(
					retcode==0 ? true : false,
					Handle,
					GetErrorMessage(retcode)
					);
		});
	}
	
	/**
	 * Write UserID
	 * will raise event writeuserid
	 * @param Handle Session Handle
	 * @param UserID value of User ID
	 */
	public void Write_UserID(final int Handle, int UserID) {
		handle.setValue(ShortPointer.ShortValue_FromInt(Handle));
		lp1.setValue(UserID);
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_WRITE_USERID), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_writeuserid(
					retcode==0 ? true : false,
					Handle,
					GetErrorMessage(retcode)
					);
		});
	}
	
	/**
	 * Read UserID
	 * will raise event readuserid
	 * @param Handle Session Handle
	 */
	public void Read_UserID(final int Handle) {
		handle.setValue(ShortPointer.ShortValue_FromInt(Handle));
		exec.submit(()->{
			retcode = this.SecureDongle(ShortPointer.ShortValue_FromInt(SD_READ_USERID), handle, lp1, lp2, p1, p2, p3, p4, buffer);
			raise_readuserid(
					retcode==0 ? true : false,
					Handle,
					retcode==0 ? lp1.getValue() : 0,
					GetErrorMessage(retcode)
					);
		});
	}
	
	private void raise_log(String msg) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}
	
	private void raise_finddongle(boolean success, int HardwareID, String message ) {
		if (need_finddongle_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_finddongle", false, new Object[] {success, HardwareID, message});
	}
	
	private void raise_opendongle(boolean success, int HardwareID, int SessionHandle, String message) {
		if (need_opendongle_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_opendongle", false, new Object[] {success, HardwareID, SessionHandle, message});
	}
	
	private void raise_closedongle(boolean success, int SessionHandle, String message) {
		if (need_closedongle_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_closedongle", false, new Object[] {success, SessionHandle, message});
	}
	
	private void raise_readuserdatazone(boolean success, int SessionHandle, byte[] data, String message) {
		if (need_readuserdatazone_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_readuserdatazone", false, new Object[] {success, SessionHandle, data, message});
	}
	
	private void raise_writeuserdatazone(boolean success, int SessionHandle, String message) {
		if (need_writeuserdatazone_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_writeuserdatazone", false, new Object[] {success, SessionHandle, message});
	}
	
	private void raise_readuserid(boolean success, int SessionHandle, int UserID, String message) {
		if (need_readuserid_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_readuserid", false, new Object[] {success, SessionHandle, UserID, message});
	}
	
	private void raise_writeuserid(boolean success, int SessionHandle, String message) {
		if (need_writeuserid_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_writeuserid", false, new Object[] {success, SessionHandle, message});
	}
}
