#include "Core.h"
#if defined CC_BUILD_3DS
#include "Window.h"
#include "Platform.h"
#include "Input.h"
#include "Event.h"
#include "Graphics.h"
#include "String.h"
#include "Funcs.h"
#include "Bitmap.h"
#include "Errors.h"
#include "ExtMath.h"
#include "Gui.h"
#include <3ds.h>

static cc_bool launcherMode;
static Result irrst_result;
static u16 top_width, top_height;
static u16 btm_width, btm_height;

struct _DisplayData DisplayInfo;
struct _WindowData WindowInfo;
struct _WindowData Window_Alt;
cc_bool launcherTop;

void Window_PreInit(void) {
	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
}

// Note from https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/gfx.h
//  * Please note that the 3DS uses *portrait* screens rotated 90 degrees counterclockwise.
//  * Width/height refer to the physical dimensions of the screen; that is, the top screen
//  * is 240 pixels wide and 400 pixels tall; while the bottom screen is 240x320.
void Window_Init(void) {
	// deliberately swapped
	gfxGetFramebuffer(GFX_TOP,    GFX_LEFT, &top_height, &top_width);
	gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &btm_height, &btm_width);
	
	DisplayInfo.Width  = top_width; 
	DisplayInfo.Height = top_height;
	DisplayInfo.ScaleX = 0.5f;
	DisplayInfo.ScaleY = 0.5f;
	
	Window_Main.Width   = top_width;
	Window_Main.Height  = top_height;
	Window_Main.Focused = true;
	Window_Main.Exists  = true;

	Window_Main.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
	Input_SetTouchMode(true);
	Gui_SetTouchUI(true);
	Input.Sources = INPUT_SOURCE_GAMEPAD;
	irrst_result  = irrstInit();
	
	Window_Alt.Width  = btm_width;
	Window_Alt.Height = btm_height;
}

void Window_Free(void) { irrstExit(); }

void Window_Create2D(int width, int height) {  
	DisplayInfo.Width = btm_width;
	Window_Main.Width = btm_width;
	Window_Alt.Width  = top_width;
	launcherMode      = true;  
}

void Window_Create3D(int width, int height) { 
	DisplayInfo.Width = top_width;
	Window_Main.Width = top_width;
	Window_Alt.Width  = btm_width;
	launcherMode      = false; 
}

void Window_SetTitle(const cc_string* title) { }
void Clipboard_GetText(cc_string* value) { }
void Clipboard_SetText(const cc_string* value) { }

int Window_GetWindowState(void) { return WINDOW_STATE_FULLSCREEN; }
cc_result Window_EnterFullscreen(void) { return 0; }
cc_result Window_ExitFullscreen(void)  { return 0; }
int Window_IsObscured(void)            { return 0; }

void Window_Show(void) { }
void Window_SetSize(int width, int height) { }

void Window_RequestClose(void) {
	Event_RaiseVoid(&WindowEvents.Closing);
}

/*########################################################################################################################*
*----------------------------------------------------Input processing-----------------------------------------------------*
*#########################################################################################################################*/
static void ProcessTouchInput(int mods) {
	touchPosition touch;
	hidTouchRead(&touch);

	if (mods & KEY_TOUCH) {
		Input_AddTouch(0,    touch.px,      touch.py);
	} else if (hidKeysUp() & KEY_TOUCH) {
		Input_RemoveTouch(0, Pointers[0].x, Pointers[0].y);
	}
}

void Window_ProcessEvents(float delta) {
	hidScanInput();

	if (!aptMainLoop()) {
		Window_Main.Exists = false;
		Window_RequestClose();
		return;
	}
	
	u32 mods = hidKeysDown() | hidKeysHeld();
	ProcessTouchInput(mods);
}

void Cursor_SetPosition(int x, int y) { } // Makes no sense for 3DS

void Window_EnableRawMouse(void)  { Input.RawMode = true;  }
void Window_DisableRawMouse(void) { Input.RawMode = false; }

void Window_UpdateRawMouse(void)  { }


/*########################################################################################################################*
*-------------------------------------------------------Gamepads----------------------------------------------------------*
*#########################################################################################################################*/
static void HandleButtons(u32 mods) {
	Gamepad_SetButton(0, CCPAD_L, mods & KEY_L);
	Gamepad_SetButton(0, CCPAD_R, mods & KEY_R);
	
	Gamepad_SetButton(0, CCPAD_A, mods & KEY_A);
	Gamepad_SetButton(0, CCPAD_B, mods & KEY_B);
	Gamepad_SetButton(0, CCPAD_X, mods & KEY_X);
	Gamepad_SetButton(0, CCPAD_Y, mods & KEY_Y);
	
	Gamepad_SetButton(0, CCPAD_START,  mods & KEY_START);
	Gamepad_SetButton(0, CCPAD_SELECT, mods & KEY_SELECT);
	
	Gamepad_SetButton(0, CCPAD_LEFT,   mods & KEY_DLEFT);
	Gamepad_SetButton(0, CCPAD_RIGHT,  mods & KEY_DRIGHT);
	Gamepad_SetButton(0, CCPAD_UP,     mods & KEY_DUP);
	Gamepad_SetButton(0, CCPAD_DOWN,   mods & KEY_DDOWN);
	
	Gamepad_SetButton(0, CCPAD_ZL, mods & KEY_ZL);
	Gamepad_SetButton(0, CCPAD_ZR, mods & KEY_ZR);
}

