/* $Id$ */
/* Copyright (c) 2010-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Graphics GServer */
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */



#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <GL/gl.h>
#include <System.h>
#include <System/Marshall.h>
#include "GServer/video.h"
#include "../data/GServer.h"
#include "platform.h"
#include "gserver.h"
#include "../config.h"

#ifndef PREFIX
# define PREFIX		"/usr/local"
#endif
#ifndef LIBDIR
# define LIBDIR		PREFIX "/lib"
#endif

#ifdef DEBUG
# define DEBUG_INTERFACE() fprintf(stderr, "DEBUG: %s()\n", __func__)
# define DEBUG_INTERFACE1i(x) fprintf(stderr, "DEBUG: %s(0x%x)\n", __func__, x)
# define DEBUG_INTERFACE2d(x, y) \
	fprintf(stderr, "DEBUG: %s(%.1f, %.1f)\n", __func__, x, y)
# define DEBUG_INTERFACE2f(x, y) DEBUG_INTERFACE2d(x, y)
# define DEBUG_INTERFACE2i(x, y) \
	fprintf(stderr, "DEBUG: %s(0x%x, 0x%x)\n", __func__, x, y)
# define DEBUG_INTERFACE3b(x, y, z) DEBUG_INTERFACE3i(x, y, z)
# define DEBUG_INTERFACE3d(x, y, z) \
	fprintf(stderr, "DEBUG: %s(%.1f, %.1f, %.1f)\n", __func__, x, y, z)
# define DEBUG_INTERFACE3f(x, y, z) DEBUG_INTERFACE3d(x, y, z)
# define DEBUG_INTERFACE3i(x, y, z) \
	fprintf(stderr, "DEBUG: %s(0x%x, 0x%x, 0x%x)\n", __func__, x, y, z)
# define DEBUG_INTERFACE3s(x, y, z) DEBUG_INTERFACE3i(x, y, z)
# define DEBUG_INTERFACE4b(x, y, z, t) DEBUG_INTERFACE4i(x, y, z, t)
# define DEBUG_INTERFACE4d(x, y, z, t) \
	fprintf(stderr, "DEBUG: %s(%.1f, %.1f, %.1f, %.1f)\n", \
			__func__, x, y, z, t)
# define DEBUG_INTERFACE4f(x, y, z, t) DEBUG_INTERFACE4d(x, y, z, t)
# define DEBUG_INTERFACE4i(x, y, z, t) \
	fprintf(stderr, "DEBUG: %s(0x%x, 0x%x, 0x%x, 0x%x)\n", \
			__func__, x, y, z, t)
# define DEBUG_INTERFACE4s(x, y, z, t) DEBUG_INTERFACE4i(x, y, z, t)
#else
# define DEBUG_INTERFACE()
# define DEBUG_INTERFACE1i(x)
# define DEBUG_INTERFACE2d(x, y)
# define DEBUG_INTERFACE2f(x, y)
# define DEBUG_INTERFACE2i(x, y)
# define DEBUG_INTERFACE3b(x, y, z)
# define DEBUG_INTERFACE3d(x, y, z)
# define DEBUG_INTERFACE3f(x, y, z)
# define DEBUG_INTERFACE3i(x, y, z)
# define DEBUG_INTERFACE3s(x, y, z)
# define DEBUG_INTERFACE4b(x, y, z, t)
# define DEBUG_INTERFACE4d(x, y, z, t)
# define DEBUG_INTERFACE4f(x, y, z, t)
# define DEBUG_INTERFACE4i(x, y, z, t)
# define DEBUG_INTERFACE4s(x, y, z, t)
#endif


/* GServer */
/* private */
/* types */
typedef struct _GServerCall GServerCall;
typedef struct _GServerClient GServerClient;

struct _App
{
	GServerPlatform * platform;

	Event * event;
	int event_own;
	AppServer * appserver;
	int loop;
	MarshallCall * calls;

