/*************************************************************************/
/*  os_bb10.cpp                                                          */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "os_bb10.h"

#include "drivers/gles2/rasterizer_gles2.h"
#include "drivers/gles1/rasterizer_gles1.h"
#include "servers/visual/visual_server_raster.h"
#include "core/os/dir_access.h"

#include "core/globals.h"
#include "main/main.h"
#include "bbutil.h"
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include "core/os/keyboard.h"

#include "bps/bps.h"
#include "bps/screen.h"
#include "bps/navigator.h"
#include "bps/accelerometer.h"
#include "bps/orientation.h"
#include "bps/virtualkeyboard.h"
#include "bps/audiodevice.h"

#ifdef BB10_SCORELOOP_ENABLED
#include "modules/scoreloop/scoreloop_bb10.h"
#endif

static char launch_dir[512];
char* launch_dir_ptr;

int OSBB10::get_video_driver_count() const {

	return 1;
}
const char * OSBB10::get_video_driver_name(int p_driver) const {

	return "GLES2";
}

OS::VideoMode OSBB10::get_default_video_mode() const {

	return OS::VideoMode();
}

int OSBB10::get_audio_driver_count() const {

	return 1;
}
const char * OSBB10::get_audio_driver_name(int p_driver) const {

	return "BB10";
}

void OSBB10::initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver) {

	data_dir = getenv("HOME");

	//Create a screen context that will be used to create an EGL surface to to receive libscreen events
	screen_create_context(&screen_cxt,0);

	//Initialize BPS library
	bps_initialize();

	//Use utility code to initialize EGL for 2D rendering with GL ES 1.1
	enum RENDERING_API api = GL_ES_2;
	#ifdef BB10_LGLES_OVERRIDE
	api = GL_ES_1;
	#endif
	if (EXIT_SUCCESS != bbutil_init(screen_cxt, api)) {
		bbutil_terminate();
		screen_destroy_context(screen_cxt);
		return;
	};

	EGLint surface_width, surface_height;

	eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
	eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);
	printf("screen size: %ix%i\n", surface_width, surface_height);
	VideoMode mode;
	mode.width = surface_width;
	mode.height = surface_height;
	mode.fullscreen = true;
	mode.resizable = false;
	set_video_mode(mode);

	//Signal BPS library that navigator and screen events will be requested
	screen_request_events(screen_cxt);
	navigator_request_events(0);
	virtualkeyboard_request_events(0);
	audiodevice_request_events(0);

	#ifdef DEBUG_ENABLED
	bps_set_verbosity(3);
	#endif

	accel_supported = accelerometer_is_supported();
	if (accel_supported)
		accelerometer_set_update_frequency(FREQ_40_HZ);
	pitch = 0;
	roll = 0;

	#ifdef BB10_LGLES_OVERRIDE
	rasterizer = memnew( RasterizerGLES1(false) );
	#else
	rasterizer = memnew( RasterizerGLES2(false, false) );
	#endif

	visual_server = memnew( VisualServerRaster(rasterizer) );
	visual_server->init();
	visual_server->cursor_set_visible(false, 0);

	audio_driver = memnew(AudioDriverBB10);
	audio_driver->set_singleton();
	audio_driver->init(NULL);

	sample_manager = memnew( SampleManagerMallocSW );
	audio_server = memnew( AudioServerSW(sample_manager) );
	audio_server->set_mixer_params(AudioMixerSW::INTERPOLATION_LINEAR,false);
	audio_server->init();

	spatial_sound_server = memnew( SpatialSoundServerSW );
	spatial_sound_server->init();

	spatial_sound_2d_server = memnew( SpatialSound2DServerSW );
	spatial_sound_2d_server->init();

	//
	physics_server = memnew( PhysicsServerSW );
	physics_server->init();
	physics_2d_server = memnew( Physics2DServerSW );
	physics_2d_server->init();

	input = memnew( InputDefault );

	#ifdef PAYMENT_SERVICE_ENABLED
	payment_service = memnew(PaymentService);
	Globals::get_singleton()->add_singleton(Globals::Singleton("InAppStore", payment_service));
	#endif

}

void OSBB10::set_main_loop( MainLoop * p_main_loop ) {

	input->set_main_loop(p_main_loop);
	main_loop=p_main_loop;
}