#define AXIS_SCALE 8.0f
static void ProcessCircleInput(int axis, circlePosition* pos, float delta) {
	// May not be exactly 0 on actual hardware
	if (Math_AbsI(pos->dx) <= 24) pos->dx = 0;
	if (Math_AbsI(pos->dy) <= 24) pos->dy = 0;
		
	Gamepad_SetAxis(0, axis, pos->dx / AXIS_SCALE, -pos->dy / AXIS_SCALE, delta);
}

void Window_ProcessGamepads(float delta) {
	u32 mods = hidKeysDown() | hidKeysHeld();
	HandleButtons(mods);
	
	circlePosition hid_pos;
	hidCircleRead(&hid_pos);

	if (irrst_result == 0) {
		circlePosition stk_pos;
		irrstScanInput();
		irrstCstickRead(&stk_pos);
		
		ProcessCircleInput(PAD_AXIS_RIGHT, &stk_pos, delta);
		ProcessCircleInput(PAD_AXIS_LEFT,  &hid_pos, delta);
	} else {
		ProcessCircleInput(PAD_AXIS_RIGHT, &hid_pos, delta);
	}
}


/*########################################################################################################################*
*------------------------------------------------------Framebuffer--------------------------------------------------------*
*#########################################################################################################################*/
void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) {
	bmp->scan0  = (BitmapCol*)Mem_Alloc(width * height, 4, "window pixels");
	bmp->width  = width;
	bmp->height = height;
}

void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) {
	u16 width, height;
	gfxScreen_t screen = launcherTop ? GFX_TOP : GFX_BOTTOM;
	
	gfxSetDoubleBuffering(screen, false);
	u8* fb = gfxGetFramebuffer(screen, GFX_LEFT, &width, &height);
	// SRC y = 0 to 240
	// SRC x = 0 to 400
	// DST X = 0 to 240
	// DST Y = 0 to 400

	for (int y = r.y; y < r.y + r.height; y++)
		for (int x = r.x; x < r.x + r.width; x++)
	{
		BitmapCol color = Bitmap_GetPixel(bmp, x, y);
		int addr   = (width - 1 - y + x * width) * 3; // TODO -1 or not
		fb[addr+0] = BitmapCol_B(color);
		fb[addr+1] = BitmapCol_G(color);
		fb[addr+2] = BitmapCol_R(color);
	}
	// TODO gspWaitForVBlank();
	gfxFlushBuffers();
	//gfxSwapBuffers();
	// TODO: tearing??
	/*
	gfxSetDoubleBuffering(GFX_TOP, false);
	gfxScreenSwapBuffers(GFX_TOP, true);
	gfxSetDoubleBuffering(GFX_TOP, true);
	gfxScreenSwapBuffers(GFX_BOTTOM, true);
	*/
}

void Window_FreeFramebuffer(struct Bitmap* bmp) {
	Mem_Free(bmp->scan0);
}


/*########################################################################################################################*
*------------------------------------------------------Soft keyboard------------------------------------------------------*
*#########################################################################################################################*/
static void OnscreenTextChanged(const char* text) {
	char tmpBuffer[NATIVE_STR_LEN];
	cc_string tmp = String_FromArray(tmpBuffer);
	String_AppendUtf8(&tmp, text, String_Length(text));
    
	Event_RaiseString(&InputEvents.TextChanged, &tmp);
	Input_SetPressed(CCKEY_ENTER);
	Input_SetReleased(CCKEY_ENTER);
}

void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) {
	const char* btnText = args->type & KEYBOARD_FLAG_SEND ? "Send" : "Enter";
	char input[NATIVE_STR_LEN]  = { 0 };
	char output[NATIVE_STR_LEN] = { 0 };
	SwkbdState swkbd;
	String_EncodeUtf8(input, args->text);
	
	int mode = args->type & 0xFF;
	int type = (mode == KEYBOARD_TYPE_INTEGER || mode == KEYBOARD_TYPE_NUMBER) ? SWKBD_TYPE_NUMPAD : SWKBD_TYPE_WESTERN;
	
	swkbdInit(&swkbd, type, 3, -1);
	swkbdSetInitialText(&swkbd, input);
	swkbdSetHintText(&swkbd, args->placeholder);
	//swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
	//swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, btnText, true);
	swkbdSetButton(&swkbd, SWKBD_BUTTON_CONFIRM, btnText, true);
	
	if (mode == KEYBOARD_TYPE_INTEGER) {
		swkbdSetNumpadKeys(&swkbd, '-', 0);
	} else if (mode == KEYBOARD_TYPE_NUMBER) {
		swkbdSetNumpadKeys(&swkbd, '-', '.');
	}
	
	if (mode == KEYBOARD_TYPE_PASSWORD)
		swkbdSetPasswordMode(&swkbd, SWKBD_PASSWORD_HIDE_DELAY);
	if (args->multiline)
		swkbdSetFeatures(&swkbd, SWKBD_MULTILINE);
		
	// TODO filter callbacks and OnscreenKeyboard_SetText ??
	int btn = swkbdInputText(&swkbd, output, sizeof(output));
	if (btn != SWKBD_BUTTON_CONFIRM) return;
	OnscreenTextChanged(output);
}
void OnscreenKeyboard_SetText(const cc_string* text) { }
void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) { }
void OnscreenKeyboard_Draw3D(void) { }
void OnscreenKeyboard_Close(void) { /* TODO implement */ }


/*########################################################################################################################*
*-------------------------------------------------------Misc/Other--------------------------------------------------------*
*#########################################################################################################################*/
void Window_ShowDialog(const char* title, const char* msg) {
	/* TODO implement */
	Platform_LogConst(title);
	Platform_LogConst(msg);
}

cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) {
	return ERR_NOT_SUPPORTED;
}

cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) {
	return ERR_NOT_SUPPORTED;
}
#endif