#include <jni.h>
#include <android/log.h>

#include <psemek/app/application.hpp>
#include <psemek/gfx/init.hpp>
#include <psemek/log/log.hpp>

namespace
{

	using namespace psemek;

	android_LogPriority log_priority(log::level level)
	{
		switch (level)
		{
		case log::level::debug:
			return ANDROID_LOG_DEBUG;
		case log::level::info:
			return ANDROID_LOG_INFO;
		case log::level::warning:
			return ANDROID_LOG_WARN;
		case log::level::error:
			return ANDROID_LOG_ERROR;
		default:
			return ANDROID_LOG_UNKNOWN;
		}
	}

	struct sink_impl
		: log::sink
	{
		void put_message(log::message const & msg) override
		{
			__android_log_write(log_priority(msg.level), "psemek", msg.message.data());
		}

		void flush() override
		{}
	};

	template <typename F>
	auto try_native(JNIEnv * env, F && f) try
	{
		return f();
	}
	catch (std::exception const & e)
	{
		env->ThrowNew(env->FindClass("java/lang/Exception"), e.what());
		__builtin_unreachable();
	}
	catch (...)
	{
		env->ThrowNew(env->FindClass("java/lang/Exception"), "Unknown exception");
		__builtin_unreachable();
	}

}

extern "C" void Java_psemek_app_PsemekApplication_setupLogging(JNIEnv *, jclass)
{
	psemek::log::add_sink(std::make_unique<sink_impl>());
}

extern "C" jlong Java_psemek_app_PsemekApplication_createNativeApp(JNIEnv * env, jclass)
{
	return try_native(env, [&]{
		auto factory = psemek::app::make_application_factory();

		psemek::gfx::init();

		psemek::app::application::context context;
		context.show_cursor = [](bool){};
		context.vsync = [](bool){};
		context.relative_mouse_mode = [](bool){};
		context.windowed = [](bool){};
		auto app = factory->create(factory->options(), context).release();

		return reinterpret_cast<jlong>(app);
	});
}

extern "C" void Java_psemek_app_PsemekApplication_destroyNativeApp(JNIEnv *, jclass, jlong ptr)
{
	delete reinterpret_cast<psemek::app::application *>(ptr);
}

extern "C" void Java_psemek_app_PsemekApplication_resizeNative(JNIEnv * env, jclass, jlong ptr, jint width, jint height)
{
	try_native(env, [&]{
		auto app = reinterpret_cast<psemek::app::application *>(ptr);
		app->on_event(psemek::app::resize_event{{width, height}});
	});
}

extern "C" void Java_psemek_app_PsemekApplication_touchDownNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
	try_native(env, [&]{
		auto app = reinterpret_cast<psemek::app::application *>(ptr);
		app->on_event(psemek::app::touch_down_event{{x, y}});
	});
}

extern "C" void Java_psemek_app_PsemekApplication_touchUpNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
	try_native(env, [&]{
		auto app = reinterpret_cast<psemek::app::application *>(ptr);
		app->on_event(psemek::app::touch_up_event{{x, y}});
	});
}

extern "C" void Java_psemek_app_PsemekApplication_touchMoveNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
	try_native(env, [&]{
		auto app = reinterpret_cast<psemek::app::application *>(ptr);
		app->on_event(psemek::app::touch_move_event{{x, y}});
	});
}

extern "C" void Java_psemek_app_PsemekApplication_drawFrameNative(JNIEnv * env, jclass, jlong ptr)
{
	try_native(env, [&]{
		auto app = reinterpret_cast<psemek::app::application *>(ptr);
		app->update();
		app->present();
	});
}