	/* plugins */
	/* video */
	GServerVideoPluginHelper video_helper;
	void * video_handle;
	GServerVideoPlugin * video_plugin;

	/* clients */
	GServerClient * clients;
	size_t clients_cnt;
};

struct _GServerCall
{
	GServerVideoCall type;
	MarshallCall call;
	Variable ** args;
	size_t args_cnt;
};

typedef struct _GServerOpenGLCall
{
	String const * name;
	GServerVideoCall type;
} GServerOpenGLCall;

struct _GServerClient
{
	AppServerClient * asc;
	GServerCall * calls;
	size_t calls_cnt;
};


/* constants */
const GServerOpenGLCall _gserver_calls[] =
{
	/* GSERVER_VIDEO_CALL0 */
	{ "glEnd",		GSERVER_VIDEO_CALL_0	},
	{ "glEndList",		GSERVER_VIDEO_CALL_0	},
	{ "glFlush",		GSERVER_VIDEO_CALL_0	},
	{ "glLoadIdentity",	GSERVER_VIDEO_CALL_0	},
	{ "SwapBuffers",	GSERVER_VIDEO_CALL_0	},
	/* GSERVER_VIDEO_CALL1d */
	{ "glClearDepth",	GSERVER_VIDEO_CALL_1d	},
	/* GSERVER_VIDEO_CALL1f */
	{ "glClearIndex",	GSERVER_VIDEO_CALL_1f	},
	{ "glPointSize",	GSERVER_VIDEO_CALL_1f	},
	/* GSERVER_VIDEO_CALL1i */
	{ "glActiveTexture",	GSERVER_VIDEO_CALL_1i	},
	{ "glArrayElement",	GSERVER_VIDEO_CALL_1i	},
	{ "glBegin",		GSERVER_VIDEO_CALL_1i	},
	{ "glBlendEquation",	GSERVER_VIDEO_CALL_1i	},
	{ "glCallList",		GSERVER_VIDEO_CALL_1i	},
	{ "glClear",		GSERVER_VIDEO_CALL_1i	},
	{ "glClearStencil",	GSERVER_VIDEO_CALL_1i	},
	{ "glClientActiveTexture",GSERVER_VIDEO_CALL_1i	},
#if 0
	{ "glCompileShader",	GSERVER_VIDEO_CALL_1i	},
#endif
	{ "glDepthFunc",	GSERVER_VIDEO_CALL_1i	},
	{ "glDisable",		GSERVER_VIDEO_CALL_1i	},
	{ "glDisableClientState",GSERVER_VIDEO_CALL_1i	},
	{ "glEnable",		GSERVER_VIDEO_CALL_1i	},
	{ "glEnableClientState",GSERVER_VIDEO_CALL_1i	},
	{ "glIsEnabled",	GSERVER_VIDEO_CALL_1i	},
	{ "glShadeModel",	GSERVER_VIDEO_CALL_1i	},
	/* GSERVER_VIDEO_CALL2f */
	{ "glPolygonOffset",	GSERVER_VIDEO_CALL_2f	},
	{ "glTexCoord2f",	GSERVER_VIDEO_CALL_2f	},
	/* GSERVER_VIDEO_CALL2i */
	{ "glAttachShader",	GSERVER_VIDEO_CALL_2i	},
	{ "glBeginQuery",	GSERVER_VIDEO_CALL_2i	},
	{ "glBindBuffer",	GSERVER_VIDEO_CALL_2i	},
	{ "glBindTexture",	GSERVER_VIDEO_CALL_2i	},
	{ "glBlendEquationSeparate",GSERVER_VIDEO_CALL_2i	},
#if 0
	{ "glBindTexture",	GSERVER_VIDEO_CALL_2i	},
#endif
	{ "glBlendFunc",	GSERVER_VIDEO_CALL_2i	},
	{ "glColorMaterial",	GSERVER_VIDEO_CALL_2i	},
	{ "glFogi",		GSERVER_VIDEO_CALL_2i	},
	{ "glHint",		GSERVER_VIDEO_CALL_2i	},
	{ "glLightModeli",	GSERVER_VIDEO_CALL_2i	},
	{ "glVertex2i",		GSERVER_VIDEO_CALL_2i	},
	/* GSERVER_VIDEO_CALL_3b */
	{ "glColor3b",		GSERVER_VIDEO_CALL_3b	},
	{ "glColor3ub",		GSERVER_VIDEO_CALL_3b	},
	/* GSERVER_VIDEO_CALL_3d */
	{ "glColor3d",		GSERVER_VIDEO_CALL_3d	},
	{ "glTranslate3d",	GSERVER_VIDEO_CALL_3d	},
	/* GSERVER_VIDEO_CALL_3f */
	{ "glColor3f",		GSERVER_VIDEO_CALL_3f	},
	{ "glLightf",		GSERVER_VIDEO_CALL_3f	},
	{ "glNormal3f",		GSERVER_VIDEO_CALL_3f	},
	{ "glTranslate3f",	GSERVER_VIDEO_CALL_3f	},
	{ "glVertex3f",		GSERVER_VIDEO_CALL_3f	},
	/* GSERVER_VIDEO_CALL_3i */
	{ "glColor3i",		GSERVER_VIDEO_CALL_3i	},
	{ "glColor3ui",		GSERVER_VIDEO_CALL_3i	},
	{ "glDrawArrays",	GSERVER_VIDEO_CALL_3i	},
	{ "glLighti",		GSERVER_VIDEO_CALL_3i	},
	{ "glTexGeni",		GSERVER_VIDEO_CALL_3i	},
	{ "glTexParameteri",	GSERVER_VIDEO_CALL_3i	},
	{ "glVertex3i",		GSERVER_VIDEO_CALL_3i	},
	/* GSERVER_VIDEO_CALL_3s */
	{ "glColor3s",		GSERVER_VIDEO_CALL_3s	},
	{ "glColor3us",		GSERVER_VIDEO_CALL_3s	},
	/* GSERVER_VIDEO_CALL_4b */
	{ "glColor4b",		GSERVER_VIDEO_CALL_4d	},
	{ "glColor4ub",		GSERVER_VIDEO_CALL_4d	},
	/* GSERVER_VIDEO_CALL_4d */
	{ "glColor4d",		GSERVER_VIDEO_CALL_4d	},
	/* GSERVER_VIDEO_CALL_4f */
	{ "glClearAccum",	GSERVER_VIDEO_CALL_4f	},
	{ "glClearColor",	GSERVER_VIDEO_CALL_4f	},
	{ "glColor4f",		GSERVER_VIDEO_CALL_4f	},
	{ "glRotatef",		GSERVER_VIDEO_CALL_4f	}
};


