package controllers;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;

import com.datecs.pinpad.Pinpad;
import com.datecs.pinpad.Pinpad.PinpadListener;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.Log;
import android.widget.Toast;

// A helper class that implements 
public class PinpadManager {
    // Specify whether to enable Pinpad debug.    
    private static final boolean PINPAD_DEBUG = true;
    
    // The UUID for the SPP bluetooth profile.
    private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	
    // The global single instance of this object.
	private static PinpadManager sInstance = null;
	
	// A listener to notify when connection is established.
	public interface OnConnectionEstablishedListener {
		public void OnConnectionEstablished();		
	};
	
	// A listener to notify when connection is closed.
	public interface OnConnectionClosedListener {
		public void OnConnectionClosed();
	};
	
	// Local parameters.
	private BluetoothAdapter mBtAdapter;
	private BluetoothSocket mBtSocket;		
	private Pinpad mPinpad;
	
    private OnConnectionEstablishedListener mOnConnectEstablishedListener;
    private OnConnectionClosedListener mOnConnectionClosedListener;    
    
    private Context context;
    // Class constructor.
	private PinpadManager(Context context) {
	    this.mBtAdapter = BluetoothAdapter.getDefaultAdapter();
	    //this.context= context;
	}
	
	/**
	 * Get instance of this class. 
	 * 
	 * @param context the application context.
	 * 
	 * @return the instance.
	 */
	public static PinpadManager getInstance(Context context) {
	    Pinpad.setDebug(PINPAD_DEBUG);
	    
	    if (sInstance == null) {
			sInstance = new PinpadManager(context); 
		}
		
		return sInstance;
	}
	
	private BluetoothSocket getBtSocket(String btAddress) throws IOException {
        BluetoothSocket socket = null;
        BluetoothDevice device = mBtAdapter.getRemoteDevice(btAddress);
        
        if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD_MR1) {
            socket = device.createRfcommSocketToServiceRecord(SPP_UUID);                
        } else {
            try {
                // compatibility with pre SDK 10 devices
                Method method = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", UUID.class);
                socket = (BluetoothSocket) method.invoke(device, SPP_UUID);                
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                throw new IOException(e);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                throw new IOException(e);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new IOException(e);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
                throw new IOException(e);
            } 
        }
        return socket;
    }
	
	private void closeBtConnection() {
	 // Release pinpad instance.
        if (mPinpad != null) {
            mPinpad.setPinpadListener(null);
            mPinpad.release();
            mPinpad = null;
        }
        
        // Release current socket.
        if (mBtSocket != null) {
            try {
                mBtSocket.close();
                mBtSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }       
	}
 	
	private synchronized void validateConnection(BluetoothSocket socket) throws IOException {
	    InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
		Pinpad pinpad = new Pinpad(in, out);
		pinpad.sysBeep(4096, 256, 50);
		pinpad.setPinpadListener(new PinpadListener() { 
            @Override
            public void onPinpadRelease() {
                closeBtConnection();
                setConnected(false);
            }
		});
		
		mPinpad = pinpad;
		mBtSocket = socket;		
		setConnected(true);
	}
	
	
	private synchronized void setConnected(boolean state) {
		if (state) {		    
            if (mOnConnectEstablishedListener != null) {
                mOnConnectEstablishedListener.OnConnectionEstablished();                               
            }					
		} else {
		    if (mOnConnectionClosedListener != null) { 
                mOnConnectionClosedListener.OnConnectionClosed();                                           
            }
		}
	}
	
	/**
	 * Connect to pinpad with specified bluetooth address.
	 * 
	 * @param btAddress the address.
	 * 
	 * @throws java.io.IOException if an I/O error occurs.
	 */
	public synchronized void connect(final String btAddress) throws IOException {
		closeBtConnection();
		
	    BluetoothSocket socket = getBtSocket(btAddress);
	    mBtAdapter.cancelDiscovery();   
	  
        socket.connect();        
        try {
            validateConnection(socket);
        } catch (IOException e) {            
            socket.close();
            throw e;
        }
    }
        
    /**
     * Close current connection.
     */
    public synchronized void disconnect() {
        closeBtConnection();    
        setConnected(false);
    }
    
    /**
     * Set a callback, which is invoked when connection is established.
     * 
     * @param listener the callback.
     */
	public void setOnConnectionEstablishedListener(OnConnectionEstablishedListener listener) {
		mOnConnectEstablishedListener = listener;
	}
	
	/**
     * Set a callback, which is invoked when connection with pinpad is closed.
     * 
     * @param listener the callback.
     */
	public void setOnConnectionClosedListener(OnConnectionClosedListener listener) {
		mOnConnectionClosedListener = listener;
	}

	/**
	 * Returns current pinpad instance. 
	 * 
	 * @return the pinpad.
	 */
	public Pinpad getPinpad() {
		return mPinpad;
	}	
	
	/**
     * Returns remote device bluetooth name. 
     * 
     * @return the name.
     */
	public String getBluetoothName() {
		return mBtSocket.getRemoteDevice().getName();       
	}
	
	/**
     * Returns remote device bluetooth address. 
     * 
     * @return the address.
     */
	public String getBluetoothAddress() {
		return mBtSocket.getRemoteDevice().getAddress();
	}

	/**
     * Returns the battery capacity of the currently connected device in percentage
     *
     * @return
     * @throws IOException
     */
    public int getPinpadBatteryCapacity() throws IOException{
        return mPinpad.sysGetHardwareInfo().getCapacity();
    }

    /**
     * Returns the battery voltage of the currently connected device
     *
     * @return
     * @throws IOException
     */
    public int getPinpadBatteryVoltage() throws IOException{
        return mPinpad.sysGetHardwareInfo().getVoltage();
    }

    /**
     *  Return true if pinpad battery is low
     *
     * @return
     * @throws IOException
     */
    public boolean isPinpadBatteryLow() throws IOException{
        return mPinpad.sysGetHardwareInfo().isLowBattery();
    }

    /**
     *  Return true if pinpad battery is very low
     *
     * @return
     * @throws IOException
     */
    public boolean isPinpadBatteryVeryLow() throws IOException{
        return mPinpad.sysGetHardwareInfo().isVeryLowBattery();
    }

    /**
     *  Returns the charging status of pinpad
     *
     * @return
     * @throws IOException
     */
    public boolean isPinpadCharging() throws IOException{
        return mPinpad.sysGetHardwareInfo().isCharging();
    }

    /**
     *  Returns the status of pinpad being on external or not
     *
     * @return
     * @throws IOException
     */
    public boolean isPinpadOnExternalPower() throws IOException{
        return mPinpad.sysGetHardwareInfo().isExternalPower();
    }


}
