/* sdl2.c --- SDL2 routines used by the TV and KBD interfaces
 */

#include <signal.h>

#include <SDL.h>

#include "tv.h"
#include "kbd.h"
#include "mouse.h"
#include "utrace.h"
#include "idle.h"

#include <X11/keysym.h>		// for XK_FOO  meh
#include <X11/X.h>		// for FOOMapIndex  meh

SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;

int
sdl2_bucky(SDL_KeyboardEvent e)
{
	int mod = SDL_GetModState();
	if (mod & KMOD_CTRL && (e.keysym.sym == SDLK_LCTRL || e.keysym.sym == SDLK_RCTRL))
		return ControlMapIndex;
	if (mod & KMOD_SHIFT && (e.keysym.sym == SDLK_LSHIFT || e.keysym.sym == SDLK_RSHIFT))
		return ShiftMapIndex;
	if (mod & KMOD_ALT && (e.keysym.sym == SDLK_LALT || e.keysym.sym == SDLK_RALT))
		return Mod1MapIndex;
//      if (mod & KMOD_NUM) return Mod2MapIndex;
//      if (mod & KMOD_MODE) return Mod3MapIndex;
	if (mod & KMOD_GUI && (e.keysym.sym == SDLK_LGUI || e.keysym.sym == SDLK_RGUI))
		return Mod4MapIndex;
//      if (mod & KMOD_SCROLL) return Mod5MapIndex;
	return -1;
}

