// Copyright (C) 2002-2007 Nikolaus Gebhardt
// Copyright (C) 2007-2011 Christian Stehno
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CIrrDeviceAndroid.h"

#include "os.h"
#include "CFileSystem.h"
#include "CAndroidAssetReader.h"
#include "CAndroidAssetFileArchive.h"
#include "CKeyEventWrapper.h"
#include "CEGLManager.h"
#include "ISceneManager.h"
#include "IGUIEnvironment.h"
#include "CEGLManager.h"

namespace irr
{
	namespace video
	{
		IVideoDriver* createOGLES1Driver(const SIrrlichtCreationParameters& params,
			io::IFileSystem* io, video::IContextManager* contextManager);

		IVideoDriver* createOGLES2Driver(const SIrrlichtCreationParameters& params,
			io::IFileSystem* io, video::IContextManager* contextManager);
	}
}

namespace irr
{

CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters& param)
	: CIrrDeviceStub(param), Accelerometer(0), Gyroscope(0), Focused(false), Initialized(false), Paused(true), JNIEnvAttachedToVM(0)
{
#ifdef _DEBUG
	setDebugName("CIrrDeviceAndroid");
#endif

	// Get the interface to the native Android activity.
	Android = (android_app*)(param.PrivateData);

	// Set the private data so we can use it in any static callbacks.
	Android->userData = this;

	// Set the default command handler. This is a callback function that the Android
	// OS invokes to send the native activity messages.
	Android->onAppCmd = handleAndroidCommand;

	createKeyMap();

	// Create a sensor manager to receive touch screen events from the java activity.
	SensorManager = ASensorManager_getInstance();
	SensorEventQueue = ASensorManager_createEventQueue(SensorManager, Android->looper, LOOPER_ID_USER, 0, 0);
	Android->onInputEvent = handleInput;

	// Create EGL manager.
	ContextManager = new video::CEGLManager();

	os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);

	do
	{
		s32 Events = 0;
		android_poll_source* Source = 0;

		while ((ALooper_pollAll(((Focused && !Paused) || !Initialized) ? 0 : -1, 0, &Events, (void**)&Source)) >= 0)
		{
			if(Source)
				Source->process(Android, Source);
		}
	}
	while(!Initialized);
}


CIrrDeviceAndroid::~CIrrDeviceAndroid()
{
	if (GUIEnvironment)
	{
		GUIEnvironment->drop();
		GUIEnvironment = 0;
	}

	if (SceneManager)
	{
		SceneManager->drop();
		SceneManager = 0;
	}

	if (VideoDriver)
	{
		VideoDriver->drop();
		VideoDriver = 0;
	}
}

bool CIrrDeviceAndroid::run()
{
	if (!Initialized)
		return false;

	os::Timer::tick();

	s32 id;
	s32 Events = 0;
	android_poll_source* Source = 0;

	while ((id = ALooper_pollAll(((Focused && !Paused) || !Initialized) ? 0 : -1, 0, &Events, (void**)&Source)) >= 0)
	{
		if(Source)
			Source->process(Android, Source);

		// if a sensor has data, we'll process it now.
        if (id == LOOPER_ID_USER)
		{
			ASensorEvent sensorEvent;
			while (ASensorEventQueue_getEvents(SensorEventQueue, &sensorEvent, 1) > 0)
			{
				switch (sensorEvent.type)
				{
					case ASENSOR_TYPE_ACCELEROMETER:
						SEvent accEvent;
						accEvent.EventType = EET_ACCELEROMETER_EVENT;
						accEvent.AccelerometerEvent.X = sensorEvent.acceleration.x;
						accEvent.AccelerometerEvent.Y = sensorEvent.acceleration.y;
						accEvent.AccelerometerEvent.Z = sensorEvent.acceleration.z;

						postEventFromUser(accEvent);
					break;

					case ASENSOR_TYPE_GYROSCOPE:
						SEvent gyroEvent;
						gyroEvent.EventType = EET_GYROSCOPE_EVENT;
						gyroEvent.GyroscopeEvent.X = sensorEvent.vector.x;
						gyroEvent.GyroscopeEvent.Y = sensorEvent.vector.y;
						gyroEvent.GyroscopeEvent.Z = sensorEvent.vector.z;

						postEventFromUser(gyroEvent);
					break;
					default:
					break;
				}
			}
		}

		if(!Initialized)
			break;
	}

	return Initialized;
}