void OSBB10::delete_main_loop() {

	memdelete( main_loop );
	main_loop = NULL;
}

void OSBB10::finalize() {

	if(main_loop)
		memdelete(main_loop);
	main_loop=NULL;

	spatial_sound_server->finish();
	memdelete(spatial_sound_server);
	spatial_sound_2d_server->finish();
	memdelete(spatial_sound_2d_server);

	//if (debugger_connection_console) {
//		memdelete(debugger_connection_console);
//}

	memdelete(sample_manager);

	audio_server->finish();
	memdelete(audio_server);

	visual_server->finish();
	memdelete(visual_server);
	memdelete(rasterizer);

	physics_server->finish();
	memdelete(physics_server);

	physics_2d_server->finish();
	memdelete(physics_2d_server);

	#ifdef PAYMENT_SERVICE_ENABLED
	memdelete(payment_service);
	#endif

	memdelete(input);

	bbutil_terminate();
	screen_destroy_context(screen_cxt);

	bps_shutdown();
}

void OSBB10::set_mouse_show(bool p_show) {

	//android has no mouse...
}

void OSBB10::set_mouse_grab(bool p_grab) {

	//it really has no mouse...!
}

bool OSBB10::is_mouse_grab_enabled() const {

	//*sigh* technology has evolved so much since i was a kid..
	return false;
}
Point2 OSBB10::get_mouse_pos() const {

	return Point2();
}
int OSBB10::get_mouse_button_state() const {

	return 0;
}
void OSBB10::set_window_title(const String& p_title) {


}

//interesting byt not yet
//void set_clipboard(const String& p_text);
//String get_clipboard() const;

void OSBB10::set_video_mode(const VideoMode& p_video_mode,int p_screen) {

	default_videomode = p_video_mode;
}

OS::VideoMode OSBB10::get_video_mode(int p_screen) const {

	return default_videomode;
}
void OSBB10::get_fullscreen_mode_list(List<VideoMode> *p_list,int p_screen) const {

	p_list->push_back(default_videomode);
}

String OSBB10::get_name() {

	return "BlackBerry 10";
}

MainLoop *OSBB10::get_main_loop() const {

	return main_loop;
}

bool OSBB10::can_draw() const {

	return !minimized;
}

void OSBB10::set_cursor_shape(CursorShape p_shape) {

	//android really really really has no mouse.. how amazing..
}