int
sdl2_keysym_to_xk(SDL_KeyboardEvent e)
{
	/* *INDENT-OFF* */
	switch (e.keysym.sym) {
	case SDLK_ESCAPE: return XK_Escape;
	case SDLK_F1: return XK_F1;
	case SDLK_F2: return XK_F2;
	case SDLK_F3: return XK_F3;
	case SDLK_F4: return XK_F4;
	case SDLK_F5: return XK_F5;
	case SDLK_F6: return XK_F6;
	case SDLK_F7: return XK_F7;
	case SDLK_PAGEUP: return XK_Page_Up;
	case SDLK_PAGEDOWN: return XK_Page_Down;
	case SDLK_HOME: return XK_Home;
	case SDLK_END: return XK_End;
	case SDLK_LEFT: return XK_Left;
	case SDLK_RIGHT: return XK_Right;
	case SDLK_UP: return XK_Up;
	case SDLK_DOWN: return XK_Down;
	case SDLK_BACKQUOTE: return e.keysym.mod & KMOD_SHIFT ? XK_asciitilde : XK_grave;
	case SDLK_1: return e.keysym.mod & KMOD_SHIFT ? XK_exclam : XK_1;
	case SDLK_2: return e.keysym.mod & KMOD_SHIFT ? XK_at : XK_2;
	case SDLK_3: return e.keysym.mod & KMOD_SHIFT ? XK_numbersign : XK_3;
	case SDLK_4: return e.keysym.mod & KMOD_SHIFT ? XK_dollar : XK_4;
	case SDLK_5: return e.keysym.mod & KMOD_SHIFT ? XK_percent : XK_5;
	case SDLK_6: return e.keysym.mod & KMOD_SHIFT ? XK_asciicircum : XK_6;
	case SDLK_7: return e.keysym.mod & KMOD_SHIFT ? XK_ampersand : XK_7;
	case SDLK_8: return e.keysym.mod & KMOD_SHIFT ? XK_asterisk : XK_8;
	case SDLK_9: return e.keysym.mod & KMOD_SHIFT ? XK_parenleft : XK_9;
	case SDLK_0: return e.keysym.mod & KMOD_SHIFT ? XK_parenright : XK_0;
	case SDLK_MINUS: return e.keysym.mod & KMOD_SHIFT ? XK_underscore : XK_minus;
	case SDLK_EQUALS: return e.keysym.mod & KMOD_SHIFT ? XK_plus : XK_equal;
	case SDLK_TAB: return XK_Tab;
	case SDLK_q: return e.keysym.mod & KMOD_SHIFT ? XK_Q : XK_q;
	case SDLK_w: return e.keysym.mod & KMOD_SHIFT ? XK_W : XK_w;
	case SDLK_e: return e.keysym.mod & KMOD_SHIFT ? XK_E : XK_e;
	case SDLK_r: return e.keysym.mod & KMOD_SHIFT ? XK_R : XK_r;
	case SDLK_t: return e.keysym.mod & KMOD_SHIFT ? XK_T : XK_t;
	case SDLK_y: return e.keysym.mod & KMOD_SHIFT ? XK_Y : XK_y;
	case SDLK_u: return e.keysym.mod & KMOD_SHIFT ? XK_U : XK_u;
	case SDLK_i: return e.keysym.mod & KMOD_SHIFT ? XK_I : XK_i;
	case SDLK_o: return e.keysym.mod & KMOD_SHIFT ? XK_O : XK_o;
	case SDLK_p: return e.keysym.mod & KMOD_SHIFT ? XK_P : XK_p;
	case SDLK_LEFTBRACKET: return e.keysym.mod & KMOD_SHIFT ? XK_braceleft : XK_bracketleft;
	case SDLK_RIGHTBRACKET: return e.keysym.mod & KMOD_SHIFT ? XK_braceright : XK_bracketright;
	case SDLK_BACKSLASH: return e.keysym.mod & KMOD_SHIFT ? XK_bar : XK_backslash;
	case SDLK_BACKSPACE: return XK_BackSpace;
	case SDLK_a: return e.keysym.mod & KMOD_SHIFT ? XK_A : XK_a;
	case SDLK_s: return e.keysym.mod & KMOD_SHIFT ? XK_S : XK_s;
	case SDLK_d: return e.keysym.mod & KMOD_SHIFT ? XK_D : XK_d;
	case SDLK_f: return e.keysym.mod & KMOD_SHIFT ? XK_F : XK_f;
	case SDLK_g: return e.keysym.mod & KMOD_SHIFT ? XK_G : XK_g;
	case SDLK_h: return e.keysym.mod & KMOD_SHIFT ? XK_H : XK_h;
	case SDLK_j: return e.keysym.mod & KMOD_SHIFT ? XK_J : XK_j;
	case SDLK_k: return e.keysym.mod & KMOD_SHIFT ? XK_K : XK_k;
	case SDLK_l: return e.keysym.mod & KMOD_SHIFT ? XK_L : XK_l;
	case SDLK_SEMICOLON: return e.keysym.mod & KMOD_SHIFT ? XK_colon : XK_semicolon;
	case SDLK_QUOTE: return e.keysym.mod & KMOD_SHIFT ? XK_quotedbl : XK_apostrophe;
	case SDLK_RETURN: return XK_Return;
	case SDLK_z: return e.keysym.mod & KMOD_SHIFT ? XK_Z : XK_z;
	case SDLK_x: return e.keysym.mod & KMOD_SHIFT ? XK_X : XK_x;
	case SDLK_c: return e.keysym.mod & KMOD_SHIFT ? XK_C : XK_c;
	case SDLK_v: return e.keysym.mod & KMOD_SHIFT ? XK_V : XK_v;
	case SDLK_b: return e.keysym.mod & KMOD_SHIFT ? XK_B : XK_b;
	case SDLK_n: return e.keysym.mod & KMOD_SHIFT ? XK_N : XK_n;
	case SDLK_m: return e.keysym.mod & KMOD_SHIFT ? XK_M : XK_m;
	case SDLK_COMMA: return e.keysym.mod & KMOD_SHIFT ? XK_less : XK_comma;
	case SDLK_PERIOD: return e.keysym.mod & KMOD_SHIFT ? XK_greater : XK_period;
	case SDLK_SLASH: return e.keysym.mod & KMOD_SHIFT ? XK_question : XK_slash;
	case SDLK_SPACE: return XK_space;

// case SDLK_MODE: return ???;
	case SDLK_LSHIFT: return XK_Shift_L;
	case SDLK_RSHIFT: return XK_Shift_R;
	case SDLK_LCTRL: return XK_Control_L;
	case SDLK_RCTRL: return XK_Control_R;
	case SDLK_CAPSLOCK: return XK_Caps_Lock;
// case ???: return XK_Shift_Lock;

	case SDLK_LGUI: return XK_Meta_L;
	case SDLK_RGUI: return XK_Meta_R;
	case SDLK_LALT: return XK_Alt_L;
	case SDLK_RALT: return XK_Alt_R;
//      case SDLK_LSUPER: return XK_Super_L;
//      case SDLK_RSUPER: return XK_Super_R;
//      case SDLK_LHYPER: return XK_Hyper_L;
//      case SDLK_RHYPER: return XK_Hyper_R;

	default: return XK_VoidSymbol;
	}
	/* *INDENT-ON* */
}