void CIrrDeviceAndroid::yield()
{
	struct timespec ts = {0,1};
	nanosleep(&ts, NULL);
}

void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
{
	const bool wasStopped = Timer ? Timer->isStopped() : true;

	struct timespec ts;
	ts.tv_sec = (time_t) (timeMs / 1000);
	ts.tv_nsec = (long) (timeMs % 1000) * 1000000;

	if (pauseTimer && !wasStopped)
		Timer->stop();

	nanosleep(&ts, NULL);

	if (pauseTimer && !wasStopped)
		Timer->start();
}

void CIrrDeviceAndroid::setWindowCaption(const wchar_t* text)
{
}

bool CIrrDeviceAndroid::isWindowActive() const
{
	return (Focused && !Paused);
}

bool CIrrDeviceAndroid::isWindowFocused() const
{
	return Focused;
}

bool CIrrDeviceAndroid::isWindowMinimized() const
{
	return !Focused;
}

void CIrrDeviceAndroid::closeDevice()
{
	ANativeActivity_finish(Android->activity);
}

void CIrrDeviceAndroid::setResizable(bool resize)
{
}

void CIrrDeviceAndroid::minimizeWindow()
{
}

void CIrrDeviceAndroid::maximizeWindow()
{
}

void CIrrDeviceAndroid::restoreWindow()
{
}

core::position2di CIrrDeviceAndroid::getWindowPosition()
{
	return core::position2di(0, 0);
}

E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
{
	return EIDT_ANDROID;
}

void CIrrDeviceAndroid::handleAndroidCommand(android_app* app, int32_t cmd)
{
	CIrrDeviceAndroid* device = (CIrrDeviceAndroid*)app->userData;

	SEvent event;
	event.EventType = EET_SYSTEM_EVENT;
	event.SystemEvent.EventType = ESET_ANDROID_CMD;
	event.SystemEvent.AndroidCmd.Cmd = cmd;
	if ( device->postEventFromUser(event) )
		return;

	switch (cmd)
	{
		case APP_CMD_INPUT_CHANGED:
			os::Printer::log("Android command APP_CMD_INPUT_CHANGED", ELL_DEBUG);
		break;
		case APP_CMD_WINDOW_RESIZED:
			os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG);
		break;
		case APP_CMD_WINDOW_REDRAW_NEEDED:
			os::Printer::log("Android command APP_CMD_WINDOW_REDRAW_NEEDED", ELL_DEBUG);
		break;
		case APP_CMD_SAVE_STATE:
			os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
		break;
		case APP_CMD_CONTENT_RECT_CHANGED:
			os::Printer::log("Android command APP_CMD_CONTENT_RECT_CHANGED", ELL_DEBUG);
		break;
		case APP_CMD_CONFIG_CHANGED:
			os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG);
		break;
		case APP_CMD_LOW_MEMORY:
			os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG);
		break;
		case APP_CMD_START:
			os::Printer::log("Android command APP_CMD_START", ELL_DEBUG);
		break;
		case APP_CMD_INIT_WINDOW:
			os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
			device->getExposedVideoData().OGLESAndroid.Window = app->window;

			if (device->CreationParams.WindowSize.Width == 0 || device->CreationParams.WindowSize.Height == 0)
			{
				device->CreationParams.WindowSize.Width = ANativeWindow_getWidth(app->window);
				device->CreationParams.WindowSize.Height = ANativeWindow_getHeight(app->window);
			}

			device->getContextManager()->initialize(device->CreationParams, device->ExposedVideoData);
			device->getContextManager()->generateSurface();
			device->getContextManager()->generateContext();
			device->getContextManager()->activateContext(device->getContextManager()->getContext());

			if (!device->Initialized)
			{
				io::CAndroidAssetFileArchive* assets = new io::CAndroidAssetFileArchive( device->Android->activity->assetManager, false, false);
				assets->addDirectoryToFileList("");
				device->FileSystem->addFileArchive(assets);
				assets->drop();

				device->createDriver();

				if (device->VideoDriver)
					device->createGUIAndScene();
			}
			device->Initialized = true;
		break;
		case APP_CMD_TERM_WINDOW:
			os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
			device->getContextManager()->destroySurface();
		break;
		case APP_CMD_GAINED_FOCUS:
			os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
			device->Focused = true;
		break;
		case APP_CMD_LOST_FOCUS:
			os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
			device->Focused = false;
		break;
		case APP_CMD_DESTROY:
			os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
			if ( device->JNIEnvAttachedToVM )
			{
				device->JNIEnvAttachedToVM  = 0;
				device->Android->activity->vm->DetachCurrentThread();
			}
			device->Initialized = false;
			break;
		case APP_CMD_PAUSE:
			os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
			device->Paused = true;
			break;
		case APP_CMD_STOP:
			os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
			break;
		case APP_CMD_RESUME:
			os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
			device->Paused = false;
			break;
		default:
			break;
	}
}

