package elytraveldocumentforandroid;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.util.Log;

import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.util.SerialInputOutputManager;

import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class Scanner {
	public static final String EXTRA_MRZ_DATA_ACTION = "MRZ_DATA_RCV";
	public static final String EXTRA_MRZ_DATA_TYPE = "MRZ_DATA_TYPE";
	public static final String EXTRA_MRZ_INIT_ACTION = "MRZ_INIT";
	public static final String EXTRA_MRZ_INIT_PERMISSION = "MRZ_INIT_PERMISSION";
	public static final String EXTRA_MRZ_DETECT_EMPTY = "MRZ_DETECT_EMPTY";
	public static final String ACTION_USB_PERMISSION="com.android.elymrtd.USB_PERMISSION";
	private static final String TAG = "elymrtd";
	
	private static Scanner m_single = null;
	private int timeout = 100;
	
	private String mScannerVersion = new String();
	private byte mstate;
	
	private boolean is3lines = false;
	private boolean is2lines = false;
	private boolean is1lines = false;
	private String[] mMRZ = null;



	private UsbManager mUsbManager = null;

	private UsbSerialDriver mUsbSerialDriver = null;
	private boolean isOpen = false;
	private boolean isclose = false;

    private static PendingIntent mPermissionIntent;

    private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
	private SerialInputOutputManager mSerialIoManager;

	private boolean isreceive=false;
	private Context mpcontext = null;
	private UsbDeviceConnection connection=null;

	private SerialInputOutputManager.Listener mListener =
            new SerialInputOutputManager.Listener() {
		
		@Override
        public void onRunError(Exception e) {
            Log.d(TAG, "Runner stopped.");
        }

				@Override
				public void onNewData(byte[] data) {
					try {
						if (data != null) {
							System.out.println("onNewData... ");
							m_single.updateReceivedData(data);
							System.out.println("onNewData... end");
							try {
								m_single.updateMRZdata();
							} catch (IOException e) {
								e.printStackTrace();
							}
						} else {
							System.out.println("onNewData.data null ");
						}
					}catch (Exception e){
						System.out.println("onNewData exception message:"+e.getMessage());
					}

				}

    };


	private Scanner(Context context)
	{

		// Get UsbManager from Android.
		Log.i(TAG, "Scanner(context) ");
		mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
		System.out.println("Scanner");
		mpcontext= context;

		try {
			for (UsbDevice device : mUsbManager.getDeviceList().values()) {
				System.out.println("Scanner    vendorId / productId: " + device.getVendorId() + "/" + device.getProductId());

				if ((device.getVendorId() == 65535 || device.getVendorId()== 11128) && (device.getProductId() == 5||device.getProductId()==2)) {
					if (mUsbManager.hasPermission(device) == false)
					{

						mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
						mUsbManager.requestPermission(device, mPermissionIntent);



					}
					if (!mUsbManager.hasPermission(device)) {
						Log.i(TAG, "Scanner  no Permission!");
					}
					else {
						Log.i(TAG, "Scanner  has Permission!");
					}
					break;
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static Scanner getInstance(Context context)
	{
		Log.i(TAG, "getInstance m");
		if( m_single == null)
		{
			Log.i(TAG, "getInstance m_single = null");
			m_single = new Scanner(context);
		}
		else
		{
			Log.i(TAG, "getInstance m_single not null");
		}

		return m_single;
	}
	
	public static Scanner getInstance()
	{		
		return m_single;
	}


	public void finalize(int nclose)
	{
		Log.i(TAG, "finalize ");
		if(nclose==1) {


			if (mUsbSerialDriver != null) {
				try {
					Log.i(TAG, "finalize ");
					isclose = true;
					stopIoManager();

					Thread.sleep(1000);
					mUsbSerialDriver.close();
					connection.close();
					m_single = null;

				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (Exception e){
					e.printStackTrace();
				}
				//mUsbSerialDriver = null;
				isOpen = false;
			} else {
				if (m_single != null) {
					m_single = null;
				}
			}
		}


	}

	public void resume() {
		try {
			for (UsbDevice device : mUsbManager.getDeviceList().values()) {
				System.out.println("resume    vendorId / productId: " + device.getVendorId() + "/" + device.getProductId());

				if ((device.getVendorId() == 65535 || device.getVendorId()== 11128) && (device.getProductId() == 5||device.getProductId()==2)) {
					if (mUsbManager.hasPermission(device) == true) {
						System.out.println("has permission");
						if (connection == null) {
							System.out.println("connection opendevice");
							connection = mUsbManager.openDevice(device);
						} else {
							System.out.println("connection open");
						}
						mUsbSerialDriver = new CdcAcmSerialDriver(device, connection);



						if (mUsbSerialDriver == null) {

							System.out.println("resume  mUsbSerialDriver == null");
							// No serial device.
							// TODO check how connection can be recovered
						} else {
							try {
								mUsbSerialDriver.open();
								Intent intent = new Intent();
								intent.setAction(EXTRA_MRZ_INIT_ACTION);
								intent.putExtra(EXTRA_MRZ_INIT_PERMISSION,true);
								m_single.mpcontext.sendBroadcast(intent);
								System.out.println("mUsbSerialDriver. opened");
								//mUsbSerialDriver.setParameters(115200, 8, UsbSerialDriver.STOPBITS_1, UsbSerialDriver.PARITY_NONE);
								isOpen = true;

							} catch (IOException e) {
								Log.e(TAG, "Error setting up device: " + e.getMessage(), e);
								System.out.println("Error opening device: " + e.getMessage());
								try {
									mUsbSerialDriver.close();
								} catch (IOException e2) {
									// Ignore.
								}
								mUsbSerialDriver = null;
								isOpen = false;
								return;
							}
							Log.i(TAG, "resume  Stopping io manager ..");
							stopIoManager();
							Log.i(TAG, "resume  start io manager ..");
							startIoManager();
						}
						// onDeviceStateChange


					}
					else{
						Intent intent = new Intent();
						intent.setAction(EXTRA_MRZ_INIT_ACTION);
						intent.putExtra(EXTRA_MRZ_INIT_PERMISSION,false);
						m_single.mpcontext.sendBroadcast(intent);
						System.out.println("resume no permission");

					}
				}
			}
		}catch (Exception e){
			e.printStackTrace();
		}


	}
	
	private void stopIoManager() {

		Log.i(TAG, "stopIoManager()");

		if(!bq.isEmpty())
			bq.clear();
        if (mSerialIoManager != null) {
            Log.i(TAG, "Stopping io manager stop..");

            mSerialIoManager.stop();
			Log.i(TAG, "Stopping io manager null..");

            //mSerialIoManager = null;
        }
        else{
			Log.i(TAG, "Stopping io manager mSerialIoManager==null");
		}

    }

    private void startIoManager() {
        if (mUsbSerialDriver != null) {

            Log.i(TAG, "Starting io manager ..");
            if(mSerialIoManager==null) {
				mSerialIoManager = new SerialInputOutputManager(mUsbSerialDriver, mListener);
			}else{
				Log.i(TAG, "startIoManager mSerialIoManager!=null");
			}

            mExecutor.submit(mSerialIoManager);
        }else{
			Log.i(TAG, "startIoManager mUsbSerialDriver==null");
		}

    }

    private BlockingQueue<ScannerPacket> bq = (BlockingQueue<ScannerPacket>) new ArrayBlockingQueue<ScannerPacket>(1024);
    ScannerPacket currentPacket = null;
    
    byte[] tmpBuffer = new byte[1024];
    int tmpPos=0;
    
    private void appendBuffer(byte [] data, int offset, int length) throws IOException {
    	
    	if(256-tmpPos<length)
			throw new IOException("Buffer size is too small.");
		
		System.arraycopy(data, offset, tmpBuffer, tmpPos, length);
		tmpPos += length;
    }
    
    private void resetResponse() {
    	currentPacket = null;
    }
    
    private ScannerPacket unpackResponse(byte[] data) throws IOException {
    	int tmpLen = 0;
    	int usedLen = 0;
        
    	if(data==null)
    	{
    		if(tmpPos==0) // Padd with existing buffer
        	{
        		// Nothing to do
        		return null;
        	} else {
    			data = tmpBuffer;
        		tmpLen = tmpPos;
        	}
    	}
    	else if(tmpPos>0) // Padd with existing buffer
    	{
    		appendBuffer(data, 0, data.length);
    		data = tmpBuffer;
    		tmpLen = tmpPos;
    	}
    	else
    	{
    		tmpLen = data.length;
    	}
		
    	if(currentPacket==null) {
        	if (tmpLen<3)	{
        		//throw new IOException("Incomplete header response.");
        		return null;
        	}
        	
            currentPacket = new ScannerPacket(data[0], data[1] * 256 + data[2]);
            usedLen = 3 + currentPacket.addData(data, 3, tmpLen-3);
        }
        else
        {
        	usedLen = currentPacket.addData(data, 0, tmpLen);
        }
    	
    	// There is a rest
    	if(usedLen<tmpLen) {
    		// Reset buffer origin
    		tmpPos = 0;
    		appendBuffer(data, usedLen, tmpLen-usedLen);
    	}
    	
    	return currentPacket;
    }

	private synchronized void updateReceivedData(byte[] data) {

		//final String message = "Read " + data.length + " bytes: \n"
		//        + HexDump.dumpHexString(data) + "\n\n";

		boolean next = true;
		ScannerPacket packet = null;
		isreceive=false;
		try {
			System.out.println("updateReceivedData... ");
			packet = unpackResponse(data);
			do {
				if(packet!=null && packet.isComplete()) {
					bq.add(packet);
					notifyAll();
					resetResponse();
					packet = unpackResponse(null);
				} else {
					next = false;
					isreceive=true;
				}
				System.out.println("updateReceivedData...while ");
			}while(next);



		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	private synchronized int updateMRZdata() throws IOException
	{
		String strtemp;
		try {
			//	while(bq.isEmpty() ) {
			wait(timeout);

			//bq.poll(timeout, TimeUnit.MILLISECONDS);
			System.out.println("waiting... ");
			Intent intent = new Intent();
			intent.setAction(EXTRA_MRZ_DATA_ACTION);

			while(!bq.isEmpty() ) {
				ScannerPacket packet = bq.poll();
				System.out.println("packet.getCode(): " + new String(new byte[] {packet.getCode()}));

				switch (packet.getCode())
				{

					case 'V':
						mScannerVersion = new String(packet.getData(), "ASCII");
						Log.d("mScannerVersion", mScannerVersion);
						intent.putExtra(EXTRA_MRZ_DATA_TYPE, packet.getCode());
						m_single.mpcontext.sendBroadcast(intent);
						break;
					case 'C':
					case 'I':
						if (packet.getCode() == 'I') {

							TimeUnit.MILLISECONDS.sleep(1);
							mMRZ = new String(packet.getData(), "ASCII").split("\r");
							System.out.println("mMRZ.length: "+ mMRZ.length+"getpacket: "+packet.getLength());
							//Log.d("+ mMRZ",mMRZ[0].toString()+mMRZ[1].toString()+mMRZ[2].toString()+mMRZ[3].toString());
							if (mMRZ.length == 3) {
								Log.d("+ mMRZ 3",mMRZ[0].toString()+mMRZ[1].toString()+mMRZ[2].toString());
								is3lines = true;
								is2lines = true;
								is1lines = true;
							} else if (mMRZ.length == 2) {
								Log.d("+ mMRZ 2",mMRZ[0].toString()+mMRZ[1].toString());
								is2lines = true;
								is1lines = true;
							} else if (mMRZ.length ==1) {
								Log.d("+ mMRZ 1",mMRZ[0].toString());
								is1lines = true;
							} else {
								Log.d("+ mMRZ 0","length = 0");
							}
							//if(mMRZ.length!=0) {
							intent.putExtra(EXTRA_MRZ_DATA_TYPE, packet.getCode());
							m_single.mpcontext.sendBroadcast(intent);
							//}
						}
						break;
					case 'P':
						mMRZ = new String(packet.getData(), "ASCII").split("\r");
						StringBuilder sb = new StringBuilder();
						for (byte b : packet.getData()) {
							sb.append(String.format("%02X ", b));
						}
						System.out.println("mMRZ.length: "+ mMRZ.length+"getpacket: "+packet.getLength()+"pcaket: "+sb);
						mstate = packet.getData()[0];
						intent.putExtra(EXTRA_MRZ_DATA_TYPE, packet.getCode());
						intent.putExtra(EXTRA_MRZ_DETECT_EMPTY,mstate);
						m_single.mpcontext.sendBroadcast(intent);
						break;
					case 'E':
						strtemp= new String(packet.getData());
						Log.d("Error code:", strtemp);
						intent.putExtra(EXTRA_MRZ_DATA_TYPE, packet.getCode());
						m_single.mpcontext.sendBroadcast(intent);
						break;
					default:
						break;
				}
			}
			//}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return 0;

		}
		return 1;
	}
    /*
     * buildMRZ();

		Log.d("Debug Scanner", "Extract datas MRZ");

		extractInfosMRZ();
     */

	private synchronized int send(byte cmd_code, byte  data) throws IOException
	{
		boolean gotResponse = false;
		byte[] cmd;


		if (cmd_code==0x49 || cmd_code==0x56) {
			cmd = new byte[]{cmd_code, 0, 0};
		} else if(cmd_code ==0x43 || cmd_code ==0x50) {

			cmd = new byte[]{cmd_code,0,1,data};
			/*cmd = new byte[3 + data.length];
			cmd[0] = cmd_code;
			cmd[1] = (byte) (data.length >> 8);
			cmd[2] = (byte) (data.length & 0xFF);
			System.arraycopy(data, 0, cmd, 3, data.length);*/

		}
		else
		{
			cmd = new byte[]{cmd_code, 0, 0};
		}

		StringBuilder sb = new StringBuilder();
		for (byte b : cmd) {
			sb.append(String.format("%02X ", b));
		}
		Log.d("Command: ", sb.toString());

		// If driver wasn't available during first loading
		if(!isOpen)
			resume();

		if(mUsbSerialDriver==null) {
			System.out.println("USB Serial is null");
			throw new IOException("No scanner device found.");
		}
		is3lines = false;
		is2lines = false;
		is1lines = false;

		if(!bq.isEmpty())
			bq.clear();

		int written = mUsbSerialDriver.write(cmd, 3000);

		// If continuous reading mode is enable expected response is Inquire

		System.out.println("cmd_code: " + new String(new byte[] {cmd_code}));
		//if(cmd_code=='C' &&  data==1)
		//	cmd_code = 'I';
		System.out.println("new cmd_code: " + new String(new byte[] {cmd_code}));

		return written;
	}

	public String getVersion() throws IOException
	{
		if (mScannerVersion.isEmpty())
		{
			Log.d(TAG, "GET VERSION");

			//Get Version
			//mScannerVersion = send((byte)'V', null);
			send((byte)'V',(byte)0x00);
		}

		return mScannerVersion;
	}

	public synchronized void readMRZ() throws IOException
	{
		Log.d("Debug Scanner", "Start MRZ capture");

		send((byte)'I', (byte) 0x00);
		//return mMRZ;
	}
	public synchronized void creadMRZ(int ctype) throws IOException
	{
		Log.d("creadMRZ", "Start MRZ capture");



		if(ctype ==2) {
			send((byte) 'C', (byte) 0x01);
		}else{
			send((byte) 'C', (byte) 0x00);
		}


		Log.d("creadMRZ", "After byte 0x00");


	}
	public synchronized void detectMRZ() throws IOException
	{
		Log.d("detectMRZ", "Start MRZ capture");


		send((byte)'P', (byte)0x01);
		Log.d("detectMRZ", "After byte 0x00");


	}

	public String extractMRZInfo()
	{
		String MRZInfo;
		Log.d(TAG, "extractMRZInfo");
		if (is2lines)
		{
			MRZInfo = mMRZ[1].substring(0, 10) + mMRZ[1].substring(13, 20) + mMRZ[1].substring(21, 28);
		}
		else if (is3lines)
		{
			MRZInfo = mMRZ[0].substring(5, 15) + mMRZ[1].substring(0, 7) + mMRZ[1].substring(8, 15);
		}
		else
		{
			MRZInfo="";
		}

		return MRZInfo;
	}


	//Extract data from MRZ
	public String getName()
	{
		Log.d(TAG, "getName");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines && mMRZ[2].length()>=30)
		{
			int pos = mMRZ[2].indexOf("<<");
			return mMRZ[2].substring(pos + 2, mMRZ[2].indexOf("<<", pos + 2)).replace('<', ' ');
		}
		else if(is2lines && mMRZ[0].length()>=30)
		{
			int pos = mMRZ[0].indexOf("<<", 5);
			return mMRZ[0].substring(pos + 2, mMRZ[0].indexOf("<<", pos + 2)).replace('<', ' ');
		}
		else
		{
			return "";
		}
	}

	public String getSurname()
	{
		Log.d(TAG, "getSurname");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&& mMRZ[2].length()>=30)
		{
			return mMRZ[2].substring(0, mMRZ[2].indexOf("<<")).replace('<', ' ');
		}
		else if(is2lines&& mMRZ[0].length()>=30)
		{
			return mMRZ[0].substring(5, mMRZ[0].indexOf("<<", 5)).replace('<', ' ');
		}
		else
		{
			return "";
		}
	}

	public String getDocumentType()
	{
		Log.d(TAG, "getDocumentType");
		if (mMRZ==null)
		{
			return "N/A";
		}
		if(mMRZ[0].length()>=30){
		return mMRZ[0].substring(0, 1);
		}else{
		return "";
		}
	}

	public String getDocumentNumber()
	{
		Log.d(TAG, "getDocumentNumber");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&& mMRZ[0].length()>=30)
		{
			if (mMRZ[0].substring(14, 15).contains("<"))
			{
				String s = mMRZ[0].substring(5, 14) + mMRZ[0].substring(15, mMRZ[0].indexOf("<<", 15));

				return s.replace('<', ' ');
			}
			else
			{
				return mMRZ[0].substring(5, 14).replace('<', ' ');
			}
		}
		else if(is2lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(0, 9).replace('<', '\0');
		}
		else
		{
			return "";
		}
	}

	public String getBirthDate()
	{
		Log.d(TAG, "getBirthDate");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(0, 6);
		}
		else if(is2lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(13, 19);
		}
		else
		{
			return "";
		}
	}

	public String getValidityDate()
	{
		Log.d(TAG, "getValidityDate");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&&mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(8, 14);
		}
		else if(is2lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(21, 27);
		}
		else
		{
			return "";
		}
	}

	public String getGender()
	{
		Log.d(TAG, "getGender");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(7, 8);
		}
		else if(is2lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(20, 21);
		}
		else
		{
			return "";
		}
	}

	public String getNationality()
	{
		Log.d(TAG, "getNationality");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if (is3lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(15, 18);
		}
		else if(is2lines&& mMRZ[1].length()>=30)
		{
			return mMRZ[1].substring(10, 13);
		}
		else
		{
			return "";
		}
	}

	public String getIssuingState()
	{
		Log.d(TAG, "getIssuingState");
		if (mMRZ==null)
		{
			return "N/A";
		}

		if(  is3lines&& mMRZ[0].length()>=30) {
			return mMRZ[0].substring(2, 5);
		}else if(is2lines&& mMRZ[0].length()>=30) {
			return mMRZ[0].substring(2, 5);
		}else {
			if(is2lines&& mMRZ[0].length()>=300) {
				return mMRZ[0].substring(2, 4);
			}
			else{
				return "";
			}
		}
	}

	public PassportView getPassportView() {

		if(mMRZ.length!=0) {
			//Extract datas from MRZ
			String name = this.getName();
			String surname = this.getSurname();
			String birthDate = this.getBirthDate();
			String nationality = this.getNationality();
			String gender = this.getGender();
			String validityDate = this.getValidityDate();
			String documentNumber = this.getDocumentNumber();
			String documentType = this.getDocumentType();
			String issueState = this.getIssuingState();

			return new PassportView(name, surname, birthDate, nationality, gender, validityDate,
					documentNumber, documentType, issueState, mMRZ, is3lines, is2lines, is1lines, null, mScannerVersion);
		}else{
			return null;
		}

	}

	public PassportView getPassportView_line() {

		if(mMRZ.length!=0) {
			//Extract datas from MRZ


			return new PassportView("", "", "", "", "", "",
					"", "", "", mMRZ, is3lines, is2lines, is1lines, null, mScannerVersion);
		}else{
			return null;
		}

	}
}