void OSBB10::handle_screen_event(bps_event_t *event) {

	screen_event_t screen_event = screen_event_get_event(event);

	int screen_val;
	screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &screen_val);

	int pos[2];

	switch (screen_val) {
	case SCREEN_EVENT_MTOUCH_TOUCH:
	case SCREEN_EVENT_MTOUCH_RELEASE: {

		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_POSITION, pos);

		InputEvent ievent;
		ievent.type = InputEvent::SCREEN_TOUCH;
		ievent.ID = ++last_id;
		ievent.device = 0;
		ievent.screen_touch.pressed = (screen_val == SCREEN_EVENT_MTOUCH_TOUCH);
		ievent.screen_touch.x = pos[0];
		ievent.screen_touch.y = pos[1];
		Point2 mpos(ievent.screen_touch.x, ievent.screen_touch.y);

		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TOUCH_ID, &pos[0]);
		ievent.screen_touch.index = pos[0];

		last_touch_x[pos[0]] = ievent.screen_touch.x;
		last_touch_y[pos[0]] = ievent.screen_touch.y;

		input->parse_input_event( ievent );

		if (ievent.screen_touch.index == 0) {

			InputEvent ievent;
			ievent.type = InputEvent::MOUSE_BUTTON;
			ievent.ID = ++last_id;
			ievent.device = 0;
			ievent.mouse_button.pressed = (screen_val == SCREEN_EVENT_MTOUCH_TOUCH);
			ievent.mouse_button.button_index = BUTTON_LEFT;
			ievent.mouse_button.doubleclick = 0;
			ievent.mouse_button.x = ievent.mouse_button.global_x = mpos.x;
			ievent.mouse_button.y = ievent.mouse_button.global_y = mpos.y;
			input->parse_input_event( ievent );
		};


	} break;
	case SCREEN_EVENT_MTOUCH_MOVE: {

		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_POSITION, pos);

		InputEvent ievent;
		ievent.type = InputEvent::SCREEN_DRAG;
		ievent.ID = ++last_id;
		ievent.device = 0;
		ievent.screen_drag.x = pos[0];
		ievent.screen_drag.y = pos[1];

		/*
		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pos);
		ievent.screen_drag.relative_x = ievent.screen_drag.x - pos[0];
		ievent.screen_drag.relative_y = ievent.screen_drag.y - pos[1];
		*/

		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TOUCH_ID, &pos[0]);
		ievent.screen_drag.index = pos[0];


		ievent.screen_drag.relative_x = ievent.screen_drag.x - last_touch_x[ievent.screen_drag.index];
		ievent.screen_drag.relative_y = ievent.screen_drag.y - last_touch_y[ievent.screen_drag.index];

		last_touch_x[ievent.screen_drag.index] = ievent.screen_drag.x;
		last_touch_y[ievent.screen_drag.index] = ievent.screen_drag.y;

		Point2 mpos(ievent.screen_drag.x, ievent.screen_drag.y);
		Point2 mrel(ievent.screen_drag.relative_x, ievent.screen_drag.relative_y);

		input->parse_input_event( ievent );

		if (ievent.screen_touch.index == 0) {

			InputEvent ievent;
			ievent.type = InputEvent::MOUSE_MOTION;
			ievent.ID = ++last_id;
			ievent.device = 0;
			ievent.mouse_motion.x = ievent.mouse_motion.global_x = mpos.x;
			ievent.mouse_motion.y = ievent.mouse_motion.global_y = mpos.y;
			input->set_mouse_pos(Point2(ievent.mouse_motion.x,ievent.mouse_motion.y));
			ievent.mouse_motion.speed_x=input->get_mouse_speed().x;
			ievent.mouse_motion.speed_y=input->get_mouse_speed().y;
			ievent.mouse_motion.relative_x = mrel.x;
			ievent.mouse_motion.relative_y = mrel.y;
			ievent.mouse_motion.button_mask = 1; // pressed

			input->parse_input_event( ievent );
		};
	} break;

	case SCREEN_EVENT_KEYBOARD: {

		InputEvent ievent;
		ievent.type = InputEvent::KEY;
		ievent.ID = ++last_id;
		ievent.device = 0;
		int val = 0;
		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_SCAN, &val);
		ievent.key.scancode = val;
		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_SYM, &val);
		ievent.key.unicode = val;
		if (val == 61448) {
			ievent.key.scancode = KEY_BACKSPACE;
			ievent.key.unicode = KEY_BACKSPACE;
		};
		if (val == 61453) {
			ievent.key.scancode = KEY_ENTER;
			ievent.key.unicode = KEY_ENTER;
		};

		int flags;
		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_FLAGS, &flags);
		ievent.key.pressed = flags & 1; // bit 1 is pressed apparently

		int mod;
		screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_KEY_MODIFIERS, &mod);

		input->parse_input_event( ievent );
	} break;

	default:
		break;

	}
};

void OSBB10::handle_accelerometer() {

	if (!accel_supported)
		return;

	if (!fullscreen)
		return;

	double force_x, force_y, force_z;
	accelerometer_read_forces(&force_x, &force_y, &force_z);
	Vector3 accel = Vector3(force_x, flip_accelerometer ? force_y : -force_y, force_z);
	input->set_accelerometer(accel);
	// rotate 90 degrees
	//input->set_accelerometer(Vector3(force_y, flip_accelerometer?force_x:(-force_x), force_z));
};


void OSBB10::_resize(bps_event_t* event) {

	int angle = navigator_event_get_orientation_angle(event);
	bbutil_rotate_screen_surface(angle);

	EGLint surface_width, surface_height;
	eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
	eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);

	VideoMode mode;
	mode.width = surface_width;
	mode.height = surface_height;
	mode.fullscreen = true;
	mode.resizable = false;
	set_video_mode(mode);
};