/* prototypes */
/* accessors */
static GServerClient * _gserver_get_client(GServer * gserver, void * id);
static MarshallCall _gserver_get_call(GServer * gserver, String const * name);

/* useful */
static int _gserver_call(GServer * gserver, Variable * ret,
		char const * name, size_t args_cnt, Variable ** args);
static void _gserver_client_calls(GServer * gserver, GServerClient * client);

/* queue */
static int _gserver_queue(GServer * gserver, AppServerClient * asc,
		GServerVideoCall type, char const * name, ...);


/* public */
/* functions */
/* gserver_new */
static int _new_init(AppServerOptions options, GServer * gserver,
		Event * event);
static int _new_init_opengl(GServer * gserver);
static int _new_init_video(GServer * gserver);

GServer * gserver_new(AppServerOptions options, Event * event)
{
	GServer * gserver;

	if((gserver = object_new(sizeof(*gserver))) == NULL)
		return NULL;
	if(_new_init(options, gserver, event) != 0)
	{
		object_delete(gserver);
		return NULL;
	}
	return gserver;
}

static int _new_init(AppServerOptions options, GServer * gserver, Event * event)
{
	if((gserver->platform = gserverplatform_new()) == NULL)
		return -1;
	gserver->video_handle = NULL;
	gserver->video_plugin = NULL;
	gserver->clients = NULL;
	gserver->clients_cnt = 0;
	if((gserver->event = event) != NULL)
		gserver->event_own = 0;
	else if((gserver->event = event_new()) == NULL)
	{
		gserverplatform_delete(gserver->platform);
		return -1;
	}
	else
		gserver->event_own = 1;
	gserver->calls = NULL;
	gserver->video_helper.gserver = gserver;
	gserver->video_helper.get_event = gserver_get_event;
	gserver->video_helper.get_platform = gserver_get_platform;
	gserver->video_helper.refresh = gserver_refresh;
	if((gserver->appserver = appserver_new_event(gserver, options,
					"GServer", NULL, gserver->event))
			!= NULL
			&& _new_init_video(gserver) == 0
			&& _new_init_opengl(gserver) == 0)
	{
		gserver->loop = 1;
		return 0;
	}
	if(gserver->calls != NULL)
		object_delete(gserver->calls);
	if(gserver->appserver != NULL)
		appserver_delete(gserver->appserver);
	if(gserver->event_own != 0)
		event_delete(gserver->event);
	gserverplatform_delete(gserver->platform);
	return -1;
}

