#include "NativeManager.h"
#include "NativeAssetFile.h"
#include "NativeAssetDir.h"
#include "NativeRootWindow.h"
#include "Log.h"
#include "Timer.h"
#include "FilePath.h"

#include <GLES/gl.h>
#include <stdexcept>

//////////////////////////////////////////////////////////////
namespace Dubious {
namespace Utility {

NativeManager::NativeManager( struct android_app* state )
	: m_State( state )
{
    // Make sure glue isn't stripped.
    app_dummy();

    m_State->userData = this;
	m_State->onAppCmd = StaticHandleCmd;
	m_State->onInputEvent = StaticHandleInput;
}

NativeManager::~NativeManager()
{
}

void NativeManager::StaticHandleCmd( struct android_app* app, int32_t cmd )
{
	reinterpret_cast<NativeManager*>(app->userData)->HandleCmd( app, cmd );
}

namespace
{
class AppCmdManager
{
public:
	AppCmdManager( struct android_app* App, int32_t Cmd )
		: m_App( App )
		, m_Cmd( Cmd )
	{
		android_app_pre_exec_cmd( m_App, m_Cmd );
	}
	~AppCmdManager()
	{
		android_app_post_exec_cmd( m_App, m_Cmd );
	}
private:
	struct android_app* m_App;
	int32_t m_Cmd;
};
}

void NativeManager::HandleCmd( struct android_app* app, int32_t cmd )
{
	AppCmdManager CmdManager( app, cmd );

	try {
		// See android_native_app_glue.h for a full list of all possible
		// cmd states and how you're supposed to handle them.
		switch (cmd) {
		case APP_CMD_INPUT_CHANGED:
			LOG_INFO( "NativeManager received a state command: APP_CMD_INPUT_CHANGED" );
			break;
		case APP_CMD_INIT_WINDOW:
			LOG_INFO( "NativeManager received a state command: APP_CMD_INIT_WINDOW" );
			m_RootWindow.reset( new NativeRootWindow( app->window ) );
			if (m_OnInitWindow) {
				m_OnInitWindow( m_RootWindow->Dimensions() );
			}
			break;
		case APP_CMD_TERM_WINDOW:
			LOG_INFO( "NativeManager received a state command: APP_CMD_TERM_WINDOW" );
			if (m_OnDestroyWindow) {
				m_OnDestroyWindow();
			}
			m_RootWindow.reset();
			break;
		case APP_CMD_WINDOW_RESIZED:
			LOG_INFO( "NativeManager received a state command: APP_CMD_WINDOW_RESIZED" );
			break;
		case APP_CMD_WINDOW_REDRAW_NEEDED:
			LOG_INFO( "NativeManager received a state command: APP_CMD_WINDOW_REDRAW_NEEDED" );
			if (m_OnDraw && m_RootWindow) {
				m_OnDraw();
				m_RootWindow->SwapBuffers();
			}
			break;
		case APP_CMD_CONTENT_RECT_CHANGED:
			LOG_INFO( "NativeManager received a state command: APP_CMD_CONTENT_RECT_CHANGED" );
			break;
		case APP_CMD_GAINED_FOCUS:
			LOG_INFO( "NativeManager received a state command: APP_CMD_GAINED_FOCUS" );
			if (m_RootWindow) {
				m_RootWindow->Animate() = true;
			}
			break;
		case APP_CMD_LOST_FOCUS:
			LOG_INFO( "NativeManager received a state command: APP_CMD_LOST_FOCUS" );
			if (m_RootWindow) {
				m_RootWindow->Animate() = false;
			}
			break;
		case APP_CMD_CONFIG_CHANGED:
			LOG_INFO( "NativeManager received a state command: APP_CMD_CONFIG_CHANGED" );
			break;
		case APP_CMD_LOW_MEMORY:
			LOG_INFO( "NativeManager received a state command: APP_CMD_LOW_MEMORY" );
			break;
		case APP_CMD_START:
			LOG_INFO( "NativeManager received a state command: APP_CMD_START" );
			if (m_RootWindow) {
				m_RootWindow->Animate() = true;
			}
			if (m_OnStart) {
				m_OnStart();
			}
			break;
		case APP_CMD_RESUME:
			LOG_INFO( "NativeManager received a state command: APP_CMD_RESUME" );
			if (m_OnResume) {
				m_OnResume();
			}
			break;
		case APP_CMD_SAVE_STATE:
			LOG_INFO( "NativeManager received a state command: APP_CMD_SAVE_STATE" );
			break;
		case APP_CMD_PAUSE:
			LOG_INFO( "NativeManager received a state command: APP_CMD_PAUSE" );
			if (m_RootWindow) {
				m_RootWindow->Animate() = false;
			}
			if (m_OnPause) {
				m_OnPause();
			}
			break;
		case APP_CMD_STOP:
			LOG_INFO( "NativeManager received a state command: APP_CMD_STOP" );
			if (m_RootWindow) {
				m_RootWindow->Animate() = false;
			}
			if (m_OnStop) {
				m_OnStop();
			}
			break;
		case APP_CMD_DESTROY:
			LOG_INFO( "NativeManager received a state command: APP_CMD_DESTROY" );
			if (m_OnDestroy) {
				m_OnDestroy();
			}
			break;
		}
	}
	catch (const std::exception& e) {
		LOG_WARN( "Caught an exception at HandleCmd: " << e.what() );
	}
}

int32_t NativeManager::StaticHandleInput( struct android_app* App, AInputEvent* Event )
{
	return reinterpret_cast<NativeManager*>(App->userData)->HandleInput( App, Event );
}

int32_t NativeManager::HandleInput( struct android_app* App, AInputEvent* Event )
{
	if (AInputEvent_getType(Event) == AINPUT_EVENT_TYPE_MOTION) {
		int32_t Action 	= AMotionEvent_getAction(Event);
		int Flags 		= Action & AMOTION_EVENT_ACTION_MASK;
		switch (Flags) {
		case AMOTION_EVENT_ACTION_POINTER_UP:
		case AMOTION_EVENT_ACTION_UP:
			if (m_OnPointerUp) {
				int Index = (Action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
				PointerData Data;
				Data.ID = AMotionEvent_getPointerId(Event, Index);
				Data.Location = Math::Point( AMotionEvent_getX(Event, Index), AMotionEvent_getY(Event, Index) );
				m_OnPointerUp( Data );
			}
			break;
		case AMOTION_EVENT_ACTION_POINTER_DOWN:
		case AMOTION_EVENT_ACTION_DOWN:
			if (m_OnPointerDown) {
				int Index = (Action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
				PointerData Data;
				Data.ID = AMotionEvent_getPointerId(Event, Index);
				Data.Location = Math::Point( AMotionEvent_getX(Event, Index), AMotionEvent_getY(Event, Index) );
				m_OnPointerDown( Data );
			}
			break;
		case AMOTION_EVENT_ACTION_MOVE:
			if (m_OnPointerMove) {
				size_t PointerCount = AMotionEvent_getPointerCount(Event);
				PointerDataVector Points;
				for (int i=0; i<PointerCount; ++i) {
					PointerData Data;
					Data.ID = AMotionEvent_getPointerId(Event, i);
					Data.Location = Math::Point( AMotionEvent_getX(Event, i), AMotionEvent_getY(Event, i) );
					Points.push_back( Data );
				}
				m_OnPointerMove( Points );
			}
			break;
		}
	}
	else if (AInputEvent_getType(Event) == AINPUT_EVENT_TYPE_KEY) {
	}
	// A return code of 1 means I've handled this event.

	// A return code of 0 means the system should perform standard handling
	return 0;
}

void NativeManager::Run()
{
	Dubious::Utility::Timer LoopTimer;
	LoopTimer.Start();
	while (true) {
		// Read all pending events.
		int LooperId;
		int Events;
		struct android_poll_source* PollSource;

		// First handle any system events.  If the app has focus then we want to burn through
		// all of these so we can get back to updating the screen, running the physics, etc.
		// If this app does not have focus then hang here to preserve battery life
		bool Animate = false;
		if (m_RootWindow) {
			Animate = m_RootWindow->Animate();
		}
		while ((LooperId=ALooper_pollAll((Animate ? 0 : -1), NULL, &Events, (void**)&PollSource)) >= 0) {

			if ((LooperId == LOOPER_ID_MAIN || LooperId == LOOPER_ID_INPUT) && PollSource != NULL) {
				// Process this event.
				PollSource->process(m_State, PollSource);
			}

			// Check if we are exiting.
			if (m_State->destroyRequested != 0) {
				m_RootWindow.reset();
				return;
			}

			if (m_RootWindow) {
				Animate = m_RootWindow->Animate();
			}

		}
		// We have handled all of the available system events.  We're now free
		// to run out own stuff, like updating the physics and the OpenGL
		if (m_OnPhysics) {
			m_OnPhysics( static_cast<float>(LoopTimer.Restart())/1000.0f );
		}
		if (m_OnDraw && m_RootWindow) {
			m_OnDraw();
			m_RootWindow->SwapBuffers();
		}
	}
}

NativeAssetFilePtr NativeManager::OpenAsset( const FilePath& File ) const
{
	return NativeAssetFilePtr( new NativeAssetFile( *m_State->activity->assetManager, File ) );
}

NativeAssetDirPtr NativeManager::OpenAssetDir( const FilePath& File ) const
{
	return NativeAssetDirPtr( new NativeAssetDir( *m_State->activity->assetManager, File ) );
}

Dubious::Math::Size NativeManager::ScreenDimensions() const
{
	if (m_RootWindow) {
		return m_RootWindow->Dimensions();
	}
	return Dubious::Math::Size();
}


}}
