/*
 * FLYFISH TECHNOLOGIES Ltd.
 * http://www.flyfish-tech.com
 * 
 * hiDBLUE API usage example
 * 
 * 
 * 
 * This code is in the Public Domain. Use it at your own risk.
 */

package com.flyfish_tech.hidblue.demo;

import java.io.UnsupportedEncodingException;
import java.util.Timer;
import java.util.TimerTask;

import com.flyfish_tech.hidblue.api.API_hiDBLUE;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements SensorEventListener {

	public final int INITIAL = -1;
	public final int MAIN = 0;
	public final int MOUSE = 1;
	public final int TOUCHPAD = 2;
	public final int NUMERIC = 3;
	public final int PASSWORDS = 4;
	public final int PRESENTATION = 5;
	public final int BARCODE = 6;
	public final int SMS = 7;

	public final int MOUSE_MOVE = 1;
	public final int MOUSE_WHEEL = 2;
	public final int MOUSE_BUTTON = 3;
	public final int MOUSE_VARIABLES = 4;

	public final int MOUSE_BUTTON_LEFT = 1;
	public final int MOUSE_BUTTON_RIGHT = 2;
	public final int MOUSE_BUTTON_NONE = 3;

	public final int TOUCHPAD_BUTTON_LEFT = 1;
	public final int TOUCHPAD_BUTTON_RIGHT = 2;
	public final int TOUCHPAD_SLIDE = 3;
	
	public final int PRESENTATION_PREVIOUS = 1;
	public final int PRESENTATION_NEXT = 2;
	public final int PRESENTATION_POINTER_SHOW = 3;
	public final int PRESENTATION_POINTER_HIDE = 4;
	public final int PRESENTATION_HIGHLIGHT_START = 5;
	public final int PRESENTATION_HIGHLIGHT_STOP = 6;
	public final int PRESENTATION_DRAW = 7;
	public final int PRESENTATION_FULLSCREEN = 8;
	public final int PRESENTATION_CLEANUP = 9;

	public final int NUMERIC_KEY_PRESSED = 1;


	private API_hiDBLUE API = new API_hiDBLUE();
	private int AppMode = INITIAL;
	private String BTPaired = null;

	private SensorManager SensorManager;
	private ImageView image;
	private TextView recipient;
	private EditText smsRecipient;
	private TextView content;
	private EditText smsContent;
	private Button smsSend;

	private Timer timer = new Timer();
	private boolean timerApplicable = false;
	private boolean mouseButtonLeft;
	private boolean mouseButtonRight;
	private float mouseXmove;
	private float mouseYmove;
	private float mouseXtouch;
	private float mouseYtouch;
	private float mouseXtouchOld;
	private float mouseYtouchOld;
	private int smsMode;

	private boolean buttonPressed = false;
	private boolean presentationMarkerActive = false;
	private boolean presentationLaserPointerActive = false;
	
	private int lenRecipient = 0;
	private int lenContent = 0;
    private boolean focusRecipient = true;
	private boolean preventLoopback = false;
	private int modeInitialDelay;



	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	    LocalBroadcastManager.getInstance(this).registerReceiver(APIDataReceiver, new IntentFilter("hiDBLUE-Data"));

		SensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		SensorManager.registerListener(this, SensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY), SensorManager.SENSOR_DELAY_NORMAL);
		//SensorManager.registerListener(this, SensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME);

		image = (ImageView) findViewById(R.id.imageView1);
		image.setImageResource(R.drawable.init);

		recipient = (TextView) findViewById(R.id.textView1);
		smsRecipient = (EditText) findViewById(R.id.editText1);
	    smsRecipient.addTextChangedListener(new TextWatcher(){		// TODO: handle cases when letters are inserted instead of added at the end
			@Override
			public void afterTextChanged(Editable arg0) {
				if ((AppMode == SMS) && (preventLoopback == false) && (modeInitialDelay == 0)) {
					if (focusRecipient == false) {
						SendPacketToPC((byte)(0x01), (byte)(0x01));
						focusRecipient = true; }
					if (smsRecipient.getText().length() < lenRecipient) {
						SendPacketToPC((byte)(0x01), (byte)(0x12));
					} else if (smsRecipient.getText().length() > 0) {
						SendPacketToPC((byte)(0x02), smsRecipient.getText().toString().getBytes()[smsRecipient.getText().length() - 1]); }	}
				preventLoopback = false; }
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count, int after) {
				lenRecipient = smsRecipient.getText().length(); }
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) { }
	    });

		content = (TextView) findViewById(R.id.textView2);
		smsContent = (EditText) findViewById(R.id.editText2);
		smsContent.addTextChangedListener(new TextWatcher(){		// TODO: handle cases when letters are inserted instead of added at the end
			@Override
			public void afterTextChanged(Editable arg0) {
				if ((AppMode == SMS) && (preventLoopback == false) && (modeInitialDelay == 0)) {
					if (focusRecipient == true) {
						SendPacketToPC((byte)(0x01), (byte)(0x02));
						focusRecipient = false; }
					if (smsContent.getText().length() < lenContent) {
						SendPacketToPC((byte)(0x01), (byte)(0x12));
					} else if (smsContent.getText().length() > 0){
						SendPacketToPC((byte)(0x02), smsContent.getText().toString().getBytes()[smsContent.getText().length() - 1]); }	}
				preventLoopback = false; }
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count, int after) {
				lenContent = smsContent.getText().length(); }
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) { }
	    });

		smsSend = (Button) findViewById(R.id.button1);
		smsSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
            	SendSMS();
        }	});

		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				TimerEntry(); }
		}, 1000, 50);
	}

    @Override
	public void onDestroy() {
    	timer.cancel();
    	SensorManager.unregisterListener(this);
    	LocalBroadcastManager.getInstance(this).unregisterReceiver(APIDataReceiver);
		if (API.GetState() != API.STATE_LIB_INIT) {
			unregisterReceiver(BluetoothEventsReceiver); }
    	API.Dispose();
		super.onDestroy();
	}


	@Override
	public boolean onTouchEvent(MotionEvent e) {
	    float x = e.getX();
	    float y = e.getY();

	    switch (e.getAction()) {
	        case MotionEvent.ACTION_DOWN:
	        	if(ProcessTouch(x, y) == true) {
	        		ProcessAction(); }
	        	break;
	        case MotionEvent.ACTION_UP:
	        	ProcessUnTouch();
	        	break;
	        case MotionEvent.ACTION_MOVE:
	        	if(ProcessSlide(x, y) == true) {
	        		ProcessAction(); }
	        	break; }
	    return true;
	}

	private boolean ProcessTouch(float x, float y) {
		int count1;
		int count2;
		int lowerBorder;
		int upperBorder;

		// check if home button touched
		switch (AppMode) {
			case PRESENTATION:
			case MOUSE:
			case TOUCHPAD:
			case PASSWORDS:
				if ((x <= 50) && (y >= 290)) {
					if (AppMode == PRESENTATION) {
						// TODO: Here we simply assume that we're in full-screen presentation mode
						SetUSBMessage(PRESENTATION_CLEANUP); }
					AppMode = MAIN;
					return true; }
				break;
			case NUMERIC:
				if ((x <= 35) && (y <= 65)) {
					AppMode = MAIN;
					return true; }
				break; }

		// check if any other applicable region touched
		switch (AppMode) {
			case INITIAL:
				if ((x < 20) || (x > 220)) return false;	// outside buttons left/right borders
				if (y >= 290) {				// we touched button
					HandleBTInit();
					return false; }		// no further processing needed
				break;
			case MAIN:
				if ((x < 20) || (x > 220)) return false;	// outside buttons left/right borders
				for (count1=0; count1<7; count1++) {
					lowerBorder = 40 + (count1 * 42);
					upperBorder = 75 + (count1 * 42);
					if ((y >= lowerBorder) && (y <= upperBorder)) {		// we touched button
						AppMode = count1 + 1; 
						return true; }	}
				break;
			case MOUSE:
				if ((y < 30) || (y > 130)) return false;	// outside buttons area
				if ((x >= 10) && (x <= 65)) {				// left button
					SetUSBMessage(MOUSE_BUTTON, MOUSE_BUTTON_LEFT);
					return false; }		// no further processing needed
				if ((x >= 175) && (x <= 230)) {				// right button
					SetUSBMessage(MOUSE_BUTTON, MOUSE_BUTTON_RIGHT);
					return false; }		// no further processing needed
				if ((x > 65) && (x < 175)) {				// wheel
					if (y < 80) {		// wheel up
						SetUSBMessage(MOUSE_WHEEL, 1);		// second parameter = multiply
					} else {			// wheel down
						SetUSBMessage(MOUSE_WHEEL, -1); }	// second parameter = multiply
					return false; }		// no further processing needed
				break;
			case TOUCHPAD:
				if ((y >= 30) && (y <= 89)) {
					if (x <= 125) {			// left button click
						if (x <= 80){		// just a click, don't hold the button
							if (buttonPressed == true) {
								buttonPressed = false;
								image.setImageResource(R.drawable.touchpad); }
						} else {
							buttonPressed = !buttonPressed;		// swap on/off
							if (buttonPressed == true) {
								image.setImageResource(R.drawable.touchpad_button);
							} else {
								image.setImageResource(R.drawable.touchpad); }	}
						SetUSBMessage(TOUCHPAD_BUTTON_LEFT);
					} else {				// right button click
						if (buttonPressed == true) {
							buttonPressed = false;
							image.setImageResource(R.drawable.touchpad); }
						SetUSBMessage(TOUCHPAD_BUTTON_RIGHT); }
				} else if ((y >= 90) && (y <= 289)) {
					mouseXtouch = x;
					mouseXtouch = y;
					mouseXtouchOld = x;
					mouseYtouchOld = y;
					timerApplicable = true;
					modeInitialDelay = 3; }
				return false;
			case NUMERIC:
				for (count1=0; count1<4; count1++) {
					lowerBorder = count1 * 60;
					upperBorder = 59 + (count1 * 60);
					if ((x >= lowerBorder) && (x <= upperBorder)) {
						for (count2=0; count2<5; count2++) {
							lowerBorder = 30 + (count2 * 60);
							upperBorder = 89 + (count2 * 60);
							if ((y >= lowerBorder) && (y <= upperBorder)) {
								SetUSBMessage(NUMERIC_KEY_PRESSED, count1, count2);
								return false;		// no further processing needed
				}	}	}	}
				break;
			case PASSWORDS:			// password logins
				for (count1=0; count1<2; count1++) {
					lowerBorder = 20 + (count1 * 110);
					upperBorder = 110 + (count1 * 110);
					if ((x >= lowerBorder) && (x <= upperBorder)) {
						for (count2=0; count2<3; count2++) {
							lowerBorder = 50 + (count2 * 75);
							upperBorder = 110 + (count2 * 75);
							if ((y >= lowerBorder) && (y <= upperBorder)) {
								SetUSBMessage(PASSWORDS, count1, count2);
								return false;		// no further processing needed
				}	}	}	}
				break;
			case PRESENTATION:
				if ((y >= 30) && (y <= 89)) {
					if (x <= 59) {				// previous
						SetUSBMessage(PRESENTATION_PREVIOUS);
					} else if (x >= 180) {		// next
						SetUSBMessage(PRESENTATION_NEXT);
					} else {					// marker or laser pointer
						buttonPressed = !buttonPressed;		// swap on/off
						mouseButtonLeft = true;
						if (x <=119) {			// marker
							if (presentationLaserPointerActive == true) {
								presentationLaserPointerActive = false;
								SetUSBMessage(PRESENTATION_POINTER_HIDE);
								buttonPressed = true; }
							if (buttonPressed == true) {
								SetUSBMessage(PRESENTATION_HIGHLIGHT_START);
								image.setImageResource(R.drawable.presentation_marker);
								presentationMarkerActive = true;
							} else {
								SetUSBMessage(PRESENTATION_HIGHLIGHT_STOP);
								image.setImageResource(R.drawable.presentation);
								presentationMarkerActive = false; }
						} else {				// laser pointer
							if (presentationMarkerActive == true) {
								presentationMarkerActive = false;
								SetUSBMessage(PRESENTATION_HIGHLIGHT_STOP);
								buttonPressed = true; }
							if (buttonPressed == true) {
								SetUSBMessage(PRESENTATION_POINTER_SHOW);
								image.setImageResource(R.drawable.presentation_laser);
								presentationLaserPointerActive = true;
							} else {
								SetUSBMessage(PRESENTATION_POINTER_HIDE);
								image.setImageResource(R.drawable.presentation);
								presentationLaserPointerActive = false; }
					}	}
					return false;
				} else {
					if ((y >= 90) && (y <= 289)) {
						mouseXtouch = x;
						mouseXtouch = y;
						mouseXtouchOld = x;
						mouseYtouchOld = y;
						timerApplicable = true;
						modeInitialDelay = 3;
						return false; }	}
				break; }
	    //Toast.makeText(getApplicationContext(), "x= " + x + " y=" + y, Toast.LENGTH_SHORT).show();
		return false;
	}
	
	private void ProcessUnTouch() {
		switch (AppMode) {
			case MOUSE:
				SetUSBMessage(MOUSE_BUTTON, MOUSE_BUTTON_NONE);
				break;
			case TOUCHPAD:
				mouseButtonRight = false;
			case PRESENTATION:
				timerApplicable = false;
				break; }
	}

	private boolean ProcessSlide(float x, float y) {
		if ((AppMode != TOUCHPAD) && (AppMode != PRESENTATION)) {
			return false; }
		mouseXtouch = x;
		mouseYtouch = y;
		if (modeInitialDelay > 0) {		// filter "noise"
			mouseXtouchOld = mouseXtouch;
			mouseYtouchOld = mouseYtouch; }
		return false;
	}

	// Processing SMS demo data received from PC demo app, function SendPacket
	private void ProcessReceivedAPIData(byte[] data) {
		byte[] arr = new byte[1];
		String newChar;
		
		arr[0] = data[1];
		switch (data[0]) {
			case 1:		// control character
				switch (data[1]) {
					case 0x01:		// focus to Recipient field
						smsRecipient.requestFocus();
						smsMode = 1;
						return;
					case 0x02:		// focus to content field
						smsContent.requestFocus();
						smsMode = 2;
						return;
					case 0x12:		// 'Backspace'
						preventLoopback = true;
						if (smsMode == 1) {		// focus is in Recipient field
							if (smsRecipient.getText().length() > 1) {
								smsRecipient.setText(smsRecipient.getText().subSequence(0, smsRecipient.getText().length()-1));
							} else {
								smsRecipient.setText(""); }
						} else {				// focus is in Content field
							if (smsContent.getText().length() > 1) {
								smsContent.setText(smsContent.getText().subSequence(0, smsContent.getText().length()-1));
							} else {
								smsContent.setText(""); }	}
						break;
					case (byte)0xF0:	// 'SEND'
						preventLoopback = true;
						SendSMS();
						break; }
				break;
			case 2:		// ordinary character
				try {
					newChar = new String(arr, "UTF8");
				} catch (UnsupportedEncodingException e) {
					newChar = "?"; }
				preventLoopback = true;
				switch (smsMode) {
					case 1:		// recipient
						smsRecipient.setText(smsRecipient.getText() + newChar);
						break;
					case 2:		// content
						smsContent.setText(smsContent.getText() + newChar);
						break; }
				break; }
	}

	// Packet is delivered to PC demo app, handler HandleReceivedPacket 
	private void SendPacketToPC(byte header, byte content) {
		byte dataPacket[] = new byte[2];
		dataPacket[0] = header;
		dataPacket[1] = content;
		API.ForwardPacket(dataPacket);
	}
 
	
	public void onSensorChanged(SensorEvent event) {
		switch (event.sensor.getType()) {
		case Sensor.TYPE_GRAVITY:
			mouseXmove = event.values[0] / 2;	// half sensitivity
			mouseYmove = event.values[2] / 2;	// half sensitivity
//			break;
//		case Sensor.TYPE_ACCELEROMETER:
//			mouseXmove = event.values[0] * 5;
//			mouseYmove = event.values[1] * 5;
			break; }
	}


	private void TimerEntry(){
		this.runOnUiThread(ProcessTimer());
	}

	private Runnable ProcessTimer(){
		if (modeInitialDelay > 0) {
			modeInitialDelay--; }

		if (timerApplicable == false) {
			return null; }
		switch (AppMode) {
			case MOUSE:
				if ((mouseButtonLeft == true) || (mouseButtonRight == true) || (mouseXmove < -1) || (mouseXmove > 1) || (mouseYmove < -1) || (mouseYmove > 1)) {
					SetUSBMessage(MOUSE_VARIABLES);}
				break;
			case PRESENTATION:
				if (mouseButtonLeft == false) {
					return null; }
				if ((Math.abs(mouseXtouch - mouseXtouchOld) > 5) || (Math.abs(mouseYtouch - mouseYtouchOld) > 5)) {
					if ((Math.abs(mouseXtouch - mouseXtouchOld) < 100) && (Math.abs(mouseYtouch - mouseYtouchOld) < 100)) {		// simple filtering
						SetUSBMessage(PRESENTATION_DRAW); }
					mouseXtouchOld = mouseXtouch;
					mouseYtouchOld = mouseYtouch; }
				break;
			case TOUCHPAD:
				if ((Math.abs(mouseXtouch - mouseXtouchOld) > 5) || (Math.abs(mouseYtouch - mouseYtouchOld) > 5)) {
					if ((Math.abs(mouseXtouch - mouseXtouchOld) < 100) && (Math.abs(mouseYtouch - mouseYtouchOld) < 100)) {		// simple filtering
						SetUSBMessage(TOUCHPAD_SLIDE); }
					mouseXtouchOld = mouseXtouch;
					mouseYtouchOld = mouseYtouch; }
				break; }
		return null;
	}


	private void ProcessAction() {
		timerApplicable = false;
		modeInitialDelay = 10;						// = 500ms, mainly for cutting-out initial EditText controls events; DIRTY WORKAROUND !  TODO: improve this mechanism
		switch (AppMode) {
			case MAIN:
				image.setImageResource(R.drawable.main);
				recipient.setVisibility(View.INVISIBLE);
				smsRecipient.setVisibility(View.INVISIBLE);
				content.setVisibility(View.INVISIBLE);
				smsContent.setVisibility(View.INVISIBLE);
				smsSend.setVisibility(View.INVISIBLE);
				// TODO: perform some housekeeping tasks, like line below
				buttonPressed = false;
				break;
			case MOUSE:
				image.setImageResource(R.drawable.mouse);
				timerApplicable = true;
				mouseButtonLeft = false;
				mouseButtonRight = false;
				break;
			case PRESENTATION:
				image.setImageResource(R.drawable.presentation);
				// TODO: Here we simply assume that PowerPoint app is up&running, with presentation file loaded, therefore we're entering full-screen presentation mode
				SetUSBMessage(PRESENTATION_FULLSCREEN);
				break;
			case TOUCHPAD:
				image.setImageResource(R.drawable.touchpad);
				break;
			case NUMERIC:
				image.setImageResource(R.drawable.numeric);
				break;
			case PASSWORDS:
				image.setImageResource(R.drawable.passwords);
				break;
			case BARCODE:
				IntentIntegrator integrator = new IntentIntegrator(this);
				integrator.initiateScan();
				AppMode = MAIN;
				break;
			case SMS:
				image.setImageResource(R.drawable.sms);
				recipient.setVisibility(View.VISIBLE);
				recipient.bringToFront();
				smsRecipient.setVisibility(View.VISIBLE);
				smsRecipient.bringToFront();
				content.setVisibility(View.VISIBLE);
				content.bringToFront();
				smsContent.setVisibility(View.VISIBLE);
				smsContent.bringToFront();
				smsSend.setVisibility(View.VISIBLE);
				smsSend.bringToFront();
				smsMode = 1;
				smsRecipient.setText("");
				smsContent.setText("");
				break;
		}
	}


	private void SendUSBString(char[] inChars) {
		byte[] scanCode;
		int count;
		for (count=0; count<inChars.length; count++) {
			scanCode = GetUSBKeyCode(inChars[count]);
		    API.TriggerKeyEvent(scanCode[0], scanCode[1], true); }
	}

	// ONLY LIMITED MAPPING SET IS IMPLEMENTED; for complete implementation information, check USB doc 
	private byte[] GetUSBKeyCode(char inChar) {
		byte[] scanCode = new byte[2];
		if((inChar >= 'a') && (inChar <= 'z')) {
			scanCode[0] = (byte)(inChar - 'a' + 4);
			scanCode[1] = 0;
			return scanCode; }
		if((inChar >= 'A') && (inChar <= 'Z')) {
			scanCode[0] = (byte)(inChar - 'A' + 4);
			scanCode[1] = 2;
			return scanCode; }
		if((inChar >= '1') && (inChar <= '9')) {
			scanCode[0] = (byte)(inChar - '1' + 30);
			scanCode[1] = 0;
			return scanCode; }
		if(inChar == '0') {
			scanCode[0] = (byte)39;
			scanCode[1] = 0;
			return scanCode; }
		if(inChar == ' ') {
			scanCode[0] = (byte)44;
			scanCode[1] = 0;
			return scanCode; }
		scanCode[0] = 56;	// return '?' for any unimplemented mapping
		scanCode[1] = 2;
		return scanCode;
	}

	private void SendSMS() {
    	// TODO: Implement actual SMS sending functionality
    	Toast.makeText(getApplicationContext(), "The SMS has been sent", Toast.LENGTH_SHORT).show();
    	if (preventLoopback == false) {
    		SendPacketToPC((byte)(0x01), (byte)(0xF0)); }
    	preventLoopback = false;
    	AppMode = MAIN;
    	ProcessAction();
	}

	
	private void SetUSBMessage(int action) {
		SetUSBMessage(action, 0, 0); }
	private void SetUSBMessage(int action, int value) {
		SetUSBMessage(action, value, 0); }
	private void SetUSBMessage(int action, int value1, int value2) {
		int BTState;
		int BTStateDetails;
		float diffX = 0;
		float diffY = 0;

		BTState = API.GetState();
		if (BTState != API.STATE_ONLINE) {
			BTStateDetails = API.GetStateDetails();
	    	//Toast.makeText(getApplicationContext(), "Unexpected state, code: " + BTState + ", details code: " + BTStateDetails, Toast.LENGTH_LONG).show();
	    	if(BTState == API.STATE_ERROR) {
	    		HandleBTError(BTStateDetails); }
			return;}

		switch (AppMode) {
			case MOUSE:
				switch (action) {
					case MOUSE_MOVE:
						API.TriggerMouseEvent((byte)value1, (byte)value2, (byte)0, false, false, false, false);
						break;
					case MOUSE_WHEEL:
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)value1, false, false, false, false);
						break;
					case MOUSE_BUTTON:
						mouseButtonLeft = false;
						mouseButtonRight = false;
						switch (value1) {
							case MOUSE_BUTTON_LEFT:
								mouseButtonLeft = true;
								break;
							case MOUSE_BUTTON_RIGHT:
								mouseButtonRight = true;
								break;
							case MOUSE_BUTTON_NONE:		// we've already reset the buttons above
								break; }
							API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, mouseButtonLeft, mouseButtonRight, false, false);
					case MOUSE_VARIABLES:			// mouse data combined from global variables
						API.TriggerMouseEvent((byte)(Math.abs(mouseXmove) * mouseXmove * -1), (byte)(Math.abs(mouseYmove) * mouseYmove * -1), (byte)0, mouseButtonLeft, mouseButtonRight, false, false);
						break; }
				break;
			case TOUCHPAD:
				switch (action) {
					case TOUCHPAD_BUTTON_LEFT:
						mouseButtonLeft = true;
						break;
					case TOUCHPAD_BUTTON_RIGHT:
						mouseButtonRight = true;
						break;
					case TOUCHPAD_SLIDE:
						diffX = mouseXtouch - mouseXtouchOld;
						diffY = mouseYtouch - mouseYtouchOld;
						if ((diffX > 25) || (diffY > 25)) {		// increase sensitivity at larger moves
							diffX *= 2;
							diffY *= 2; }
						if (diffX < -127) {
							diffX = -127; }
						if (diffX > 127) {
							diffX = 127; }
						if (diffY < -127) {
							diffY = -127; }
						if (diffY > 127) {
							diffY = 127; }
						break; }
				API.TriggerMouseEvent((byte)diffX, (byte)diffY, (byte)0, mouseButtonLeft, mouseButtonRight, false, buttonPressed);
				if ((buttonPressed == false) && (mouseButtonLeft == true)) {
					mouseButtonLeft = false;
					API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, false, false, false, false); }
				break;
			case NUMERIC:
				switch (action) {
					case NUMERIC_KEY_PRESSED:
						// ORDER: "N/A (menu home)", "div", "mul", "minus", "7", "8", "9", "+", "4", "5", "6", "+", "1", "2", "3", "enter", "0", "0", "dot", "enter"};
						//byte keyNumericCode[] = {0, 84, 85, 86, 95, 96, 97, 87, 92, 93, 94, 87, 89, 90, 91, 88, 98, 98, 99, 88};		// VALID WHEN NUM LOCK IS *ON*; see HID Usage Tables document, page 55 & 56
						byte keyNumericCode[] = {0, 84, 85, 86, 36, 37, 38, 87, 33, 34, 35, 87, 30, 31, 32, 88, 39, 39, 55, 88};		// see HID Usage Tables document, page 55 & 56
					    API.TriggerKeyEvent(keyNumericCode[value1 + (value2 * 4)], (byte)0, true);		// key clicked
						break; }
				break;
			case PASSWORDS:		// passwords login
				// NOTE: password strings are defined here instead of at beginning of the file to make sure that you at least skim this code instead of blindly copy & paste it..  ;-)))
				String keyLogin[] = {"SkypeUsername", "SkypePassword", "FacebookUsername", "FacebookPassword", "LinkedInUsername", "LinkedInPassword", "FlickrUsername", "FlickrPassword", "TwitterUsername", "TwitterPassword", "PayPalUsername", "PayPalPassword"};
				SendUSBString(keyLogin[((value1 + (value2 * 2)) * 2)].toCharArray());
			    API.TriggerKeyEvent((byte)43, (byte)0, true);		// 'Tab' clicked
				SendUSBString(keyLogin[((value1 + (value2 * 2)) * 2) + 1].toCharArray());
			    API.TriggerKeyEvent((byte)40, (byte)0, true);		// 'Return' clicked
				break;
			case PRESENTATION:
				switch (action) {
					case PRESENTATION_PREVIOUS:
					    API.TriggerKeyEvent((byte)80, (byte)0, true);		// 'Left Arrow' key clicked
						break;
					case PRESENTATION_NEXT:
					    API.TriggerKeyEvent((byte)79, (byte)0, true);		// 'Right Arrow' key clicked
						break;
					case PRESENTATION_POINTER_SHOW:
					    API.TriggerKeyEvent((byte)0, (byte)1, false);		// 'Left Ctrl' key pressed (and keep it pressed)
						mouseButtonLeft = true;
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, true, false, false, true);	// mouse left button clicked (& keep it pressed)
						break; 
					case PRESENTATION_POINTER_HIDE:
					    API.TriggerKeyEvent((byte)0, (byte)0, false);		// 'Ctrl' key released
						mouseButtonLeft = false;
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, false, false, false, false);	// no mouse button clicked
						break;
					case PRESENTATION_HIGHLIGHT_START:
						mouseButtonLeft = true;
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, false, true, false, false);	// mouse right button clicked
					    API.TriggerKeyEvent((byte)18, (byte)0, true);		// 'o' key pressed
					    //API.TriggerKeyEvent((byte)19, (byte)0, true);		// 'p' key pressed (and 'o' key released) -> PEN
					    API.TriggerKeyEvent((byte)11, (byte)0, true);		// 'h' key pressed (and 'o' key released) -> MARKER
						break;
					case PRESENTATION_HIGHLIGHT_STOP:
						mouseButtonLeft = false;
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, false, false, false, false);	// no mouse button clicked
					    API.TriggerKeyEvent((byte)41, (byte)0, true);		// 'Esc' pressed
						break;
					case PRESENTATION_DRAW:
						diffX = (mouseXtouch - mouseXtouchOld) * 2;			// double the sensitivity
						diffY = (mouseYtouch - mouseYtouchOld) * 2;
						if (diffX < -127) {
							diffX = -127; }
						if (diffX > 127) {
							diffX = 127; }
						if (diffY < -127) {
							diffY = -127; }
						if (diffY > 127) {
							diffY = 127; }
						API.TriggerMouseEvent((byte)diffX, (byte)diffY, (byte)0, mouseButtonLeft, mouseButtonRight, false, true);
						break;
					case PRESENTATION_FULLSCREEN:
					    API.TriggerKeyEvent((byte)62, (byte)0, true);		// 'F5' pressed
						break;
					case PRESENTATION_CLEANUP:
					    API.TriggerKeyEvent((byte)41, (byte)0, true);		// 'Esc' pressed
					    API.TriggerKeyEvent((byte)41, (byte)0, true);		// 'Esc' pressed
					    API.TriggerKeyEvent((byte)7, (byte)0, true);		// 'd' pressed for Discard option
						mouseButtonLeft = false;
						mouseButtonRight = false;
						API.TriggerMouseEvent((byte)0, (byte)0, (byte)0, false, false, false, false);	// no mouse button clicked
						presentationMarkerActive = false;
						presentationLaserPointerActive = false;
						break; }
				break; }
	}


	private void HandleBTInit() {
		int BTState;
		int BTStateDetails;
	
		BTState = API.GetState();
		BTStateDetails = API.GetStateDetails();

		if (BTState == API.STATE_ERROR) {
		    if (BTStateDetails != API.ERROR_EXCEPTION) {
		    	Toast.makeText(getApplicationContext(), "Ups, ERROR...  :-(     code: " + BTStateDetails, Toast.LENGTH_LONG).show();
		    } else {
		    	Toast.makeText(getApplicationContext(), "Ups, ERROR...  :-(     " + API.GetLastException().getMessage(), Toast.LENGTH_LONG).show(); }
			HandleBTError(BTStateDetails);
			return; }
		if (BTState == API.STATE_BUSY) {
		    Toast.makeText(getApplicationContext(), "Busy...", Toast.LENGTH_LONG).show();
			return; }
		if (BTState == API.STATE_LIB_INIT) {
		    Toast.makeText(getApplicationContext(), "API initializing...", Toast.LENGTH_LONG).show();
			API.Init(this, BluetoothEventsReceiver);
			return; }
		if (BTState == API.STATE_DEVICES_SCAN_REQUIRED) {
		    Toast.makeText(getApplicationContext(), "Search for Bluetooth Devices has been started...", Toast.LENGTH_LONG).show();
			API.StartDiscovery();
			return; }
		if (BTState == API.STATE_DEVICES_SCAN_COMPLETED) {
			String[][] strAvailableDevices = API.GetPairedDevices();
/*			if (strAvailableDevices == null) {		// we don't have available paired hiDBLUE device in range, check if there is any unpaired one
				strAvailableDevices = API.GetDiscoveredDevices();
				if (strAvailableDevices == null) {		// there are also no unpaired hiDBLUE devices in range
					Toast.makeText(getApplicationContext(), "Unable to find hiDBLUE device... Recheck your system and restart the application.", Toast.LENGTH_LONG).show();
					return; }
				// TODO: implement a way of hiDBLUE MAC selection. In case of single hiDBLUE device, MAC=strAvailableDevices[0][1];
			    Toast.makeText(getApplicationContext(), "Pairing hiDBLUE Device...", Toast.LENGTH_LONG).show();
			    BTPaired = strAvailableDevices[0][1];	// save MAC for usage at STATE_DEVICE_PAIRED event
			    API.Pair(BTPaired);						// request Pairing
				return; }
			// TODO: implement way of hiDBLUE MAC selection. In case of single hiDBLUE device, MAC=strAvailableDevices[0][1];
			Toast.makeText(getApplicationContext(), "Connecting to hiDBLUE device...", Toast.LENGTH_LONG).show();
*/
			Toast.makeText(getApplicationContext(), "Connecting to hiDBLUE device...", Toast.LENGTH_LONG).show();
			API.Connect(strAvailableDevices[0][1]);		//hardcoded substitute: API.Connect("00:06:66:4D:54:86");
			return; }
		if (BTState == API.STATE_DEVICE_PAIRED) {	// we connect the device which got paired above, no need to make any MAC selection here, simply use the same MAC as for BT.Pair call
			Toast.makeText(getApplicationContext(), "Connecting to hiDBLUE device...", Toast.LENGTH_LONG).show();
			API.Connect(BTPaired);
			return; }
		if (BTState == API.STATE_CONNECTED) {
		    Toast.makeText(getApplicationContext(), "Starting communication with hiDBLUE device...", Toast.LENGTH_LONG).show();
			API.StartCommunication();
			return; }
		if (BTState == API.STATE_ONLINE) {
			AppMode = MAIN;
			ProcessAction(); }
    }

	private void HandleBTError(int error) {
		if (error == API.ERROR_BLUETOOTH_NOT_PRESENT) {
			// REASON: Cannot run hiDBLUE app on a device without bluetooth hardware...   O;-/
			// TODO: write error msg and terminate the app
			return; }
		// 
		if (error == API.ERROR_PEER_NOT_FOUND) {
			// REASON: Improper MAC address specified. Recheck, rescan... ?
			// TODO: possible approach (if not MAC typo) is to rescan -> API.ForceState(STATE_DEVICES_SCAN_REQUIRED)  or to start over -> call API.Dispose() & API.Init()
			return; }
		if (error == API.ERROR_PAIRING_FAILED) {
			// REASON: Improper PIN entered
			// TODO: write error msg then revert state machine -> API.ForceState(STATE_DEVICES_SCAN_REQUIRED)  or to start over -> call API.Dispose() & API.Init()
			return; }
		if (error == API.ERROR_SOCKET_NOT_AVAILABLE) {
			// REASON: Something went wrong during connect (device just got out of range, computer turned off, stick pulled-out, etc.)
			// TODO: possible solution to reconnect -> call API.ForceState(STATE_CONNECT_REQUIRED)
			return; }
		if (error == API.ERROR_STREAM_NOT_OPENED) {
			// REASON: Same as ERROR_SOCKET_NOT_AVAILABLE 
			// TODO: see ERROR_SOCKET_NOT_AVAILABLE
			return; }
		if (error == API.ERROR_BUFFER_OVERFLOW) {
			// REASON: Frequency of calling TriggerMouseEvent/TriggerKeyEvent API functions is too high.
			//			Development-time solution: decrease frequency of mentioned functions calls (including increasing timer interval)
			//			Run-time solution: reset the error and call the TriggerMouseEvent/TriggerKeyEvent API function again
			// TODO:  -> call ResetErrorState() & API.TriggerMouseEvent() / API.TriggerKeyEvent()
			API.ResetErrorState();
			return; }
		if (error == API.ERROR_EXCEPTION) {
			// REASON: Auch, internal library exception...  ;-?
			// TODO: display fatal error, then reinit the system -> call API.Dispose() & API.Init()
			// exception details -> API.GetLastException().getMessage()
			return; }
	}

	
	private final BroadcastReceiver BluetoothEventsReceiver = new BroadcastReceiver() {
		@Override
	    public void onReceive(Context context, Intent intent) {
			API.ForwardBroadcast(intent);
		}
	};

	private final BroadcastReceiver APIDataReceiver = new BroadcastReceiver() {
		@Override
	    public void onReceive(Context context, Intent intent) {
			Bundle bundle = intent.getBundleExtra("Packet");
    		if (bundle.getString("Content") == "Raw") {
    			int datalen = bundle.getInt("DataLen");
        		byte[] data = new byte[datalen];
        		data = bundle.getByteArray("Data");
        		ProcessReceivedAPIData(data);
    		}
		}
	};

	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
		IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
		if (scanResult != null) {
			String scanData = scanResult.toString();
			int startPtr = scanData.indexOf("Contents: ") + 10;		// Parse result from ZXING activity
			int endPtr = scanData.indexOf("Raw bytes: (") - 1;
			SendUSBString(scanData.substring(startPtr, endPtr).toCharArray());
			API.TriggerKeyEvent((byte)40, (byte)0, true);		// Add 'Return' at the end
		}
	}


	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
	}

}