s32 CIrrDeviceAndroid::handleInput(android_app* app, AInputEvent* androidEvent)
{
	CIrrDeviceAndroid* device = (CIrrDeviceAndroid*)app->userData;
	s32 status = 0;

	switch ( AInputEvent_getType(androidEvent) )
	{
		case AINPUT_EVENT_TYPE_MOTION:
		{
			SEvent event;
			event.EventType = EET_TOUCH_INPUT_EVENT;

			s32 eventAction = AMotionEvent_getAction(androidEvent);
			s32 eventType =  eventAction & AMOTION_EVENT_ACTION_MASK;

#if 0
			// Useful for debugging. We might have to pass some of those infos on at some point.
			// but preferably device independent (so iphone can use same irrlicht flags).
			int32_t flags = AMotionEvent_getFlags(androidEvent);
			os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG);
			int32_t metaState = AMotionEvent_getMetaState(androidEvent);
			os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG);
			int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent);
			os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG);
#endif

			bool touchReceived = true;

			switch (eventType)
			{
			case AMOTION_EVENT_ACTION_DOWN:
			case AMOTION_EVENT_ACTION_POINTER_DOWN:
				event.TouchInput.Event = ETIE_PRESSED_DOWN;
				break;
			case AMOTION_EVENT_ACTION_MOVE:
				event.TouchInput.Event = ETIE_MOVED;
				break;
			case AMOTION_EVENT_ACTION_UP:
			case AMOTION_EVENT_ACTION_POINTER_UP:
			case AMOTION_EVENT_ACTION_CANCEL:
				event.TouchInput.Event = ETIE_LEFT_UP;
				break;
			default:
				touchReceived = false;
				break;
			}

			if (touchReceived)
			{
				// Process all touches for move action.
				if (event.TouchInput.Event == ETIE_MOVED)
				{
					s32 pointerCount = AMotionEvent_getPointerCount(androidEvent);

					for (s32 i = 0; i < pointerCount; ++i)
					{
						event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
						event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
						event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
						event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);

						device->postEventFromUser(event);
					}
				}
				else // Process one touch for other actions.
				{
					s32 pointerIndex = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;

					event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
					event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
					event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
					event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);

					device->postEventFromUser(event);
				}

				status = 1;
			}
		}
		break;
		case AINPUT_EVENT_TYPE_KEY:
		{
			SEvent event;
			event.EventType = EET_KEY_INPUT_EVENT;

			int32_t keyCode = AKeyEvent_getKeyCode(androidEvent);
			// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);

			int32_t keyAction = AKeyEvent_getAction(androidEvent);
			int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent);

			if ( keyCode >= 0 && (u32)keyCode < device->KeyMap.size() )
				event.KeyInput.Key = device->KeyMap[keyCode];
			else
				event.KeyInput.Key = KEY_UNKNOWN;
			event.KeyInput.SystemKeyCode = (u32)keyCode;
			if ( keyAction == AKEY_EVENT_ACTION_DOWN )
				event.KeyInput.PressedDown = true;
			else if ( keyAction == AKEY_EVENT_ACTION_UP )
				event.KeyInput.PressedDown = false;
			else if ( keyAction == AKEY_EVENT_ACTION_MULTIPLE )
			{
				// TODO: Multiple duplicate key events have occurred in a row,
				// or a complex string is being delivered. The repeat_count
				// property of the key event contains the number of times the
				// given key code should be executed.
				// I guess this might necessary for more complicated i18n key input,
				// but don't see yet how to handle this correctly.
			}

			/* no use for meta keys so far.
			if (   keyMetaState & AMETA_ALT_ON
				|| keyMetaState & AMETA_ALT_LEFT_ON
				|| keyMetaState & AMETA_ALT_RIGHT_ON )
				;
			// what is a sym?
			if (   keyMetaState & AMETA_SYM_ON )
				;
			*/
			if (   keyMetaState & AMETA_SHIFT_ON
				|| keyMetaState & AMETA_SHIFT_LEFT_ON
				|| keyMetaState & AMETA_SHIFT_RIGHT_ON )
				event.KeyInput.Shift = true;
			else
				event.KeyInput.Shift = false;
			event.KeyInput.Control = false;

			// Having memory allocations + going through JNI for each key-press is pretty bad (slow).
			// So we do it only for those keys which are likely text-characters and avoid it for all other keys.
			// So it's fast for keys like game controller input and special keys. And text keys are typically
			// only used or entering text and not for gaming on Android, so speed likely doesn't matter there too much.
			if ( event.KeyInput.Key > 0 )
			{
				// TODO:
				// Not sure why we have to attach a JNIEnv here, but it won't work when doing that in the constructor or
				// trying to use the activity->env. My best guess is that the event-handling happens in an own thread.
				// It means JNIEnvAttachedToVM will never get detached as I don't know a safe way where to do that
				// (we could attach & detach each time, but that would probably be slow)
				// Also - it has to be each time as it get's invalid when the application mode changes.
				if ( device->Initialized && device->Android && device->Android->activity && device->Android->activity->vm )
				{
					JavaVMAttachArgs attachArgs;
					attachArgs.version = JNI_VERSION_1_6;
					attachArgs.name = 0;
					attachArgs.group = NULL;

					// Not a big problem calling it each time - it's a no-op when the thread already is attached.
					// And we have to do that as someone else can have detached the thread in the meantime.
					jint result = device->Android->activity->vm->AttachCurrentThread(&device->JNIEnvAttachedToVM, &attachArgs);
					if(result == JNI_ERR)
					{
						os::Printer::log("AttachCurrentThread for the JNI environment failed.", ELL_WARNING);
						device->JNIEnvAttachedToVM = 0;
					}

					if ( device->JNIEnvAttachedToVM )
					{
						jni::CKeyEventWrapper * keyEventWrapper = new jni::CKeyEventWrapper(device->JNIEnvAttachedToVM, keyAction, keyCode);
						event.KeyInput.Char = keyEventWrapper->getUnicodeChar(keyMetaState);
						delete keyEventWrapper;
					}
				}
				if ( event.KeyInput.Key == KEY_BACK )
				{
					event.KeyInput.Char =  0x08;	// same key-code as on other operating systems. Otherwise we have to handle too much system specific stuff in the editbox.
				}
				//os::Printer::log("char-code: ", core::stringc((int)event.KeyInput.Char).c_str(), ELL_DEBUG);

			}
			else
			{
				// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
				event.KeyInput.Char = 0;
			}

			status = device->postEventFromUser(event);
		}
		break;
		default:
		break;
	}

	return status;
}