bool
cadet_allup_key(void)
{
	bool allup;
	int mods;
	int shifts;

	int statesize;
	Uint8 *state;

	allup = true;
	mods = 0;
	shifts = 0;

	state = SDL_GetKeyboardState(&statesize);
	for (int i = 0; allup && i < statesize; i++) {
		int bucky;

		if (state[i] != 1)
			continue; /* bucky not pressed */
		bucky = -1;
		// This needs to be cleaned up -- translates scancode to bucky xk/index
		switch (SDL_GetKeyFromScancode(i)) {
		case SDLK_LSHIFT: case SDLK_RSHIFT: bucky = modifier_map[ShiftMapIndex]; break;
		case SDLK_LCTRL: case SDLK_RCTRL: bucky = modifier_map[ControlMapIndex]; break;
		case SDLK_LALT: case SDLK_RALT: bucky = modifier_map[Mod1MapIndex]; break;
		case SDLK_LGUI: case SDLK_RGUI:  bucky = modifier_map[Mod4MapIndex]; break;
//		case SDLK_LSUPER: case SDLK_RSUPER: 
//		case SDLK_MODE: bucky = modifier_map[Mod5MapIndex]; break;
//		case SDLK_MENU: bucky = modifier_map[KBD_NoSymbol]; break;
//		case SDLK_COMPOSE: bucky = modifier_map[]; break;
		case SDLK_CAPSLOCK:bucky = modifier_map[LockMapIndex]; break;
		}
		DEBUG(TRACE_KBD, "cadet_allup_key() - bucky pressed (%d), i = %d\n", bucky, i);
		cadet_press_bucky(bucky, &mods, &shifts);
	}
	if (allup == true) {
		DEBUG(TRACE_KBD, "cadet_allup_key() - all-up event; mods = 0%o, shifts = 0%o\n", mods, shifts);
		cadet_shifts = shifts;
		cadet_allup_event(mods);
	}
	return allup;
}

static void
process_key(SDL_KeyboardEvent e, int keydown)
{
//      KeySym keysym;
//      SDLKey keycode;

	int bi;
	int keysym;

	idle_keyboard_activity();
	keysym = sdl2_keysym_to_xk(e);
	if (keysym == XK_VoidSymbol || keysym > NELEM(kbd_map)) {
		NOTICE(TRACE_USIM, "cadet@sdl2): unable to translate to keysym (keysym = 0%o)\n", keysym);
		return;
	}
	if (kbd_type == 0) {
		if (!keydown)
			return;
		bi = 0;
		int mod = SDL_GetModState();
		if (mod & KMOD_SHIFT)
			knight_process_bucky(ShiftMapIndex, &bi);
		if (mod & KMOD_CAPS)
			knight_process_bucky(LockMapIndex, &bi);
		if (mod & KMOD_CTRL)
			knight_process_bucky(ControlMapIndex, &bi);
//              if (mod & Mod1Mask)
//                      knight_process_bucky(Mod1MapIndex, &bi);
//              if (mod & Mod2Mask)
//                      knight_process_bucky(Mod2MapIndex, &bi);
//              if (mod & Mod3Mask)
//                      knight_process_bucky(Mod3MapIndex, &bi);
//              if (mod & Mod4Mask)
//                      knight_process_bucky(Mod4MapIndex, &bi);
//              if (mod & Mod5Mask)
//                      knight_process_bucky(Mod5MapIndex, &bi);
		knight_process_key(e.keysym.sym, bi, keydown);
	} else {
		bi = sdl2_bucky(e);
		cadet_process_key(keysym /* xk_keysym */ , bi, keydown, &cadet_allup_key);
	}
}

void
update(int u_minh, int u_minv, int hs, int vs)
{
	SDL_UpdateTexture(texture, NULL, tv_bitmap, tv_width * sizeof(Uint32));
	// Flush
	SDL_RenderClear(renderer);
	SDL_RenderCopy(renderer, texture, NULL, NULL);
	SDL_RenderPresent(renderer);
}