static int _new_init_opengl(GServer * gserver)
{
	Plugin * plugin;
	const size_t cnt = sizeof(_gserver_calls) / sizeof(*_gserver_calls);
	size_t i;

	if((gserver->calls = object_new(cnt * sizeof(*gserver->calls))) == NULL)
		return -1;
	if((plugin = plugin_new_self()) == NULL)
		return -1;
	for(i = 0; i < cnt; i++)
		if((gserver->calls[i] = (MarshallCall)plugin_lookup(plugin,
						_gserver_calls[i].name))
				== NULL)
			break;
	plugin_delete(plugin);
	return (i == cnt) ? 0 : -1;
}

static int _new_init_video(GServer * gserver)
{
	String const subsystem[] = "video";
	GServerPlatform * platform = gserver->platform;

	if((gserver->video_handle = plugin_new(LIBDIR, PACKAGE, subsystem,
					gserverplatform_get_driver(platform,
						subsystem))) == NULL)
		return 1;
	if((gserver->video_plugin = plugin_lookup(gserver->video_handle,
					"video_plugin")) == NULL)
	{
		plugin_delete(gserver->video_handle);
		gserver->video_handle = NULL;
		return 1;
	}
	gserver->video_plugin->helper = &gserver->video_helper;
	gserver->video_plugin->init(gserver->video_plugin);
	return 0;
}


/* gserver_delete */
static void _delete_video(GServer * gserver);

void gserver_delete(GServer * gserver)
{
	_delete_video(gserver);
	if(gserver->calls != NULL)
		object_delete(gserver->calls);
	if(gserver->appserver != NULL)
		appserver_delete(gserver->appserver);
	if(gserver->event != NULL)
		event_delete(gserver->event);
	if(gserver->platform != NULL)
		gserverplatform_delete(gserver->platform);
	object_delete(gserver);
}

static void _delete_video(GServer * gserver)
{
	if(gserver->video_plugin != NULL)
		gserver->video_plugin->destroy(gserver->video_plugin);
	if(gserver->video_handle != NULL)
		plugin_delete(gserver->video_handle);
}


/* accessors */
/* gserver_get_event */
Event * gserver_get_event(GServer * gserver)
{
	return gserver->event;
}


/* gserver_get_platform */
GServerPlatform * gserver_get_platform(GServer * gserver)
{
	return gserver->platform;
}


/* useful */
/* gserver_loop */
int gserver_loop(GServer * gserver)
{
	int ret = 0;

	while(gserver->loop == 1)
		ret |= event_loop(gserver->event);
	return ret;
}