void CIrrDeviceAndroid::createDriver()
{
	switch(CreationParams.DriverType)
	{
	case video::EDT_OGLES1:
#ifdef _IRR_COMPILE_WITH_OGLES1_
		VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);
#else
		os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
#endif
		break;
	case video::EDT_OGLES2:
#ifdef _IRR_COMPILE_WITH_OGLES2_
		VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);
#else
		os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
#endif
		break;
	case video::EDT_NULL:
		VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
		break;
	case video::EDT_OPENGL:
		os::Printer::log("This driver is not available in Android. Try OpenGL ES 1.0 or ES 2.0.", ELL_ERROR);
		break;
	default:
		os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
		break;
	}
}

video::SExposedVideoData& CIrrDeviceAndroid::getExposedVideoData()
{
	return ExposedVideoData;
}

void CIrrDeviceAndroid::createKeyMap()
{
	KeyMap.set_used(223);

    KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN
    KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
    KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
    KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
    KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
    KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
    KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
    KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
    KeyMap[8] = KEY_KEY_1; // AKEYCODE_1
    KeyMap[9] = KEY_KEY_2; // AKEYCODE_2
    KeyMap[10] = KEY_KEY_3; // AKEYCODE_3
    KeyMap[11] = KEY_KEY_4; // AKEYCODE_4
    KeyMap[12] = KEY_KEY_5; // AKEYCODE_5
    KeyMap[13] = KEY_KEY_6; // AKEYCODE_6
    KeyMap[14] = KEY_KEY_7; // AKEYCODE_7
    KeyMap[15] = KEY_KEY_8; // AKEYCODE_8
    KeyMap[16] = KEY_KEY_9; // AKEYCODE_9
    KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR
    KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND
    KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP
    KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN
    KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT
    KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT
    KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER
    KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP
    KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN
    KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER
    KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA
    KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR
    KeyMap[29] = KEY_KEY_A; // AKEYCODE_A
    KeyMap[30] = KEY_KEY_B; // AKEYCODE_B
    KeyMap[31] = KEY_KEY_C; // AKEYCODE_C
    KeyMap[32] = KEY_KEY_D; // AKEYCODE_D
    KeyMap[33] = KEY_KEY_E; // AKEYCODE_E
    KeyMap[34] = KEY_KEY_F; // AKEYCODE_F
    KeyMap[35] = KEY_KEY_G; // AKEYCODE_G
    KeyMap[36] = KEY_KEY_H; // AKEYCODE_H
    KeyMap[37] = KEY_KEY_I; // AKEYCODE_I
    KeyMap[38] = KEY_KEY_J; // AKEYCODE_J
    KeyMap[39] = KEY_KEY_K; // AKEYCODE_K
    KeyMap[40] = KEY_KEY_L; // AKEYCODE_L
    KeyMap[41] = KEY_KEY_M; // AKEYCODE_M
    KeyMap[42] = KEY_KEY_N; // AKEYCODE_N
    KeyMap[43] = KEY_KEY_O; // AKEYCODE_O
    KeyMap[44] = KEY_KEY_P; // AKEYCODE_P
    KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q
    KeyMap[46] = KEY_KEY_R; // AKEYCODE_R
    KeyMap[47] = KEY_KEY_S; // AKEYCODE_S
    KeyMap[48] = KEY_KEY_T; // AKEYCODE_T
    KeyMap[49] = KEY_KEY_U; // AKEYCODE_U
    KeyMap[50] = KEY_KEY_V; // AKEYCODE_V
    KeyMap[51] = KEY_KEY_W; // AKEYCODE_W
    KeyMap[52] = KEY_KEY_X; // AKEYCODE_X
    KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y
    KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z
    KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA
    KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD
    KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT
    KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT
    KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT
    KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT
    KeyMap[61] = KEY_TAB; // AKEYCODE_TAB
    KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE
    KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM
    KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER
    KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE
    KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER
    KeyMap[67] = KEY_BACK; // AKEYCODE_DEL
    KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE
    KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS
    KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS
    KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET
    KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET
    KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH
    KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON
    KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE
    KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH
    KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT
    KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM
    KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK
    KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus)
    KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS
    KeyMap[82] = KEY_MENU; // AKEYCODE_MENU
    KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION
    KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH
    KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE
    KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP
    KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT
    KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS
    KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND
    KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD
    KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE
    KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP
    KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN
    KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS
    KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET

    // following look like controller inputs
    KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A
    KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B
    KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C
    KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X
    KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y
    KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z
    KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1
    KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1
    KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2
    KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2
    KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL
    KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR
    KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START
    KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT
    KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE

    KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE
    KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL
    KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT
    KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT
    KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK
    KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK
    KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT
    KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT
    KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION
    KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ
    KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK
    KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME
    KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END
    KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT
    KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD
    KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY
    KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE
    KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE
    KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT
    KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD
    KeyMap[131] = KEY_F1; // AKEYCODE_F1
    KeyMap[132] = KEY_F2; // AKEYCODE_F2
    KeyMap[133] = KEY_F3; // AKEYCODE_F3
    KeyMap[134] = KEY_F4; // AKEYCODE_F4
    KeyMap[135] = KEY_F5; // AKEYCODE_F5
    KeyMap[136] = KEY_F6; // AKEYCODE_F6
    KeyMap[137] = KEY_F7; // AKEYCODE_F7
    KeyMap[138] = KEY_F8; // AKEYCODE_F8
    KeyMap[139] = KEY_F9; // AKEYCODE_F9
    KeyMap[140] = KEY_F10; // AKEYCODE_F10
    KeyMap[141] = KEY_F11; // AKEYCODE_F11
    KeyMap[142] = KEY_F12; // AKEYCODE_F12
    KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK
    KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0
    KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1
    KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2
    KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3
    KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4
    KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5
    KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6
    KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7
    KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8
    KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9
    KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE
    KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY
    KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT
    KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD
    KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT
    KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA
    KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER
    KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS
    KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN
    KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN
    KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE
    KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO
    KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP
    KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN
    KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN
    KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT
    KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV
    KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW
    KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE
    KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR
    KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK
    KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS
    KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS
    KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER
    KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT
    KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER
    KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT
    KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER
    KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT
    KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED
    KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN
    KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW
    KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE
    KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH
    KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1
    KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2
    KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3
    KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4
    KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5
    KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6
    KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7
    KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8
    KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9
    KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10
    KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11
    KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12
    KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13
    KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14
    KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15
    KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16
    KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH
    KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE
    KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE
    KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS
    KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR
    KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC
    KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR
    KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU
    KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU
    KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN
    KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN
    KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA
    KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN
    KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO
    KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA
    KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST
    KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN
    KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP ,
    KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK
}

bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval)
{
	if (!isAccelerometerAvailable())
		return false;

	ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer);
	ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds

	os::Printer::log("Activated accelerometer", ELL_DEBUG);
	return true;
}

bool CIrrDeviceAndroid::deactivateAccelerometer()
{
	if (Accelerometer)
	{
		ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer);
		Accelerometer = 0;
		os::Printer::log("Deactivated accelerometer", ELL_DEBUG);
		return true;
	}

	return false;
}

bool CIrrDeviceAndroid::isAccelerometerActive()
{
	return (Accelerometer != 0);
}

bool CIrrDeviceAndroid::isAccelerometerAvailable()
{
	if (!Accelerometer)
		Accelerometer = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_ACCELEROMETER);

	return (Accelerometer != 0);
}

bool CIrrDeviceAndroid::activateGyroscope(float updateInterval)
{
	if (!isGyroscopeAvailable())
		return false;

	ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope);
	ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds

	os::Printer::log("Activated gyroscope", ELL_DEBUG);
	return true;
}

bool CIrrDeviceAndroid::deactivateGyroscope()
{
	if (Gyroscope)
	{
		ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope);
		Gyroscope = 0;
		os::Printer::log("Deactivated gyroscope", ELL_DEBUG);
		return true;
	}

	return false;
}

bool CIrrDeviceAndroid::isGyroscopeActive()
{
	return (Gyroscope != 0);
}

bool CIrrDeviceAndroid::isGyroscopeAvailable()
{
	if (!Gyroscope)
		Gyroscope = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_GYROSCOPE);

	return (Gyroscope != 0);
}

} // end namespace irr