void
sdl2_event(void)
{
	SDL_Event ev;

	send_accumulated_updates(&update);
	kbd_dequeue_key_event();
	if (is_mouse_warp) {
		is_mouse_warp = 0;
		SDL_WarpMouseInWindow(window, mouse_warp_x, mouse_warp_y);
	}
	while (SDL_PollEvent(&ev)) {
		switch (ev.type) {
		case SDL_WINDOWEVENT:
			SDL_UpdateTexture(texture, NULL, tv_bitmap, tv_width * sizeof(Uint32));
			// flush
			SDL_RenderClear(renderer);
			SDL_RenderCopy(renderer, texture, NULL, NULL);
			SDL_RenderPresent(renderer);
			break;
		case SDL_KEYDOWN:
			process_key(ev.key, 1);
			break;
		case SDL_KEYUP:
			process_key(ev.key, 0);
			break;
		case SDL_MOUSEMOTION:
		case SDL_MOUSEBUTTONDOWN:
		case SDL_MOUSEBUTTONUP:
			mouse_event(ev.button.x, ev.button.y, ev.button.button);
			break;
		case SDL_QUIT:
			exit(0);
			break;
		}
	}
}

float xbeep_volume = 1.0f;
int AMPLITUDE = 28000;
const int SAMPLE_RATE = 44100;

int xbeep_sample_nr = 0;
float xbeep_freq = 0.0f;

void
xbeep_audio_callback(void *user_data, Uint8 *raw_buffer, int bytes)
{
	Sint16 *buffer = (Sint16 *) raw_buffer;
	int length = bytes / 2;	// 2 bytes per sample for AUDIO_S16SYS
	float *freq = (float *) (user_data);
	int amp = AMPLITUDE * xbeep_volume;

	for (int i = 0; i < length; i++, xbeep_sample_nr++) {
		double time = (double) xbeep_sample_nr / (double) SAMPLE_RATE;
		buffer[i] = (Sint16) (amp * sin(2.0f * M_PI * (*freq) * time));	// render sine wave
	}
}

void
xbeep_audio_init(void)
{
	SDL_AudioSpec want;
	SDL_AudioSpec have;

	want.freq = SAMPLE_RATE;
	want.format = AUDIO_S16SYS;
	want.channels = 1;
	want.samples = 2048;
	want.callback = xbeep_audio_callback;
	want.userdata = &xbeep_freq;
	if (SDL_OpenAudio(&want, &have) != 0)
		SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Failed to open audio: %s", SDL_GetError());
	if (want.format != have.format)
		SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Failed to get the desired AudioSpec");
	SDL_PauseAudio(1);
}

void
xbeep_audio_close(void)
{
	SDL_CloseAudio();
}

void
xbeep(int halfwavelength, int duration)
{
	xbeep_freq = 1000000.0f / (float) (halfwavelength * 2);
	xbeep_sample_nr = 0;

	SDL_PauseAudio(0);
	SDL_Delay(duration / 1000);
	SDL_PauseAudio(1);
}

void
sdl2_beep(int v)
{
///XBEEP (MISC-INST-ENTRY %BEEP)
///;;; First argument is half-wavelength, second is duration.  Both are in microseconds.
///;;; M-1 has 2nd argument (duration) which is added to initial time-check
///;;; M-2 contains most recent time check
///;;;     to compute quitting time
///;;; M-C contains 1st argument, the wavelength
///;;; M-4 contains the time at which the next click must be done.
///;;; Note that the 32-bit clock wraps around once an hour, we have to be careful
///;;; to compare clock values in the correct way, namely without overflow checking.
	extern uint32_t mmem[32];
#define DTP_FIX_VAL(x) ((x) & ((1 << 24) - 1))
#define DTP_FIX(x) (DTP_FIX_VAL(x) | 5 << 25)
	int wavelength = DTP_FIX_VAL(mmem[7]);	//M-C M-MEM 7
	int duration = DTP_FIX_VAL(mmem[22]);	//M-1 M-MEM 22
	xbeep(wavelength, duration);
}

void
sdl2_init(void)
{
	NOTICE(TRACE_USIM, "tv: using SDL2 backend for monitor and keyboard\n");

	Foreground = 0xffffff;	// White
	Background = 0x000000;	// Black

	xbeep_audio_init();
	SDL_CreateWindowAndRenderer(tv_width, tv_height, SDL_WINDOW_OPENGL, &window, &renderer);
	SDL_ShowCursor(0);	/* Invisible cursor. */

//      SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
//      SDL_RenderClear(renderer);
//      SDL_RenderPresent(renderer);

	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");	// make the scaled rendering look smoother.
	SDL_RenderSetLogicalSize(renderer, tv_width, tv_height);

	texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, tv_width, tv_height);
}
