/* -*- tab-width: 8; indent-tabs-mode: t; -*- */

/*
 * StratoHAL
 * Main code for Linux X11 OpenGL
 */

/*-
 * SPDX-License-Identifier: Zlib
 *
 * Copyright (c) 2025-2026 Awe Morris
 * Copyright (c) 1996-2024 Keiichi Tabata
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *	claim that you wrote the original software. If you use this software
 *	in a product, an acknowledgment in the product documentation would be
 *	appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *	misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

/*
 * If we use Wayland/X11-dual support, we use EGL and OpenGL ES 2 for X11.
 * If we use X11-only support, we use xgl and OpenGL 3 for X11.
 */

/* HAL */
#include <strato/strato.h>		/* Public Interface */
#include "callback.h"
#include "stdfile.h"			/* Standard C File Implementation */
#if defined(HAL_TARGET_LINUX)
#include "asound.h"			/* ALSA Sound Implemenatation */
#else
#include "bsdsound.h"			/* BSD Sound Implementation */
#endif
#include "evgamepad.h"			/* evdev Gamepad */

/* Xlib */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/xpm.h>
#include <X11/Xatom.h>
#include <X11/Xlocale.h>

/* OpenGL */
#if defined(HAL_USE_X11_ONLY)
#include <GL/gl.h>			/* OpenGL */
#include <GL/glx.h>			/* GLX */
#else
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#endif
#include "glrender.h"			/* OpenGL Compatibility Helper */

/* POSIX */
#include <sys/types.h>
#include <sys/stat.h>	/* stat(), mkdir() */
#include <sys/time.h>	/* gettimeofday() */
#include <unistd.h>	/* usleep(), access() */

/* Standard C */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <assert.h>

/* Gstreamer Video HAL */
#include "gstplay.h"

/* Color Format */
#define DEPTH		(24)
#define BPP		(32)

/* Log File */
#define LOG_FILE	"log.txt"

/* Save Directory */
#define SAVE_DIR	"save"

/* Frame Time */
#define FRAME_MILLI	(16)	/* Millisec of a frame */
#define SLEEP_MILLI	(5)	/* Millisec to sleep */

/* Window config. */
static char *window_title;
static int screen_width;
static int screen_height;
static int viewport_width;
static int viewport_height;
static float mouse_scale = 1.0f;
static int mouse_ofs_x;
static int mouse_ofs_y;
static bool is_full_screen;
static int physical_width;
static int physical_height;

/* X11 objects. */
Display *display;
Window window = BadAlloc;
static int screen;
static Pixmap icon = BadAlloc;
static Pixmap icon_mask = BadAlloc;
static Atom delete_message = BadAlloc;

/* GLX objects. */
#if defined(HAL_USE_X11_ONLY)
static GLXWindow glx_window = None;
static GLXContext glx_context = None;
#endif

/* EGL objects. */
#if !defined(HAL_USE_X11_ONLY)
static EGLDisplay egl_display;
static EGLContext egl_context;
static EGLSurface egl_surface;
#endif

/* Frame start time. */
static struct timeval tv_start;

/* Log file. */
#define LOG_BUF_SIZE	(4096)
static FILE *log_fp;

/* Locale. */
static const char *lang_code;

/* Flag to indicate whether we are playing a video or not. */
static bool is_gst_playing;

/* Flag to indicate whether a video is skippable or not. */
static bool is_gst_skippable;

/* Icon */
extern char *icon_xpm[35];

/* HAL callback. */
#if defined(HAL_USE_X11_ONLY)
struct hal_callback hal_callback;
HAL_DLL bool (*hal_bootstrap_ptr)(char **title, int *width, int *height, struct hal_callback *callback);
#endif