/* gserver_refresh */
void gserver_refresh(GServer * gserver)
{
	size_t i;
	uint32_t u = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
	Variable * v;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	v = variable_new(VT_UINT32, &u);
	_gserver_call(gserver, NULL, "glClear", 1, &v);
	_gserver_call(gserver, NULL, "glLoadIdentity", 0, NULL);
	for(i = 0; i < gserver->clients_cnt; i++)
		_gserver_client_calls(gserver, &gserver->clients[i]);
	_gserver_call(gserver, NULL, "SwapBuffers", 0, NULL);
}


/* interface */
#define GSERVER_PROTO0(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc) \
{ \
	DEBUG_INTERFACE(); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_0, \
			"" # func); \
}
#define GSERVER_PROTO1d(type, func) \
	type GServer_ ## func(GServer * gserver, AppServerClient * asc, \
			double x) \
{ \
	DEBUG_INTERFACE(); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_1d, \
			"" # func, x); \
}
#define GSERVER_PROTO1f(type, func) \
	type GServer_ ## func(GServer * gserver, AppServerClient * asc, \
			float x) \
{ \
	DEBUG_INTERFACE(); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_1f, \
			"" # func, x); \
}
#define GSERVER_PROTO1i(type, func, type1) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x) \
{ \
	DEBUG_INTERFACE1i(x); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_1i, \
			"" # func, x); \
}
#define GSERVER_PROTO2f(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			float x, float y) \
{ \
	DEBUG_INTERFACE2f(x, y); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_2f, \
			"" # func, x, y); \
}
#define GSERVER_PROTO2i(type, func, type1, type2) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y) \
{ \
	DEBUG_INTERFACE2i(x, y); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_2i, \
			"" # func, x, y); \
}
#define GSERVER_PROTO3b(type, func, type1, type2, type3) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z) \
{ \
	DEBUG_INTERFACE3b(x, y, z); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_3b, \
			"" # func, x, y, z); \
}
#define GSERVER_PROTO3d(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			double x, double y, double z) \
{ \
	DEBUG_INTERFACE3d(x, y, z); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_3d, \
			"" # func, x, y, z); \
}
#define GSERVER_PROTO3f(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			float x, float y, float z) \
{ \
	DEBUG_INTERFACE3f(x, y, z); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_3f, \
			"" # func, x, y, z); \
}
#define GSERVER_PROTO3i(type, func, type1, type2, type3) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z) \
{ \
	DEBUG_INTERFACE3i(x, y, z); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_3i, \
			"" # func, x, y, z); \
}
#define GSERVER_PROTO3s(type, func, type1, type2, type3) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z) \
{ \
	DEBUG_INTERFACE3s(x, y, z); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_3s, \
			"" # func, x, y, z); \
}
#define GSERVER_PROTO4b(type, func, type1, type2, type3, type4) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z, type4 t) \
{ \
	DEBUG_INTERFACE4b(x, y, z, t); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_4b, \
			"" # func, x, y, z, t); \
}
#define GSERVER_PROTO4d(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			double x, double y, double z, double t) \
{ \
	DEBUG_INTERFACE4d(x, y, z, t); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_4d, \
			"" # func, x, y, z, t); \
}
#define GSERVER_PROTO4f(type, func) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			float x, float y, float z, float t) \
{ \
	DEBUG_INTERFACE4f(x, y, z, t); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_4f, \
			"" # func, x, y, z, t); \
}
#define GSERVER_PROTO4i(type, func, type1, type2, type3, type4) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z, type4 t) \
{ \
	DEBUG_INTERFACE4i(x, y, z, t); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_4i, \
			"" # func, x, y, z, t); \
}
#define GSERVER_PROTO4s(type, func, type1, type2, type3, type4) \
	type GServer_ ## func (GServer * gserver, AppServerClient * asc, \
			type1 x, type2 y, type3 z, type4 t) \
{ \
	DEBUG_INTERFACE4s(x, y, z, t); \
	_gserver_queue(gserver, asc, GSERVER_VIDEO_CALL_4s, \
			"" # func, x, y, z, t); \
}

/* proto0 */
GSERVER_PROTO0(void, glEnd)
GSERVER_PROTO0(void, glEndList)
GSERVER_PROTO0(void, glFlush)
GSERVER_PROTO0(void, glLoadIdentity)
GSERVER_PROTO0(void, SwapBuffers)

/* proto1d */
GSERVER_PROTO1d(void, glClearDepth)

/* proto1f*/
GSERVER_PROTO1f(void, glClearIndex)
GSERVER_PROTO1f(void, glPointSize)

/* proto1i */
GSERVER_PROTO1i(void, glActiveTexture, uint32_t)
GSERVER_PROTO1i(void, glArrayElement, int32_t)
GSERVER_PROTO1i(void, glBegin, uint32_t)
GSERVER_PROTO1i(void, glBlendEquation, uint32_t)
GSERVER_PROTO1i(void, glCallList, uint32_t)
GSERVER_PROTO1i(void, glClear, uint32_t)
GSERVER_PROTO1i(void, glClearStencil, int32_t)
GSERVER_PROTO1i(void, glClientActiveTexture, uint32_t)
#if 0
GSERVER_PROTO1i(void, glCompileShader, uint32_t)
#endif
GSERVER_PROTO1i(void, glDepthFunc, uint32_t)
GSERVER_PROTO1i(void, glDisable, uint32_t)
GSERVER_PROTO1i(void, glDisableClientState, uint32_t)
GSERVER_PROTO1i(void, glEnable, uint32_t)
GSERVER_PROTO1i(void, glEnableClientState, uint32_t)
/* FIXME really returns bool */
GSERVER_PROTO1i(bool, glIsEnabled, uint32_t)
GSERVER_PROTO1i(void, glShadeModel, uint32_t)

/* proto2f */
GSERVER_PROTO2f(void, glPolygonOffset)
GSERVER_PROTO2f(void, glTexCoord2f)

/* proto2i */
#if 0
GSERVER_PROTO2i(void, glAttachShader, uint32_t, uint32_t)
GSERVER_PROTO2i(void, glBeginQuery, uint32_t, uint32_t)
GSERVER_PROTO2i(void, glBindBuffer, uint32_t, uint32_t)
#endif
GSERVER_PROTO2i(void, glBindTexture, uint32_t, uint32_t)
#if 0
GSERVER_PROTO2i(void, glBlendEquationSeparate, uint32_t, uint32_t)
#endif
GSERVER_PROTO2i(void, glBlendFunc, uint32_t, uint32_t)
GSERVER_PROTO2i(void, glColorMaterial, uint32_t, uint32_t)
GSERVER_PROTO2i(void, glFogi, uint32_t, int32_t)
GSERVER_PROTO2i(void, glHint, uint32_t, uint32_t)
GSERVER_PROTO2i(void, glLightModeli, uint32_t, int32_t)
GSERVER_PROTO2i(void, glVertex2i, int32_t, int32_t)

/* proto3b */
GSERVER_PROTO3b(void, glColor3b, int8_t, int8_t, int8_t)
GSERVER_PROTO3b(void, glColor3ub, uint8_t, uint8_t, uint8_t)

/* proto3d */
GSERVER_PROTO3d(void, glColor3d)
GSERVER_PROTO3d(void, glTranslated)

/* proto3f */
GSERVER_PROTO3f(void, glColor3f)
GSERVER_PROTO3f(void, glNormal3f)
GSERVER_PROTO3f(void, glTranslatef)
GSERVER_PROTO3f(void, glVertex3f)

/* proto3i */
GSERVER_PROTO3i(void, glColor3i, int32_t, int32_t, int32_t)
GSERVER_PROTO3i(void, glColor3ui, uint32_t, uint32_t, uint32_t)
GSERVER_PROTO3i(void, glDrawArrays, uint32_t, int32_t, int32_t)
GSERVER_PROTO3i(void, glLighti, uint32_t, uint32_t, int32_t)
GSERVER_PROTO3i(void, glTexGeni, uint32_t, uint32_t, int32_t)
GSERVER_PROTO3i(void, glTexParameteri, uint32_t, uint32_t, uint32_t)
GSERVER_PROTO3i(void, glVertex3i, int32_t, int32_t, int32_t)

/* proto3s */
GSERVER_PROTO3s(void, glColor3s, int16_t, int16_t, int16_t)
GSERVER_PROTO3s(void, glColor3us, uint16_t, uint16_t, uint16_t)

/* proto4b */
GSERVER_PROTO4b(void, glColor4b, int8_t, int8_t, int8_t, int8_t)
GSERVER_PROTO4b(void, glColor4ub, uint8_t, uint8_t, uint8_t, uint8_t)

/* proto4d */
GSERVER_PROTO4d(void, glColor4d)

/* proto4f */
GSERVER_PROTO4f(void, glClearAccum)
GSERVER_PROTO4f(void, glClearColor)
GSERVER_PROTO4f(void, glColor4f)
GSERVER_PROTO4f(void, glRotatef)

/* proto4i */
#if 0
GSERVER_PROTO4i(void, glBlendFuncSeparate, uint32_t, uint32_t, uint32_t,
		uint32_t)
#endif
GSERVER_PROTO4i(void, glColor4i, int32_t, int32_t, int32_t, int32_t)
GSERVER_PROTO4i(void, glColor4ui, uint32_t, uint32_t, uint32_t, uint32_t)
GSERVER_PROTO4i(void, glColorMask, bool, bool, bool, bool)
GSERVER_PROTO4i(void, glScissor, int32_t, int32_t, int32_t, int32_t)
GSERVER_PROTO4i(void, glViewport, int32_t, int32_t, int32_t, int32_t)

/* proto4s */
GSERVER_PROTO4s(void, glColor4s, int16_t, int16_t, int16_t, int16_t)
GSERVER_PROTO4s(void, glColor4us, uint16_t, uint16_t, uint16_t, uint16_t)


/* private */
/* functions */
/* accessors */
static MarshallCall _gserver_get_call(GServer * gserver, String const * name)
{
	const size_t cnt = sizeof(_gserver_calls) / sizeof(*_gserver_calls);
	size_t i;

	for(i = 0; i < cnt; i++)
		if(strcmp(_gserver_calls[i].name, name) == 0)
			return gserver->calls[i];
	/* XXX report the error */
	return NULL;
}


/* gserver_get_client */
static GServerClient * _gserver_get_client(GServer * gserver,
		AppServerClient * asc)
{
	GServerClient * ret;
	size_t i;

	for(i = 0; i < gserver->clients_cnt; i++)
		if(gserver->clients[i].asc == asc)
			return &gserver->clients[i];
	if((ret = realloc(gserver->clients, sizeof(*ret) * (gserver->clients_cnt
						+ 1))) == NULL)
		return NULL;
	gserver->clients = ret;
	ret = &gserver->clients[gserver->clients_cnt++];
	ret->asc = asc;
	ret->calls = NULL;
	ret->calls_cnt = 0;
	return ret;
}


/* useful */
/* gserver_call */
static int _gserver_call(GServer * gserver, Variable * ret,
		char const * name, size_t args_cnt, Variable ** args)
{
	MarshallCall call;

	if((call = _gserver_get_call(gserver, name)) != NULL)
		return marshall_callp(ret, call, args_cnt, args);
	return -1;
}


/* gserver_client_calls */
static void _gserver_client_calls(GServer * gserver, GServerClient * client)
{
	size_t i;
	GServerCall * call;

	/* FIXME place the calls in context (windows...) */
	for(i = 0; i < client->calls_cnt; i++)
	{
		call = &client->calls[i];
		marshall_call(NULL, call->call, call->args_cnt, call->args);
	}
}


/* queue */
/* gserver_queue */
static int _queue_error(GServerCall * call);

static int _gserver_queue(GServer * gserver, AppServerClient * asc,
		GServerVideoCall type, char const * name, ...)
{
	va_list ap;
	GServerCall * call;
	GServerClient * gsc;
	size_t i = 0;
	Variable * v;
	uint8_t u8;
	double d;
	float f;
	uint16_t u16;
	uint32_t u32;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p, %u, %u)\n", __func__, (void *)gserver,
			type, name);