void OSBB10::process_events() {

	handle_accelerometer();

	bps_event_t *event = NULL;

	do {
		int rc = bps_get_event(&event, 0);
		assert(rc == BPS_SUCCESS);

		if (!event) break;

		#ifdef BB10_SCORELOOP_ENABLED
		ScoreloopBB10* sc = Globals::get_singleton()->get_singleton_object("Scoreloop")->cast_to<ScoreloopBB10>();
		if (sc->handle_event(event))
			continue;
		#endif

		#ifdef PAYMENT_SERVICE_ENABLED
		if (payment_service->handle_event(event))
			continue;
		#endif

		int domain = bps_event_get_domain(event);
		if (domain == screen_get_domain()) {

			handle_screen_event(event);

		} else if (domain == navigator_get_domain()) {

			if (NAVIGATOR_EXIT == bps_event_get_code(event)) {
				if (main_loop)
					main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
				bps_event_destroy(event);
				exit(0);
				return;
			/*
			} else if (bps_event_get_code(event) == NAVIGATOR_ORIENTATION_CHECK) {

				int angle = navigator_event_get_orientation_angle(event);
				navigator_orientation_check_response(event, false);

			} else if (bps_event_get_code(event) == NAVIGATOR_ORIENTATION) {

				_resize(event);
			*/
			} else if (bps_event_get_code(event) == NAVIGATOR_WINDOW_STATE) {

				int state = navigator_event_get_window_state(event);
				bool was_fullscreen = fullscreen;
				minimized = state == NAVIGATOR_WINDOW_INVISIBLE;
				fullscreen = state == NAVIGATOR_WINDOW_FULLSCREEN;
				set_low_processor_usage_mode(!fullscreen);
				if (fullscreen != was_fullscreen) {
					if (fullscreen) {
						audio_server->set_fx_global_volume_scale(fullscreen_mixer_volume);
						audio_server->set_stream_global_volume_scale(fullscreen_stream_volume);
					} else {
						fullscreen_mixer_volume = audio_server->get_fx_global_volume_scale();
						fullscreen_stream_volume = audio_server->get_stream_global_volume_scale();
						audio_server->set_fx_global_volume_scale(0);
						audio_server->set_stream_global_volume_scale(0);
					};
				};
			};
		} else if (domain == audiodevice_get_domain()) {

			const char * audiodevice_path = audiodevice_event_get_path(event);
			printf("************* got audiodevice event, path %s\n", audiodevice_path);
			audio_driver->finish();
			audio_driver->init(audiodevice_path);
			audio_driver->start();
		};

		//bps_event_destroy(event);
	} while (event);
};

bool OSBB10::has_virtual_keyboard() const {

	return true;
};

void OSBB10::show_virtual_keyboard(const String& p_existing_text,const Rect2& p_screen_rect) {

	virtualkeyboard_show();
};

void OSBB10::hide_virtual_keyboard() {

	virtualkeyboard_hide();
};

void OSBB10::run() {

	if (!main_loop)
		return;

	main_loop->init();

	int flip = bbutil_is_flipped();
	int rot = bbutil_get_rotation();
	flip_accelerometer = rot == 90;
	printf("**************** rot is %i, flip %i\n", rot, (int)flip_accelerometer);
	/*
	orientation_direction_t orientation;
	int angle;
	orientation_get(&orientation, &angle);
	printf("******************** orientation %i, %i, %i\n", orientation, ORIENTATION_BOTTOM_UP, ORIENTATION_TOP_UP);
	if (orientation == ORIENTATION_BOTTOM_UP) {
		flip_accelerometer = true;
	};
	*/


	while (true) {

		process_events(); // get rid of pending events
		if (Main::iteration()==true)
			break;
		bbutil_swap();
		//#ifdef DEBUG_ENABLED
		fflush(stdout);
		//#endif
	};

	main_loop->finish();

};

bool OSBB10::has_touchscreen_ui_hint() const {

	return true;
}

Error OSBB10::shell_open(String p_uri) {

	char* msg = NULL;
	int ret = navigator_invoke(p_uri.utf8().get_data(), &msg);

	return ret == BPS_SUCCESS ? OK : FAILED;
};

String OSBB10::get_data_dir() const {

	return data_dir;
};


OSBB10::OSBB10() {

	main_loop=NULL;
	last_id=1;
	minimized = false;
	fullscreen = true;
	flip_accelerometer = true;
	fullscreen_mixer_volume = 1;
	fullscreen_stream_volume = 1;


	printf("godot bb10!\n");
	getcwd(launch_dir, sizeof(launch_dir));
	printf("launch dir %s\n", launch_dir);
	chdir("app/native");
	launch_dir_ptr = launch_dir;
}

OSBB10::~OSBB10() {


}