#if defined(HAL_USE_X11_ONLY)
/* OpenGL 3.0 API. */
GLuint (APIENTRY *glCreateShader)(GLenum type);
void (APIENTRY *glShaderSource)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
void (APIENTRY *glCompileShader)(GLuint shader);
void (APIENTRY *glGetShaderiv)(GLuint shader, GLenum pname, GLint *params);
void (APIENTRY *glGetShaderInfoLog)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
void (APIENTRY *glAttachShader)(GLuint program, GLuint shader);
void (APIENTRY *glLinkProgram)(GLuint program);
void (APIENTRY *glGetProgramiv)(GLuint program, GLenum pname, GLint *params);
void (APIENTRY *glGetProgramInfoLog)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
GLuint (APIENTRY *glCreateProgram)(void);
void (APIENTRY *glUseProgram)(GLuint program);
void (APIENTRY *glGenVertexArrays)(GLsizei n, GLuint *arrays);
void (APIENTRY *glBindVertexArray)(GLuint array);
void (APIENTRY *glGenBuffers)(GLsizei n, GLuint *buffers);
void (APIENTRY *glBindBuffer)(GLenum target, GLuint buffer);
GLint (APIENTRY *glGetAttribLocation)(GLuint program, const GLchar *name);
void (APIENTRY *glVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
void (APIENTRY *glEnableVertexAttribArray)(GLuint index);
GLint (APIENTRY *glGetUniformLocation)(GLuint program, const GLchar *name);
void (APIENTRY *glUniform1i)(GLint location, GLint v0);
void (APIENTRY *glBufferData)(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
void (APIENTRY *glDeleteShader)(GLuint shader);
void (APIENTRY *glDeleteProgram)(GLuint program);
void (APIENTRY *glDeleteVertexArrays)(GLsizei n, const GLuint *arrays);
void (APIENTRY *glDeleteBuffers)(GLsizei n, const GLuint *buffers);
struct API
{
	void **func;
	const char *name;
};
static struct API api[] =
{
	{(void **)&glCreateShader, "glCreateShader"},
	{(void **)&glShaderSource, "glShaderSource"},
	{(void **)&glCompileShader, "glCompileShader"},
	{(void **)&glGetShaderiv, "glGetShaderiv"},
	{(void **)&glGetShaderInfoLog, "glGetShaderInfoLog"},
	{(void **)&glAttachShader, "glAttachShader"},
	{(void **)&glLinkProgram, "glLinkProgram"},
	{(void **)&glGetProgramiv, "glGetProgramiv"},
	{(void **)&glGetProgramInfoLog, "glGetProgramInfoLog"},
	{(void **)&glCreateProgram, "glCreateProgram"},
	{(void **)&glUseProgram, "glUseProgram"},
	{(void **)&glGenVertexArrays, "glGenVertexArrays"},
	{(void **)&glBindVertexArray, "glBindVertexArray"},
	{(void **)&glGenBuffers, "glGenBuffers"},
	{(void **)&glBindBuffer, "glBindBuffer"},
	{(void **)&glGetAttribLocation, "glGetAttribLocation"},
	{(void **)&glVertexAttribPointer, "glVertexAttribPointer"},
	{(void **)&glEnableVertexAttribArray, "glEnableVertexAttribArray"},
	{(void **)&glGetUniformLocation, "glGetUniformLocation"},
	{(void **)&glUniform1i, "glUniform1i"},
	{(void **)&glBufferData, "glBufferData"},
	{(void **)&glDeleteShader, "glDeleteShader"},
	{(void **)&glDeleteProgram, "glDeleteProgram"},
	{(void **)&glDeleteVertexArrays, "glDeleteVertexArrays"},
	{(void **)&glDeleteBuffers, "glDeleteBuffers"},
};
#endif

/* forward declaration */
static void init_locale(void);
static bool init_hal(int argc, char *argv[]);
static bool open_log_file(void);
static void close_log_file(void);
static bool open_display(void);
static bool create_window(void);
static bool set_window_title(void);
static bool show_window(void);
static void set_window_size(void);
static bool set_event_mask(void);
static void set_close_button(void);
static void close_display(void);
static bool setup_window(void);
static bool create_icon_image(void);
static bool set_icon(void);
static void cleanup_hal(void);
static void destroy_window(void);
static void destroy_icon_image(void);
static void run_game_loop(void);
static bool run_frame(void);
static void render_video_frame(void);
static bool wait_for_next_frame(void);
static bool next_event(void);
static void event_key_press(XEvent *event);
static void event_key_release(XEvent *event);
static int get_key_code(XEvent *event);
static void event_button_press(XEvent *event);
static void event_button_release(XEvent *event);
static void event_motion_notify(XEvent *event);
static void event_resize(XEvent *event);
static void update_viewport_size(int width, int height);

/*
 * Main
 */

#if defined(HAL_USE_X11_ONLY)
HAL_DLL
int
hal_main(
     int argc,
     char *argv[])
{
	/* Initialize HAL. */
	if (!init_hal(argc, argv))
		return 1;

	/* Do a start callback. */
	if (!hal_callback.on_start())
		return 1;

	/* Run game loop. */
	run_game_loop();

	/* Do a stop callback.. */
	hal_callback_on_event_stop();

	/* Cleanup HAL. */
	cleanup_hal();

	return 0;
}
#else
bool
main_init_x11(
	int argc,
	char *argv[])
{
	/* Initialize HAL. */
	if (!init_hal(argc, argv))
		return false;

	return true;
}

bool
main_run_x11(void)
{
	/* Do a start callback. */
	if (!hal_callback.on_start())
		return false;

	/* Run game loop. */
	run_game_loop();

	/* Do a stop callback.. */
	hal_callback.on_stop();

	/* Cleanup HAL. */
	cleanup_hal();

	return true;
}
#endif

/* Initialize HAL. */
static bool
init_hal(
	 int argc,
	 char *argv[])
{
#if defined(HAL_USE_LIBINTL)
	bindtextdomain("libstrato", LOCALEDIR);
	bind_textdomain_codeset("libstrato", "UTF-8");
#endif

	/* Initialize the locale. */
	init_locale();

	/* Initialize the file HAL. */
	if (!init_file())
		return false;

	/* Do a boot callback. */
	if (!hal_bootstrap_ptr(&window_title, &screen_width, &screen_height, &hal_callback))
		return false;

	/* Initialize the sound HAL. */
	if (!init_sound()) {
		/* Ignore a failure. */
		hal_log_info("Can't initialize sound.\n");
	}

	/* Open an X11 display. */
	if (!open_display()) {
		hal_log_error("Can't open display.\n");
		return false;
	}

	/* Create a window. */
	if (!create_window()) {
		hal_log_error("Failed to initialize graphics.");
		return false;
	}

	/* Setup the window. */
	if (!setup_window()) {
		hal_log_error("Failed to setup a window.");
		return false;
	}

	/* Create an icon. */
	if (!create_icon_image()) {
		hal_log_error("Can't create icon.\n");
		return false;
	}

	/* Set the icon. */
	set_icon();

	/* Initialize the gamepad. */
	init_evgamepad();

	/* Initialize the viddeo HAL. */
	gstplay_init(argc, argv);

	return true;
}

/* Initialize the locale. */
static void
init_locale(void)
{
	const char *locale = setlocale(LC_MESSAGES, "");
        if (locale == NULL || locale[0] == '\0') {
		locale = getenv("LC_ALL");
		if (locale == NULL || locale[0] == '\0') {
			locale = getenv("LC_MESSAGES");
			if (locale == NULL || locale[0] == '\0')
				locale = getenv("LANG");
		}
	}
	if (locale == NULL || locale[0] == '\0') {
		lang_code = "en";
		return;
	}

	/* English */
	if (strncmp(locale, "en_AU", 5) == 0) {
		lang_code = "en-au";
		return;
	}
	if (strncmp(locale, "en_GB", 5) == 0) {
		lang_code = "en-gb";
		return;
	}
	if (strncmp(locale, "en_NZ", 5) == 0) {
		lang_code = "en-nz";
		return;
	}
	if (strncmp(locale, "en_US", 5) == 0) {
		lang_code = "en-us";
		return;
	}
	if (strncmp(locale, "en", 2) == 0) {
		lang_code = "en";
		return;
	}

	/* French */
	if (strncmp(locale, "fr_CA", 5) == 0) {
		lang_code = "fr-ca";
		return;
	}
	if (strncmp(locale, "fr", 2) == 0) {
		lang_code = "fr";
		return;
	}

	/* Spanish */
	if (strncmp(locale, "es_ES", 5) == 0) {
		lang_code = "es";
		return;
	}
	if (strncmp(locale, "es", 2) == 0) {
		lang_code = "es-la";
		return;
	}

	/* Chinese */
	if (strncmp(locale, "zh_TW", 5) == 0 ||
	    strncmp(locale, "zh_HK", 5) == 0) {
		lang_code = "zh-tw";
		return;
	}
	if (strncmp(locale, "zh", 2) == 0) {
		lang_code = "zh-cn";
		return;
	}

	/* Others */
	if (strncmp(locale, "ja", 2) == 0) {
		lang_code = "ja";
		return;
	}
	if (strncmp(locale, "de", 2) == 0) {
		lang_code = "de";
		return;
	}
	if (strncmp(locale, "it", 2) == 0){
		lang_code = "it";
		return;
	}
	if (strncmp(locale, "el", 2) == 0) {
		lang_code = "el";
		return;
	}
	if (strncmp(locale, "ru", 2) == 0) {
		lang_code = "ru";
		return;
	}
	if (strncmp(locale, "ko", 2) == 0) {
		lang_code = "ko";
		return;
	}

	/* Fallback */
	lang_code = "en";
}

/* Open an X11 display. */
static bool
open_display(void)
{
	display = XOpenDisplay(NULL);
	if (display == NULL) {
		hal_log_error("XOpenDisplay() failed.");
		return false;
	}
	return true;
}

#if defined(HAL_USE_X11_ONLY)
/* Create a GLX window. */
static bool
create_window(void)
{
	int pix_attr[] = {
		GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
		GLX_RENDER_TYPE, GLX_RGBA_BIT,
		GLX_DOUBLEBUFFER, True,
		GLX_RED_SIZE, 1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 1,
		None
	};
	int ctx_attr[]= {
		GLX_CONTEXT_MAJOR_VERSION_ARB, 2,
		GLX_CONTEXT_MINOR_VERSION_ARB, 0,
		GLX_CONTEXT_FLAGS_ARB, 0,
		GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
		None
	};
	GLXContext (*glXCreateContextAttribsARB)(Display *dpy,
						 GLXFBConfig config,
						 GLXContext share_context,
						 Bool direct,
						 const int *attrib_list);
	GLXFBConfig *config;
	XVisualInfo *vi;
	XSetWindowAttributes swa;
	XEvent event;
	int i, n;

	viewport_width = screen_width;
	viewport_height = screen_height;

	/* Get the physical display size. */
	screen = DefaultScreen(display);
	physical_width = DisplayWidth(display, screen);
	physical_height = DisplayHeight(display, screen);

	/* Choose a framebuffer format. */
	config = glXChooseFBConfig(display, screen, pix_attr, &n);
	if (config == NULL)
		return false;
	vi = glXGetVisualFromFBConfig(display, config[0]);

	/* Create a window. */
	swa.border_pixel = 0;
	swa.event_mask = StructureNotifyMask;
	swa.colormap = XCreateColormap(display,
				       RootWindow(display, vi->screen),
				       vi->visual,
				       AllocNone);
	window = XCreateWindow(display,
			       RootWindow(display, vi->screen),
			       0,
			       0,
			       (unsigned int)screen_width,
			       (unsigned int)screen_height,
			       0,
			       vi->depth,
			       InputOutput,
			       vi->visual,
			       CWBorderPixel | CWColormap | CWEventMask,
			       &swa);
	XFree(vi);

	/* Create a GLX context. */
	glXCreateContextAttribsARB = (void *)glXGetProcAddress((const unsigned char *)"glXCreateContextAttribsARB");
	if (glXCreateContextAttribsARB == NULL) {
		XDestroyWindow(display, window);
		return false;
	}
	glx_context = glXCreateContextAttribsARB(display, config[0], 0, True, ctx_attr);
	if (glx_context == NULL) {
		XDestroyWindow(display, window);
		return false;
	}

	/* Create a GLX window. */
	glx_window = glXCreateWindow(display, config[0], window, NULL);
	XFree(config);

	/* Map the window to the screen, and wait for showing. */
	XMapWindow(display, window);
	XNextEvent(display, &event);

	/* Bind the GLX context to the window. */
	glXMakeContextCurrent(display, glx_window, glx_window, glx_context);

	/* Get the API pointers. */
	for (i = 0; i < (int)(sizeof(api)/sizeof(struct API)); i++) {
		*api[i].func = (void *)glXGetProcAddress((const unsigned char *)api[i].name);
		if(*api[i].func == NULL) {
			hal_log_info("Failed to get %s", api[i].name);
			glXMakeContextCurrent(display, None, None, None);
			glXDestroyContext(display, glx_context);
			glXDestroyWindow(display, glx_window);
			glx_context = None;
			glx_window = None;
			return false;
		}
	}

	/* Initialize the OpenGL rendering subsystem. */
	if (!init_opengl(screen_width, screen_height)) {
		glXMakeContextCurrent(display, None, None, None);
		glXDestroyContext(display, glx_context);
		glXDestroyWindow(display, glx_window);
		glx_context = None;
		glx_window = None;
		return false;
	}

	return true;
}
#else
/* Create an EGL window. */
static bool
create_window(void)
{
	EGLint config_attr[] = {
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
		EGL_RED_SIZE, 1,
		EGL_GREEN_SIZE, 1,
		EGL_BLUE_SIZE, 1,
		EGL_ALPHA_SIZE, 0,
		EGL_DEPTH_SIZE, 0,
		EGL_STENCIL_SIZE, 0,
		EGL_NONE
	};
	EGLint ctx_attr[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};
	EGLConfig config;
	EGLint nconfig;
	EGLint visual_id;
	XVisualInfo vi_template;
	XVisualInfo *vi;
	XSetWindowAttributes swa;
	XEvent event;
	int nvi;

	viewport_width = screen_width;
	viewport_height = screen_height;

	screen = DefaultScreen(display);
	physical_width = DisplayWidth(display, screen);
	physical_height = DisplayHeight(display, screen);

	/* Initialize EGL. */
	egl_display = eglGetDisplay((EGLNativeDisplayType)display);
	if (egl_display == EGL_NO_DISPLAY)
		return false;

	if (!eglInitialize(egl_display, NULL, NULL))
		return false;

	if (!eglBindAPI(EGL_OPENGL_ES_API))
		return false;

	/* Choose an EGL framebuffer config. */
	if (!eglChooseConfig(egl_display,
			     config_attr,
			     &config,
			     1,
			     &nconfig) ||
	    nconfig == 0)
		return false;

	/* Get the native X11 visual matching the EGL config. */
	if (!eglGetConfigAttrib(egl_display,
				config,
				EGL_NATIVE_VISUAL_ID,
				&visual_id))
		return false;

	vi_template.visualid = (VisualID)visual_id;
	vi = XGetVisualInfo(display, VisualIDMask, &vi_template, &nvi);
	if (vi == NULL || nvi == 0)
		return false;

	/* Create an X11 window. */
	swa.border_pixel = 0;
	swa.event_mask = StructureNotifyMask;
	swa.colormap = XCreateColormap(display,
				       RootWindow(display, vi->screen),
				       vi->visual,
				       AllocNone);

	window = XCreateWindow(display,
			       RootWindow(display, vi->screen),
			       0,
			       0,
			       (unsigned int)screen_width,
			       (unsigned int)screen_height,
			       0,
			       vi->depth,
			       InputOutput,
			       vi->visual,
			       CWBorderPixel | CWColormap | CWEventMask,
			       &swa);

	XFree(vi);

	if (window == None)
		return false;

	/* Create an EGL window surface. */
	egl_surface = eglCreateWindowSurface(
		egl_display,
		config,
		(EGLNativeWindowType)window,
		NULL);

	if (egl_surface == EGL_NO_SURFACE) {
		XDestroyWindow(display, window);
		window = None;
		return false;
	}

	/* Create a GLESv2 context. */
	egl_context = eglCreateContext(
		egl_display,
		config,
		EGL_NO_CONTEXT,
		ctx_attr);

	if (egl_context == EGL_NO_CONTEXT) {
		eglDestroySurface(egl_display, egl_surface);
		XDestroyWindow(display, window);
		egl_surface = EGL_NO_SURFACE;
		window = None;
		return false;
	}

	/* Map the window and wait until it is visible. */
	XMapWindow(display, window);
	XNextEvent(display, &event);

	/* Bind the GLES context. */
	if (!eglMakeCurrent(egl_display,
			    egl_surface,
			    egl_surface,
			    egl_context)) {
		eglDestroyContext(egl_display, egl_context);
		eglDestroySurface(egl_display, egl_surface);
		XDestroyWindow(display, window);
		egl_context = EGL_NO_CONTEXT;
		egl_surface = EGL_NO_SURFACE;
		window = None;
		return false;
	}

	/* Initialize the GLES rendering subsystem. */
	if (!init_opengl(screen_width, screen_height)) {
		eglMakeCurrent(egl_display,
			       EGL_NO_SURFACE,
			       EGL_NO_SURFACE,
			       EGL_NO_CONTEXT);
		eglDestroyContext(egl_display, egl_context);
		eglDestroySurface(egl_display, egl_surface);
		XDestroyWindow(display, window);
		egl_context = EGL_NO_CONTEXT;
		egl_surface = EGL_NO_SURFACE;
		window = None;
		return false;
	}

	return true;
}
#endif

/* Setup the window. */
static bool
setup_window(void)
{
	/* Set the window title. */
	if (!set_window_title())
		return false;

	/* Show the window. */
	if (!show_window())
		return false;

	/* Set the fixed window size. */
	set_window_size();

	/* Set the event mask. */
	if (!set_event_mask())
		return false;

	/* Capture close button events if possible. */
	set_close_button();

	return true;
}

/* Set the window title. */
static bool
set_window_title(void)
{
	XTextProperty tp;
	Atom net_wm_name;
	Atom utf8_string;
	XClassHint ch;
	int ret;

	/* ICCCM: set legacy WM_NAME too. */
	ret = XmbTextListToTextProperty(display,
					&window_title,
					1,
					XCompoundTextStyle,
					&tp);
	if (ret == XNoMemory || ret == XLocaleNotSupported) {
		hal_log_error("XmbTextListToTextProperty() failed.");
		return false;
	}
	XSetWMName(display, window, &tp);
	XFree(tp.value);

	/* EWMH: KDE/modern WMs prefer _NET_WM_NAME. */
	net_wm_name = XInternAtom(display, "_NET_WM_NAME", False);
	utf8_string = XInternAtom(display, "UTF8_STRING", False);
	XChangeProperty(display,
			window,
			net_wm_name,
			utf8_string,
			8,
			PropModeReplace,
			(const unsigned char *)window_title,
			(int)strlen(window_title));

	/* Set WM_CLASS. */
	ch.res_name = "vn.suika3.engine";
	ch.res_class = "vn.suika3.engine";
	XSetClassHint(display, window, &ch);

	return true;
}

/* Show the window. */
static bool
show_window(void)
{
	int ret;

	ret = XMapWindow(display, window);
	if (ret == BadWindow) {
		hal_log_error("XMapWindow() failed.");
		return false;
	}

	return true;
}

/* Set the fixed window size. */
static void
set_window_size(void)
{
#if 0
	XSizeHints *sh;

	sh = XAllocSizeHints();
	sh->flags = PMinSize | PMaxSize;
	sh->min_width = screen_width;
	sh->min_height = screen_height;
	sh->max_width = screen_width;
	sh->max_height = screen_height;
	XSetWMSizeHints(display, window, sh, XA_WM_NORMAL_HINTS);
	XFree(sh);
#endif
}

/* Set the event mask. */
static bool
set_event_mask(void)
{
	int ret;

	ret = XSelectInput(display,
			   window,
			   KeyPressMask |
			   ExposureMask |
			   ButtonPressMask |
			   ButtonReleaseMask |
			   KeyReleaseMask |
			   PointerMotionMask |
			   StructureNotifyMask);
	if (ret == BadWindow) {
		hal_log_error("XSelectInput() failed.");
		return false;
	}

	return true;
}

/* Capture close button events if possible. */
static void
set_close_button(void)
{
	delete_message = XInternAtom(display, "WM_DELETE_WINDOW", True);
	if (delete_message != None)
		XSetWMProtocols(display, window, &delete_message, 1);
}

/* Create an icon image. */
static bool
create_icon_image(void)
{
	XpmAttributes attr;
	Colormap cm;
	int ret;

	/* Create a color map. */
	cm = XCreateColormap(display, window,
			     DefaultVisual(display, DefaultScreen(display)),
			     AllocNone);
	if (cm == BadAlloc || cm == BadMatch || cm == BadValue ||
	    cm == BadWindow) {
		hal_log_error("XCreateColorMap() failed.");
		return false;
	}

	/* Create a pixmap. */
	attr.valuemask = XpmColormap;
	attr.colormap = cm;
	ret = XpmCreatePixmapFromData(display, window, icon_xpm, &icon, &icon_mask, &attr);
	if (ret != XpmSuccess) {
		hal_log_error("XpmCreatePixmapFromData() failed.");
		XFreeColormap(display, cm);
		return false;
	}
	XFreeColormap(display,cm);

	return true;
}

/* Set _NET_WM_ICON from pixmap/mask. */
static bool
set_icon(void)
{
	XWMHints *win_hints;
	XWindowAttributes wa;
	Atom net_wm_icon;
	Atom cardinal;
	XImage *img;
	XImage *mask_img;
	unsigned long *data;
	int x, y;
	unsigned int width, height;
	unsigned int border_width, depth;
	Window root;

	/* Set the classic icon. */
	win_hints = XAllocWMHints();
	if (win_hints) {
		win_hints->flags = IconPixmapHint | IconMaskHint;
		win_hints->icon_pixmap = icon;
		win_hints->icon_mask = icon_mask;
		XSetWMHints(display, window, win_hints);
		XFree(win_hints);
	}

	/* Get the window attributs. */
	if (!XGetWindowAttributes(display, window, &wa)) {
		hal_log_info("XGetWindowAttributes() failed.");
		return false;
	}

	/* Get the icon size. */
	if (!XGetGeometry(display, icon, &root, &x, &y, &width, &height,
			  &border_width, &depth)) {
		hal_log_info("XGetGeometry() failed.");
		return false;
	}

	/* Create an image. */
        img = XGetImage(display, icon, 0, 0,
			(unsigned int)width, (unsigned int)height,
                        AllPlanes, ZPixmap);
	mask_img = XGetImage(display, icon_mask, 0, 0,
                             (unsigned int)width, (unsigned int)height,
                             1, ZPixmap);
	if (!img || !mask_img) {
                if (img)
                        XDestroyImage(img);
                if (mask_img)
                        XDestroyImage(mask_img);
                hal_log_info("XGetImage() failed.");
                return false;
        }

	/* Create image data. */
        data = (unsigned long *)malloc(sizeof(unsigned long) * (2 + width * height));
        if (!data) {
                XDestroyImage(img);
                XDestroyImage(mask_img);
                hal_log_out_of_memory();
                return false;
        }
        data[0] = (unsigned long)width;
        data[1] = (unsigned long)height;
        for (y = 0; y < (int)height; y++) {
                for (x = 0; x < (int)width; x++) {
                        unsigned long p;
                        unsigned long m;
                        unsigned long r, g, b, a;
                        size_t i;
                        p = XGetPixel(img, x, y);
                        m = XGetPixel(mask_img, x, y);
                        r = ((p & wa.visual->red_mask)   * 255UL) / wa.visual->red_mask;
                        g = ((p & wa.visual->green_mask) * 255UL) / wa.visual->green_mask;
                        b = ((p & wa.visual->blue_mask)  * 255UL) / wa.visual->blue_mask;
                        a = m ? 255UL : 0UL;
                        i = 2 + (size_t)y * (size_t)width + (size_t)x;
                        data[i] = (a << 24) | (r << 16) | (g << 8) | b;
                }
        }

        net_wm_icon = XInternAtom(display, "_NET_WM_ICON", False);
        cardinal = XInternAtom(display, "CARDINAL", False);

        XChangeProperty(display,
			window,
                        net_wm_icon,
			cardinal,
			32,
                        PropModeReplace,
                        (unsigned char *)data,
                        (int)(2 + width * height));

        free(data);
        XDestroyImage(img);
        XDestroyImage(mask_img);

        return true;
}

/* Cleanup the subsystems. */
static void
cleanup_hal(void)
{
	/* Cleanup sound. */
	cleanup_sound();

	/* Destroy the window. */
	destroy_window();

	/* Destroy the icon. */
	destroy_icon_image();

	/* Close the display. */
	close_display();

	/* Close the log file. */
	close_log_file();
}

#if defined(HAL_USE_X11_ONLY)
/* Destroy the GLX window. */
static void
destroy_window(void)
{
	cleanup_opengl();

	glXMakeContextCurrent(display, None, None, None);

	if (glx_context != None) {
		glXDestroyContext(display, glx_context);
		glx_context = None;
	}

	if (glx_window != None) {
		glXDestroyWindow(display, glx_window);
		glx_window = None;
	}

	if (display != NULL) {
		if (window != BadAlloc)
			XDestroyWindow(display, window);
	}
}
#else
/* Destroy the EGL window. */
static void
destroy_window(void)
{
	if (egl_display != EGL_NO_DISPLAY) {
		eglMakeCurrent(egl_display,
			       EGL_NO_SURFACE,
			       EGL_NO_SURFACE,
			       EGL_NO_CONTEXT);

		if (egl_context != EGL_NO_CONTEXT)
			eglDestroyContext(egl_display, egl_context);

		if (egl_surface != EGL_NO_SURFACE)
			eglDestroySurface(egl_display, egl_surface);

		eglTerminate(egl_display);
	}

	egl_display = EGL_NO_DISPLAY;
	egl_context = EGL_NO_CONTEXT;
	egl_surface = EGL_NO_SURFACE;

	if (window != None) {
		XDestroyWindow(display, window);
		window = None;
	}
}
#endif

/* Destroy the icon image. */
static void
destroy_icon_image(void)
{
	if (display != NULL) {
		if (icon != BadAlloc)
			XFreePixmap(display, icon);
		if (icon_mask != BadAlloc)
			XFreePixmap(display, icon_mask);
	}
}

/* Close the X11 display. */
static void
close_display(void)
{
	if (display != NULL)
		XCloseDisplay(display);
}

/* Close the log file. */
static void
close_log_file(void)
{
	if (log_fp != NULL)
		fclose(log_fp);
}

/* Run the event loop. */
static void
run_game_loop(void)
{
	/* Get the frame start time. */
	gettimeofday(&tv_start, NULL);

	/* Main Loop */
	while (true) {
		/* Run a frame. */
		if (!run_frame())
			break;

		/* Wait for the next frame timing. */
		if (!wait_for_next_frame())
			break;	/* Close button was pressed. */

		/* Get the frame start time. */
		gettimeofday(&tv_start, NULL);
	}
}

/* Run a frame. */
static bool
run_frame(void)
{
	bool cont;

	/* Read the gamepad. */
	update_evgamepad();

	if (!is_gst_playing) {
		/* Start rendering. */
		opengl_start_rendering();

		/* Call a frame event. */
		cont = hal_callback.on_update();
		if (cont)
			hal_callback.on_render();

		/* End rendering. */
		opengl_end_rendering();

		/* Swap buffers. */
#if defined(HAL_USE_X11_ONLY)
		glXSwapBuffers(display, glx_window);
#else
		eglSwapBuffers(egl_display, egl_surface);
#endif
	} else {
		/* Render. */
		render_video_frame();

		/* Call a frame event. */
		cont = hal_callback.on_update();

		/* If the playback is finished. */
		if (!gstplay_is_playing()) {
			gstplay_stop();
			is_gst_playing = false;
		}
	}

	return cont;
}

/* Render a video frame. */
static void
render_video_frame(void)
{
	struct hal_image *image;
	int dst_width, dst_height, dst_x, dst_y;

	/* Update the playback stauts. */
	image = gstplay_loop_iteration();
	if (image == NULL) {
		/* Rendering is not required for this game frame. */
		return;
	}

        /* Fit while preserving aspect ratio. */
        if (screen_width * image->height <= screen_height * image->width) {
                dst_width = screen_width;
                dst_height = screen_width * image->height / image->width;
        } else {
                dst_height = screen_height;
                dst_width = screen_height * image->width / image->height;
        }
        dst_x = (screen_width - dst_width) / 2;
        dst_y = (screen_height - dst_height) / 2;

	/* Render. */
	opengl_start_rendering();
        opengl_render_image_normal(
                dst_x,
                dst_y,
                dst_width,
                dst_height,
                image,
                0,
                0,
                image->width,
                image->height,
                255);
	opengl_end_rendering();

	/* Swap buffers. */
#if defined(HAL_USE_X11_ONLY)
	glXSwapBuffers(display, glx_window);
#else
	eglSwapBuffers(egl_display, egl_surface);
#endif
}

/* Wait for the next frame timing. */
static bool
wait_for_next_frame(void)
{
	struct timeval tv_end;
	uint32_t lap, wait, span;

	span = FRAME_MILLI;

	/* Do event processing and sleep until the time of next frame start. */
	do {
		/* Process events if exist. */
		while (XEventsQueued(display, QueuedAfterFlush) > 0)
			if (!next_event())
				return false;

		/* Get a lap time. */
		gettimeofday(&tv_end, NULL);
		lap = (uint32_t)((tv_end.tv_sec - tv_start.tv_sec) * 1000 +
				 (tv_end.tv_usec - tv_start.tv_usec) / 1000);

		/* Break if the time has come. */
		if (lap > span) {
			tv_start = tv_end;
			break;
		}

		/* Calc a sleep time. */
		wait = (span - lap > SLEEP_MILLI) ? SLEEP_MILLI : span - lap;

		/* Do sleep. */
		usleep(wait * 1000);

	} while(wait > 0);

	return true;
}

/* Process an event. */
static bool
next_event(void)
{
	XEvent event;

	XNextEvent(display, &event);
	switch (event.type) {
	case KeyPress:
		event_key_press(&event);
		break;
	case KeyRelease:
		event_key_release(&event);
		break;
	case ButtonPress:
		event_button_press(&event);
		break;
	case ButtonRelease:
		event_button_release(&event);
		break;
	case MotionNotify:
		event_motion_notify(&event);
		break;
	case MappingNotify:
		XRefreshKeyboardMapping(&event.xmapping);
		break;
	case ConfigureNotify:
		event_resize(&event);
		break;
	case ClientMessage:
		/* Close button was pressed. */
		if ((Atom)event.xclient.data.l[0] == delete_message)
			return false;
		break;
	}
	return true;
}

/* Process a KeyPress event. */
static void
event_key_press(
	XEvent *event)
{
	int key;
	KeySym keysym;

	keysym = XLookupKeysym(&event->xkey, 0);
	if ((event->xkey.state & Mod1Mask) && (keysym == XK_Return)) {
		if (!is_full_screen)
			hal_enter_full_screen_mode();
		else
			hal_leave_full_screen_mode();
		return;
	}

	/* Get a key code. */
	key = get_key_code(event);
	if (key == -1)
		return;

	/* Call an event handler. */
	hal_callback.on_key_press(key);
}

/* Process a KeyRelease event. */
static void
event_key_release(
	XEvent *event)
{
	int key;

	/* Ignore auto repeat events. */
	if (XEventsQueued(display, QueuedAfterReading) > 0) {
		XEvent next;
		XPeekEvent(display, &next);
		if (next.type == KeyPress &&
		    next.xkey.keycode == event->xkey.keycode &&
		    next.xkey.time == event->xkey.time) {
			XNextEvent(display, &next);
			return;
		}
	}

	/* Get a key code. */
	key = get_key_code(event);
	if (key == -1)
		return;

	/* Call an event handler. */
	hal_callback.on_key_release(key);
}

/* Convert 'KeySym' to 'enum key_code'. */
static int
get_key_code(
	XEvent *event)
{
	char text[255];
	KeySym keysym;

	/* Get a KeySym. */
	XLookupString(&event->xkey, text, sizeof(text), &keysym, 0);

	/* Convert to key_code. */
	switch (keysym) {
	case XK_Escape:
		return HAL_KEY_ESCAPE;
	case XK_Return:
	case XK_KP_Enter:
		return HAL_KEY_RETURN;
	case XK_space:
		return HAL_KEY_SPACE;
	case XK_Tab:
		return HAL_KEY_TAB;
	case XK_BackSpace:
		return HAL_KEY_BACKSPACE;
	case XK_Delete:
		return HAL_KEY_DELETE;
	case XK_Home:
		return HAL_KEY_HOME;
	case XK_End:
		return HAL_KEY_END;
	case XK_Page_Up:
		return HAL_KEY_PAGEUP;
	case XK_Page_Down:
		return HAL_KEY_PAGEDOWN;
	case XK_Shift_L:
	case XK_Shift_R:
		return HAL_KEY_SHIFT;
	case XK_Control_L:
	case XK_Control_R:
		return HAL_KEY_CONTROL;
	case XK_Alt_L:
	case XK_Alt_R:
		return HAL_KEY_ALT;
	case XK_Down:
		return HAL_KEY_DOWN;
	case XK_Up:
		return HAL_KEY_UP;
	case XK_Left:
		return HAL_KEY_LEFT;
	case XK_Right:
		return HAL_KEY_RIGHT;
	case XK_A:
		return HAL_KEY_A;
	case XK_B:
		return HAL_KEY_B;
	case XK_C:
		return HAL_KEY_C;
	case XK_D:
		return HAL_KEY_D;
	case XK_E:
		return HAL_KEY_E;
	case XK_F:
		return HAL_KEY_F;
	case XK_G:
		return HAL_KEY_G;
	case XK_H:
		return HAL_KEY_H;
	case XK_I:
		return HAL_KEY_I;
	case XK_J:
		return HAL_KEY_J;
	case XK_K:
		return HAL_KEY_K;
	case XK_L:
		return HAL_KEY_L;
	case XK_M:
		return HAL_KEY_M;
	case XK_N:
		return HAL_KEY_N;
	case XK_O:
		return HAL_KEY_O;
	case XK_P:
		return HAL_KEY_P;
	case XK_Q:
		return HAL_KEY_Q;
	case XK_R:
		return HAL_KEY_R;
	case XK_S:
		return HAL_KEY_S;
	case XK_T:
		return HAL_KEY_T;
	case XK_U:
		return HAL_KEY_U;
	case XK_V:
		return HAL_KEY_V;
	case XK_W:
		return HAL_KEY_W;
	case XK_X:
		return HAL_KEY_X;
	case XK_Y:
		return HAL_KEY_Y;
	case XK_Z:
		return HAL_KEY_Z;
	case XK_1:
		return HAL_KEY_1;
	case XK_2:
		return HAL_KEY_2;
	case XK_3:
		return HAL_KEY_3;
	case XK_4:
		return HAL_KEY_4;
	case XK_5:
		return HAL_KEY_5;
	case XK_6:
		return HAL_KEY_6;
	case XK_7:
		return HAL_KEY_7;
	case XK_8:
		return HAL_KEY_8;
	case XK_9:
		return HAL_KEY_9;
	case XK_0:
		return HAL_KEY_0;
	case XK_F1:
		return HAL_KEY_F1;
	case XK_F2:
		return HAL_KEY_F2;
	case XK_F3:
		return HAL_KEY_F3;
	case XK_F4:
		return HAL_KEY_F4;
	case XK_F5:
		return HAL_KEY_F5;
	case XK_F6:
		return HAL_KEY_F6;
	case XK_F7:
		return HAL_KEY_F7;
	case XK_F8:
		return HAL_KEY_F8;
	case XK_F9:
		return HAL_KEY_F9;
	case XK_F10:
		return HAL_KEY_F10;
	case XK_F11:
		return HAL_KEY_F11;
	case XK_F12:
		return HAL_KEY_F12;
	default:
		break;
	}
	return -1;
}

/* Process a ButtonPress event. */
static void
event_button_press(
	XEvent *event)
{
	/* See the button type and dispatch. */
	switch (event->xbutton.button) {
	case Button1:
		hal_callback.on_mouse_press(
			HAL_MOUSE_LEFT,
			(int)((float)(event->xbutton.x - mouse_ofs_x) * mouse_scale),
			(int)((float)(event->xbutton.y - mouse_ofs_y) * mouse_scale));
		break;
	case Button3:
		hal_callback.on_mouse_press(
			HAL_MOUSE_RIGHT,
			(int)((float)(event->xbutton.x - mouse_ofs_x) * mouse_scale),
			(int)((float)(event->xbutton.y - mouse_ofs_y) * mouse_scale));
		break;
	case Button4:
		hal_callback.on_key_press(HAL_KEY_UP);
		hal_callback.on_key_release(HAL_KEY_UP);
		break;
	case Button5:
		hal_callback.on_key_press(HAL_KEY_DOWN);
		hal_callback.on_key_release(HAL_KEY_DOWN);
		break;
	default:
		break;
	}
}

/* Process a ButtonPress event. */
static void
event_button_release(
	XEvent *event)
{
	/* See the button type and dispatch. */
	switch (event->xbutton.button) {
	case Button1:
		hal_callback.on_mouse_release(
			HAL_MOUSE_LEFT,
			(int)((float)(event->xbutton.x - mouse_ofs_x) * mouse_scale),
			(int)((float)(event->xbutton.y - mouse_ofs_y) * mouse_scale));
		break;
	case Button3:
		hal_callback.on_mouse_release(
			HAL_MOUSE_RIGHT,
			(int)((float)(event->xbutton.x - mouse_ofs_x) * mouse_scale),
			(int)((float)(event->xbutton.y - mouse_ofs_y) * mouse_scale));
		break;
	}
}

/* Process a MotionNotify event. */
static void event_motion_notify(XEvent *event)
{
	/* Call an event handler. */
	hal_callback.on_mouse_move(
		(int)((float)(event->xbutton.x - mouse_ofs_x) * mouse_scale),
		(int)((float)(event->xbutton.y - mouse_ofs_y) * mouse_scale));
}

/* Process a ConfigureNotify event. */
static void
event_resize(
	XEvent *event)
{
	update_viewport_size(event->xconfigure.width, event->xconfigure.height);
}

/*
 * Sets the window size.
 */
void update_viewport_size(
	int width,
	int height)
{
	float aspect, use_width, use_height;
	int orig_x, orig_y;
	int viewport_width, viewport_height;

	/* Calc the aspect ratio of the game. */
	aspect = (float)screen_height / (float)screen_width;

	/* Calc the height. (temporarily with "width-first") */
	use_width = (float)width;
	use_height = use_width * aspect;
	mouse_scale = (float)screen_width / (float)width;

	/* If height is not enough, calc the width. (with "height-first") */
	if(use_height > (float)height) {
		use_height = (float)height;
		use_width = (float)use_height / aspect;
		mouse_scale = (float)screen_height / (float)height;
	}

	/* Calc the viewport origin. */
	orig_x = (int)((((float)width - use_width) / 2.0f) + 0.5);
	orig_y = (int)((((float)height - use_height) / 2.0f) + 0.5);
	mouse_ofs_x = orig_x;
	mouse_ofs_y = orig_y;

	/* Calc the viewport size. */
	viewport_width = (int)use_width;
	viewport_height = (int)use_height;

	/* Update the screen offset and scale for drawing subsystem. */
	opengl_set_screen(orig_x, orig_y, viewport_width, viewport_height);
}

/*
 * HAL
 */

#if defined(HAL_USE_X11_ONLY)
#define EXPORT_IF_X11_ONLY			HAL_DLL
#define hal_log_info_x11			hal_log_info
#define hal_log_warn_x11			hal_log_warn
#define hal_log_error_x11			hal_log_error
#define hal_log_out_of_memory_x11		hal_log_out_of_memory
#define open_log_file_x11			open_log_file
#define make_save_directory_x11		make_save_directory
#define make_real_path_x11			make_real_path
#define hal_reset_lap_timer_x11			hal_reset_lap_timer
#define hal_get_lap_timer_millisec_x11		hal_get_lap_timer_millisec
#define hal_notify_image_update_x11		hal_notify_image_update
#define hal_notify_image_free_x11		hal_notify_image_free
#define hal_render_image_normal_x11		hal_render_image_normal
#define hal_render_image_add_x11		hal_render_image_add
#define hal_render_image_sub_x11		hal_render_image_sub
#define hal_render_image_dim_x11		hal_render_image_dim
#define hal_render_image_rule_x11		hal_render_image_rule
#define hal_render_image_melt_x11		hal_render_image_melt
#define hal_render_image_cross_x11		hal_render_image_cross
#define hal_render_image_3d_normal_x11		hal_render_image_3d_normal
#define hal_render_image_3d_add_x11		hal_render_image_3d_add
#define hal_render_image_3d_sub_x11		hal_render_image_3d_sub
#define hal_render_image_3d_dim_x11		hal_render_image_3d_dim
#define hal_render_image_3d_cross_x11		hal_render_image_3d_cross
#define hal_play_video_x11			hal_play_video
#define hal_stop_video_x11			hal_stop_video
#define hal_is_video_playing_x11		hal_is_video_playing
#define hal_is_full_screen_supported_x11	hal_is_full_screen_supported
#define hal_is_full_screen_mode_x11		hal_is_full_screen_mode
#define hal_enter_full_screen_mode_x11		hal_enter_full_screen_mode
#define hal_leave_full_screen_mode_x11		hal_leave_full_screen_mode
#define hal_get_system_language_x11		hal_get_system_language
#define hal_set_continuous_swipe_enabled_x11	hal_set_continuous_swipe_enabled
#else
#define EXPORT_IF_X11_ONLY
#endif

/*
 * Put an INFO log.
 */
EXPORT_IF_X11_ONLY
bool
hal_log_info_x11(
	const char *s,
	...)
{
	char buf[LOG_BUF_SIZE];
	va_list ap;

	va_start(ap, s);
	vsnprintf(buf, sizeof(buf), s, ap);
	va_end(ap);

	printf("%s\n", buf);

	return true;
}

/*
 * Put a WARN log.
 */
EXPORT_IF_X11_ONLY
bool
hal_log_warn_x11(
	const char *s,
	...)
{
	char buf[LOG_BUF_SIZE];
	va_list ap;

	va_start(ap, s);
	vsnprintf(buf, sizeof(buf), s, ap);
	va_end(ap);

	printf("%s\n", buf);

	open_log_file();
	if (log_fp != NULL) {
		fprintf(log_fp, "%s\n", buf);
		fflush(log_fp);
		if (ferror(log_fp))
			return false;
	}

	return true;
}

/*
 * Put an ERROR log.
 */
EXPORT_IF_X11_ONLY
bool
hal_log_error_x11(
	const char *s,
	...)
{
	char buf[LOG_BUF_SIZE];
	va_list ap;

	va_start(ap, s);
	vsnprintf(buf, sizeof(buf), s, ap);
	va_end(ap);

	printf("%s\n", buf);

	open_log_file();
	if (log_fp != NULL) {
		fprintf(log_fp, "%s\n", buf);
		fflush(log_fp);
		if (ferror(log_fp))
			return false;
	}
	
	return true;
}

/*
 * Put an out-of-memory error.
 */
bool
hal_log_out_of_memory_x11(void)
{
	hal_log_error(HAL_TR("Out of memory."));
	return true;
}

/* Open the log file. */
static bool
open_log_file(void)
{
	if (log_fp == NULL) {
		log_fp = fopen(LOG_FILE, "w");
		if (log_fp == NULL) {
			printf("Can't open log file.\n");
			return false;
		}
	}
	return true;
}

/*
 * Reset a timer.
 */
EXPORT_IF_X11_ONLY
void
hal_reset_lap_timer_x11(
	uint64_t *t)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	*t = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;

#if 0
	/* Use this for older systems. */
	struct timeval tv;
	gettimeofday(&tv, NULL);
	*t = (uint64_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
#endif
}

/*
 * Get a timer lap.
 */
EXPORT_IF_X11_ONLY
uint64_t
hal_get_lap_timer_millisec_x11(
	uint64_t *t)
{
	struct timespec ts;
	uint64_t end;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	end = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
	return (end - *t);

#if 0
	/* Use this for older systems. */
	struct timeval tv;
	uint64_t end;
	gettimeofday(&tv, NULL);
	end = (uint64_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
	return (uint64_t)(end - *t);
#endif
}

/*
 * Notify an image update.
 */
EXPORT_IF_X11_ONLY
void
hal_notify_image_update_x11(
	struct hal_image *img)
{
	opengl_notify_image_update(img);
}

/*
 * Notify an image free.
 */
EXPORT_IF_X11_ONLY
void
hal_notify_image_free_x11(
	struct hal_image *img)
{
	opengl_notify_image_free(img);
}

/*
 * Render an image. (alpha blend)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_normal_x11(
	int dst_left,
	int dst_top,
	int dst_width,
	int dst_height,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_normal(
		dst_left,
		dst_top,
		dst_width,
		dst_height,
		src_image,
		src_left,
		src_top,
		src_width,
		src_height,
		alpha
	);
}

/*
 * Render an image. (add blend)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_add_x11(
	int dst_left,
	int dst_top,
	int dst_width,
	int dst_height,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_add(
		dst_left,
		dst_top,
		dst_width,
		dst_height,
		src_image,
		src_left,
		src_top,
		src_width,
		src_height,
		alpha
	);
}

/*
 * Render an image. (sub blend)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_sub_x11(
	int dst_left,
	int dst_top,
	int dst_width,
	int dst_height,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_add(
		dst_left,
		dst_top,
		dst_width,
		dst_height,
		src_image,
		src_left,
		src_top,
		src_width,
		src_height,
		alpha
	);
}

/*
 * Render an image. (dim blend)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_dim_x11(
	int dst_left,
	int dst_top,
	int dst_width,
	int dst_height,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_dim(
		dst_left,
		dst_top,
		dst_width,
		dst_height,
		src_image,
		src_left,
		src_top,
		src_width,
		src_height,
		alpha
	);
}

/*
 * Render an image. (rule universal transition)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_rule_x11(
	struct hal_image *src_img,
	struct hal_image *rule_img,
	int threshold)
{
	opengl_render_image_rule(src_img, rule_img, threshold);
}

/*
 * Render an image. (melt universal transition)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_melt_x11(
	struct hal_image *src_img,
	struct hal_image *rule_img,
	int progress)
{
	opengl_render_image_melt(src_img, rule_img, progress);
}

/*
 * Render two images for a cross fading.
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_cross_x11(
	struct hal_image *src1_img,
	struct hal_image *src2_img,
	float src1_left,
	float src1_top,
	float src2_left,
	float src2_top,
	int alpha)
{
	opengl_render_image_cross(src1_img,
				  src2_img,
				  (int)src1_left,
				  (int)src1_top,
				  (int)src2_left,
				  (int)src2_top,
				  alpha);
}

/*
 * Render an image. (3d transform, alpha blending)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_3d_normal_x11(
	float x1,
	float y1,
	float x2,
	float y2,
	float x3,
	float y3,
	float x4,
	float y4,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_3d_normal(
		x1, y1, x2, y2, x3, y3, x4, y4,
		src_image, src_left, src_top, src_width, src_height,
		alpha);
}

/*
 * Render an image. (3d transform, add blending)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_3d_add_x11(
	float x1,
	float y1,
	float x2,
	float y2,
	float x3,
	float y3,
	float x4,
	float y4,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_3d_add(
		x1, y1, x2, y2, x3, y3, x4, y4,
		src_image, src_left, src_top, src_width, src_height,
		alpha);
}

/*
 * Render an image. (3d transform, sub blending)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_3d_sub_x11(
	float x1,
	float y1,
	float x2,
	float y2,
	float x3,
	float y3,
	float x4,
	float y4,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_3d_sub(
		x1, y1, x2, y2, x3, y3, x4, y4,
		src_image, src_left, src_top, src_width, src_height,
		alpha);
}

/*
 * Render an image. (3d transform, dim blending)
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_3d_dim_x11(
	float x1,
	float y1,
	float x2,
	float y2,
	float x3,
	float y3,
	float x4,
	float y4,
	struct hal_image *src_image,
	int src_left,
	int src_top,
	int src_width,
	int src_height,
	int alpha)
{
	opengl_render_image_3d_dim(
		x1, y1, x2, y2, x3, y3, x4, y4,
		src_image, src_left, src_top, src_width, src_height,
		alpha);
}

/*
 * Render two images for a cross fading.
 */
EXPORT_IF_X11_ONLY
void
hal_render_image_3d_cross_x11(
	struct hal_image *src1_img,
	struct hal_image *src2_img,
	float src1_x1,
	float src1_y1,
	float src1_x2,
	float src1_y2,
	float src1_x3,
	float src1_y3,
	float src1_x4,
	float src1_y4,
	float src2_x1,
	float src2_y1,
	float src2_x2,
	float src2_y2,
	float src2_x3,
	float src2_y3,
	float src2_x4,
	float src2_y4,
	int alpha)
{
	opengl_render_image_cross_3d(src1_img,
				     src2_img,
				     src1_x1,
				     src1_y1,
				     src1_x2,
				     src1_y2,
				     src1_x3,
				     src1_y3,
				     src1_x4,
				     src1_y4,
				     src2_x1,
				     src2_y1,
				     src2_x2,
				     src2_y2,
				     src2_x3,
				     src2_y3,
				     src2_x4,
				     src2_y4,
				     alpha);
}

/*
 * Play a video.
 */
EXPORT_IF_X11_ONLY
bool
hal_play_video_x11(
	const char *fname,
	bool is_skippable)
{
	char *path;

	path = make_real_path(fname);

	is_gst_playing = true;
	is_gst_skippable = is_skippable;

	gstplay_play(path);

	free(path);

	return true;
}

/*
 * Stop the video.
 */
EXPORT_IF_X11_ONLY
void
hal_stop_video_x11(void)
{
	gstplay_stop();

	is_gst_playing = false;
}

/*
 * Check whether a video is playing.
 */
EXPORT_IF_X11_ONLY
bool
hal_is_video_playing_x11(void)
{
	return is_gst_playing;
}

/*
 * Check whether full screen mode is supported.
 */
EXPORT_IF_X11_ONLY
bool
hal_is_full_screen_supported_x11(void)
{
	return false;
}

/*
 * Check whether we are in full screen mode.
 */
EXPORT_IF_X11_ONLY
bool
hal_is_full_screen_mode_x11(void)
{
	return false;
}

/*
 * Enter full screen mode.
 */
EXPORT_IF_X11_ONLY
void
hal_enter_full_screen_mode_x11(void)
{
	Atom wm_state;
	Atom fs_atom;
	XEvent xev = {0};

	if (is_full_screen)
		return;

	wm_state = XInternAtom(display, "_NET_WM_STATE", False);
	fs_atom = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);

	xev.type = ClientMessage;
	xev.xclient.window = window;
	xev.xclient.message_type = wm_state;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = 1;
	xev.xclient.data.l[1] = (long)fs_atom;
	xev.xclient.data.l[2] = 0; // no second property
	xev.xclient.data.l[3] = 1;

	XSendEvent(display,
		   DefaultRootWindow(display),
		   False,
		   SubstructureRedirectMask | SubstructureNotifyMask,
		   &xev);

	is_full_screen = true;
}

/*
 * Leave full screen mode.
 */
EXPORT_IF_X11_ONLY
void
hal_leave_full_screen_mode_x11(void)
{
	Atom wm_state;
	Atom fs_atom;
	XEvent xev = {0};

	if (!is_full_screen)
		return;

	wm_state = XInternAtom(display, "_NET_WM_STATE", False);
	fs_atom = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);

	xev.type = ClientMessage;
	xev.xclient.window = window;
	xev.xclient.message_type = wm_state;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = 0;
	xev.xclient.data.l[1] = (long)fs_atom;
	xev.xclient.data.l[2] = 0;
	xev.xclient.data.l[3] = 1;

	XSendEvent(display,
		   DefaultRootWindow(display),
		   False,
		   SubstructureRedirectMask | SubstructureNotifyMask,
		   &xev);

	is_full_screen = false;
}

/*
 * Get the system locale.
 */
EXPORT_IF_X11_ONLY
const char *
hal_get_system_language_x11(void)
{
	const char *locale = setlocale(LC_MESSAGES, "");
        if (locale == NULL || locale[0] == '\0') {
		locale = getenv("LC_ALL");
		if (locale == NULL || locale[0] == '\0') {
			locale = getenv("LC_MESSAGES");
			if (locale == NULL || locale[0] == '\0')
				locale = getenv("LANG");
		}
	}
	if (locale == NULL || locale[0] == '\0')
		return "en";

	/* English */
	if (strncmp(locale, "en_AU", 5) == 0)
		return "en-au";
	if (strncmp(locale, "en_GB", 5) == 0)
		return "en-gb";
	if (strncmp(locale, "en_NZ", 5) == 0)
		return "en-nz";
	if (strncmp(locale, "en_US", 5) == 0)
		return "en-us";
	if (strncmp(locale, "en", 2) == 0)
		return "en";

	/* French */
	if (strncmp(locale, "fr_CA", 5) == 0)
		return "fr-ca";
	if (strncmp(locale, "fr", 2) == 0)
		return "fr-fr";

	/* Spanish */
	if (strncmp(locale, "es_ES", 5) == 0)
		return "es-es";
	if (strncmp(locale, "es", 2) == 0)
		return "es-la";

	/* Chinese */
	if (strncmp(locale, "zh_TW", 5) == 0 ||
	    strncmp(locale, "zh_HK", 5) == 0)
		return "zh-tw";
	if (strncmp(locale, "zh", 2) == 0)
		return "zh-cn";

	/* Others */
	if (strncmp(locale, "ja", 2) == 0)
		return "ja";
	if (strncmp(locale, "de", 2) == 0)
		return "de";
	if (strncmp(locale, "it", 2) == 0)
		return "it";
	if (strncmp(locale, "el", 2) == 0)
		return "el";
	if (strncmp(locale, "ru", 2) == 0)
		return "ru";
	if (strncmp(locale, "ko", 2) == 0)
		return "ko";

	/* Fallback */
	return "en";
}

/*
 * Enable/disable message skip by touch move.
 */
EXPORT_IF_X11_ONLY
void
hal_set_continuous_swipe_enabled_x11(
	bool is_enabled)
{
	UNUSED_PARAMETER(is_enabled);
}

/*
 * Make a save directory.
 */
bool
make_save_directory_x11(void)
{
	struct stat st = {0};

	if (stat(SAVE_DIR, &st) == -1)
		mkdir(SAVE_DIR, 0700);

	return true;
}

/*
 * Make an effective path from a directory name and a file name.
 */
char *
make_real_path_x11(
	const char *fname)
{
	char *buf;
	size_t len;

	/* Allocate a path buffer. */
	len = strlen(fname) + 1;
	buf = malloc(len);
	if (buf == NULL) {
		hal_log_out_of_memory();
		return NULL;
	}

	/* Copy as is. */
	snprintf(buf, len, "%s", fname);

	return buf;
}