#endif
	if((gsc = _gserver_get_client(gserver, asc)) == NULL)
		return -1;
	if((call = realloc(gsc->calls, sizeof(*call) * (gsc->calls_cnt + 1)))
			== NULL)
		return -1;
	gsc->calls = call;
	call = &gsc->calls[gsc->calls_cnt];
	call->type = type;
	call->call = _gserver_get_call(gserver, name);
#if 0 /* XXX probably hurts performance without protecting anything */
	memset(&gsc->args, 0, sizeof(gsc->args));
#endif
	va_start(ap, name);
	switch(type)
	{
		case GSERVER_VIDEO_CALL_0:
			/* FIXME intercept SwapBuffers() and glClear() */
			call->args_cnt = 0;
			call->args = NULL;
			break;
		case GSERVER_VIDEO_CALL_1d:
			call->args_cnt = 1;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			break;
		case GSERVER_VIDEO_CALL_1f:
			call->args_cnt = 1;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_FLOAT, &f);
			break;
		case GSERVER_VIDEO_CALL_1i:
			call->args_cnt = 1;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			break;
		case GSERVER_VIDEO_CALL_2f:
			call->args_cnt = 2;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_FLOAT, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_FLOAT, &f);
			break;
		case GSERVER_VIDEO_CALL_2i:
			call->args_cnt = 2;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			break;
		case GSERVER_VIDEO_CALL_3b:
			call->args_cnt = 3;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			break;
		case GSERVER_VIDEO_CALL_3d:
			call->args_cnt = 3;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			break;
		case GSERVER_VIDEO_CALL_3f:
			call->args_cnt = 3;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			break;
		case GSERVER_VIDEO_CALL_3i:
			call->args_cnt = 3;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			break;
		case GSERVER_VIDEO_CALL_3s:
			call->args_cnt = 3;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			break;
		case GSERVER_VIDEO_CALL_4b:
			call->args_cnt = 4;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			u8 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_BOOL, &u8);
			break;
		case GSERVER_VIDEO_CALL_4d:
			call->args_cnt = 4;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			d = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &d);
			break;
		case GSERVER_VIDEO_CALL_4f:
			call->args_cnt = 4;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			f = va_arg(ap, double);
			call->args[i++] = variable_new(VT_DOUBLE, &f);
			break;
		case GSERVER_VIDEO_CALL_4i:
			call->args_cnt = 4;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			u32 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT32, &u32);
			break;
		case GSERVER_VIDEO_CALL_4s:
			call->args_cnt = 4;
			if((call->args = object_new(sizeof(v) * call->args_cnt))
					== NULL)
				return _queue_error(call);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			u16 = va_arg(ap, uint32_t);
			call->args[i++] = variable_new(VT_UINT16, &u16);
			break;
	}
	va_end(ap);
	for(i = 0; i < call->args_cnt; i++)
		if(call->args[i] == NULL)
			return _queue_error(call);
	gsc->calls_cnt++;
	return 0;
}

static int _queue_error(GServerCall * call)
{
	size_t i;

	for(i = 0; i < call->args_cnt; i++)
		if(call->args[i] != NULL)
			variable_delete(call->args[i]);
	object_delete(call->args);
	return -1;
}
