// vid_x.c -- general x video driver

#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>

#include "quakedef.h"
#include "d_local.h"

static char *quake_xpm[] = {
    "64 64 5 1",
    " 	c None",
    ".	c #000000",
    "+	c #1C1C89",
    "@	c #ABBAC6",
    "#	c #E21616",
    "                                                                ",
    "                                                                ",
    "                                                                ",
    "                                                                ",
    "    .......................................................     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++#+#++#++######++###++++++++++++#++++++++++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++++++++++++#++++++++++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++++++++++++#++++++++++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++++++++++++#++++++++++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#+++##++#+#++###+++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#++++#++#+#++#+#+++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#++###++##+++#+#+++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#++#+#++#+#++###+++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#++#+#++#+#++#+++++++++@     ",
    "    .+++++++#+#++#++#+#++#++#+#++#+#++#+#++#+#++#+#+++++++@     ",
    "    .+++++++######++#+#++#++###++####+####+#+#++###+++++++@     ",
    "    .+++++++++++++++++++++++++#+++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .++++++++++++++++++++++#++++++###+++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+++++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+##++##+##++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++##+##+++###+++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    .+++++++++++++++++++++++++++++++++++++++++++++++++++++@     ",
    "    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@     ",
    "                                                                ",
    "                                                                ",
    "                                                                ",
    "                                                                ",
    "                                                                "
};

viddef_t vid;			// global video state
unsigned short d_8to16table[256];

int num_shades = 32;
int gdk_initialized = 0;
int scalefactor = 0;

int d_con_indirect = 0;

int vid_buffersize;

static GdkWindow *x_win;
static GdkWindow *x_icon;
static GdkGC *x_gc;
static GdkPixmap *background;
static GdkBitmap *mask;

#if 0
static GdkRgbCmap *x_cmap = NULL;
#endif

#if 0
static qboolean oktodraw = false;
#endif

static unsigned char *x_framebuffer = NULL;

static int verbose = 0;

static byte current_palette[768];

static long X11_highhunkmark;
static long X11_buffersize;

int vid_surfcachesize;
void *vid_surfcache;

void (*vid_menudrawfn) (void);
void (*vid_menukeyfn) (int key);
void VID_MenuKey(int key);

// ========================================================================
// Tragic death handler
// ========================================================================

void TragicDeath(int signal_num)
{
    Sys_Error("This death brought to you by the number %d\n", signal_num);
}

void ResetFrameBuffer(void)
{

    int mem;
    if (x_framebuffer)
	free(x_framebuffer);

    if (d_pzbuffer) {
	D_FlushCaches();
	Hunk_FreeToHighMark(X11_highhunkmark);
	d_pzbuffer = NULL;
    }
    X11_highhunkmark = Hunk_HighMark();

/* alloc an extra line in case we want to wrap, and allocate the z-buffer */
    X11_buffersize = vid.width * vid.height * sizeof(*d_pzbuffer);

    vid_surfcachesize = D_SurfaceCacheForRes(vid.width, vid.height);

    X11_buffersize += vid_surfcachesize;

    d_pzbuffer = Hunk_HighAllocName(X11_buffersize, "video");
    if (d_pzbuffer == NULL)
	Sys_Error("Not enough memory for video mode\n");

    vid_surfcache = (byte *) d_pzbuffer
	+ vid.width * vid.height * sizeof(*d_pzbuffer);

    D_InitCaches(vid_surfcache, vid_surfcachesize);

    mem = ((vid.width + 7) & ~7) * vid.height;

    x_framebuffer = malloc(vid.width * vid.height * 3);

    if (!x_framebuffer)
	Sys_Error("VID: gdk_image_new failed\n");

    vid.buffer = (byte *) (x_framebuffer);
    vid.conbuffer = vid.buffer;
}

// Called at startup to set up translation tables, takes 256 8 bit RGB values
// the palette data will go away after the call, so it must be copied off if
// the video driver will need it again

void VID_Init(unsigned char *palette)
{
    GdkWindowAttr attr;
    struct sigaction sa;
    XWMHints wmhints;

    vid.width = 320;
    vid.height = 320;
    vid.maxwarpwidth = WARP_WIDTH;
    vid.maxwarpheight = WARP_HEIGHT;
    vid.numpages = 2;
    vid.colormap = host_colormap;
/*      vid.cbits = VID_CBITS;
        vid.grades = VID_GRADES; */
    vid.fullbright = 256 - LittleLong(*((int *) vid.colormap + 2048));

    scalefactor = vid.width / 53;

    srandom(getpid());

    verbose = COM_CheckParm("-verbose");

    /* initialize GDK */
    gdk_init(NULL, NULL);
    gdk_rgb_init();
    gdk_initialized = 1;

    sigaction(SIGINT, 0, &sa);
    sa.sa_handler = TragicDeath;
    sigaction(SIGINT, &sa, 0);
    sigaction(SIGTERM, &sa, 0);

/* setup attributes for main window */
    memset(&attr, 0, sizeof(attr));

    attr.width = 64;
    attr.height = 64;
    attr.title = "quake";
    attr.event_mask = GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
	GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK |
	GDK_LEAVE_NOTIFY_MASK;
    attr.visual = gdk_visual_get_system();
    attr.colormap = gdk_colormap_get_system();
    attr.wmclass_name = "quake";
    attr.wmclass_class = "quake";
    attr.window_type = GDK_WINDOW_TOPLEVEL;

/* create the main window */
    x_win = gdk_window_new(NULL, &attr, GDK_WA_TITLE | GDK_WA_WMCLASS |
			   GDK_WA_VISUAL | GDK_WA_COLORMAP);

    memset(&attr, 0, sizeof(attr));
    attr.width = 64;
    attr.height = 64;
    attr.title = "quake";
    attr.event_mask = GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
	GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK |
	GDK_LEAVE_NOTIFY_MASK;
    attr.visual = gdk_visual_get_system();
    attr.colormap = gdk_colormap_get_system();
    attr.wmclass_name = "quake";
    attr.wmclass_class = "quake";
    attr.window_type = GDK_WINDOW_TOPLEVEL;

    x_icon = gdk_window_new(x_win, &attr, GDK_WA_TITLE | GDK_WA_WMCLASS);

    wmhints.initial_state = WithdrawnState;
    wmhints.icon_window = GDK_WINDOW_XWINDOW(x_icon);
    wmhints.icon_x = 0;
    wmhints.icon_y = 0;
    wmhints.window_group = GDK_WINDOW_XWINDOW(x_win);
    wmhints.flags =
	StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
    XSetWMHints(GDK_WINDOW_XDISPLAY(x_win), GDK_WINDOW_XWINDOW(x_win),
		&wmhints);

/* create the GC */
    x_gc = gdk_gc_new(x_win);
    if (!x_gc)
	Sys_Error("VID: Cannot make GC\n");

    background =
	gdk_pixmap_create_from_xpm_d(x_win, &mask, NULL, quake_xpm);
    gdk_window_shape_combine_mask(x_win, mask, 0, 0);
    gdk_window_shape_combine_mask(x_icon, mask, 0, 0);

    gdk_window_set_back_pixmap(x_win, background, False);
    gdk_window_set_back_pixmap(x_icon, background, False);

/* map the window */
    gdk_window_show(x_win);

#if 0
/* wait for first exposure event */
    do {
	while (gdk_events_pending()) {
	    event = gdk_event_get();
	    if (event) {
		if (event->type == GDK_EXPOSE && !event->expose.count)
		    oktodraw = true;
	    }
	}
    } while (!oktodraw);
/*now safe to draw */
#endif

    ResetFrameBuffer();

    vid.rowbytes = vid.width;
    vid.buffer = x_framebuffer;
    vid.direct = 0;
    vid.conbuffer = x_framebuffer;
    vid.conrowbytes = vid.rowbytes;
    vid.conwidth = vid.width;
    vid.conheight = vid.height;
    vid.aspect =
	((float) vid.height / (float) vid.width) * (320.0 / 320.0);
}

void VID_ShiftPalette(unsigned char *p)
{
    VID_SetPalette(p);
}

void VID_SetPalette(unsigned char *palette)
{
    int i = 768;
#if 0
    unsigned char *ptr;
    unsigned int colors[256];

    if (x_cmap) {
	gdk_rgb_cmap_free(x_cmap);
    }
#endif

    while (i--)
	current_palette[i] = palette[i];

#if 0
    ptr = current_palette;

    for (i = 0; i < 256; i++) {
	unsigned char r, g, b;
	r = *ptr++;
	g = *ptr++;
	b = *ptr++;
	colors[i] = r << 16 | g << 8 | b;
    }
    x_cmap = gdk_rgb_cmap_new(colors, 256);
#endif
}

/* Called at shutdown */
void VID_Shutdown(void)
{
    Con_Printf("VID_Shutdown\n");
}

int XLateKey(int keysym)
{
    int key = 0;

    switch (keysym) {
    case GDK_Page_Up:
	key = K_PGUP;
	break;
    case GDK_Page_Down:
	key = K_PGDN;
	break;
    case GDK_Home:
	key = K_HOME;
	break;
    case GDK_End:
	key = K_END;
	break;
    case GDK_Left:
	key = K_LEFTARROW;
	break;
    case GDK_Right:
	key = K_RIGHTARROW;
	break;
    case GDK_Down:
	key = K_DOWNARROW;
	break;
    case GDK_Up:
	key = K_UPARROW;
	break;
    case GDK_Escape:
	key = K_ESCAPE;
	break;
    case GDK_Return:
	key = K_ENTER;
	break;
    case GDK_Tab:
	key = K_TAB;
	break;
    case GDK_space:
	key = K_SPACE;
	break;
    case GDK_F1:
	key = K_F1;
	break;
    case GDK_F2:
	key = K_F2;
	break;
    case GDK_F3:
	key = K_F3;
	break;
    case GDK_F4:
	key = K_F4;
	break;
    case GDK_F5:
	key = K_F5;
	break;
    case GDK_F6:
	key = K_F6;
	break;
    case GDK_F7:
	key = K_F7;
	break;
    case GDK_F8:
	key = K_F8;
	break;
    case GDK_F9:
	key = K_F9;
	break;
    case GDK_F10:
	key = K_F10;
	break;
    case GDK_F11:
	key = K_F11;
	break;
    case GDK_F12:
	key = K_F12;
	break;
    case GDK_BackSpace:
    case GDK_Delete:
	key = K_BACKSPACE;
	break;
    case GDK_Pause:
	key = K_PAUSE;
	break;
    case GDK_Shift_L:
    case GDK_Shift_R:
	key = K_SHIFT;
	break;
    case GDK_Execute:
    case GDK_Control_L:
    case GDK_Control_R:
	key = K_CTRL;
	break;
    case GDK_Alt_L:
    case GDK_Meta_L:
    case GDK_Alt_R:
    case GDK_Meta_R:
	key = K_ALT;
	break;
    case GDK_grave:
	key = '`';
	break;
    default:
	if (keysym > GDK_space && keysym <= GDK_9)
	    key = keysym;
	if (keysym >= GDK_A && keysym <= GDK_Z)
	    key = keysym - GDK_A + 'a';
	if (keysym >= GDK_a && keysym <= GDK_z)
	    key = keysym - GDK_a + 'a';
	break;
    }
    /* printf("keysym: %x, key: %x\n", keysym, key); */
    return key;
}

struct {
    int key;
    int down;
} keyq[64];
int keyq_head = 0;
int keyq_tail = 0;

void GetEvent(void)
{
    GdkEvent *event;

    event = gdk_event_get();

    if (event)
	switch (event->type) {
	case GDK_KEY_PRESS:
	    keyq[keyq_head].key = XLateKey(event->key.keyval);
	    keyq[keyq_head].down = true;
	    keyq_head = (keyq_head + 1) & 63;
	    break;
	case GDK_KEY_RELEASE:
	    keyq[keyq_head].key = XLateKey(event->key.keyval);
	    keyq[keyq_head].down = false;
	    keyq_head = (keyq_head + 1) & 63;
	    break;
	case GDK_ENTER_NOTIFY:
	    XSetInputFocus(GDK_WINDOW_XDISPLAY(x_win),
			   GDK_WINDOW_XWINDOW(x_icon), RevertToNone,
			   CurrentTime);
	    break;
	case GDK_LEAVE_NOTIFY:
	    XSetInputFocus(GDK_WINDOW_XDISPLAY(x_win),
			   None, RevertToNone, CurrentTime);
	    break;
	default:
	    break;
	}

}

// flushes the given rectangles from the view buffer to the screen
void VID_Update(vrect_t * rects)
{
    while (rects) {
	unsigned char buf[53 * 53 * 3];
	unsigned char *bufptr = buf;
	unsigned char sample;
	int x, y;
	for (y = 0; y < 53; y++) {
	    for (x = 0; x < 53; x++) {
		sample =
		    x_framebuffer[y * scalefactor * vid.width +
				  x * scalefactor];
		*bufptr++ = current_palette[sample * 3];
		*bufptr++ = current_palette[sample * 3 + 1];
		*bufptr++ = current_palette[sample * 3 + 2];
	    }
	}
	/* printf("drawing: %d %d %d %d %d %d\n", rects->x, rects->y, 
	   rects->x, rects->y, rects->width, rects->height); */
	/*gdk_draw_indexed_image(x_win, x_gc, rects->x, rects->y,
	   rects->width, rects->height,
	   GDK_RGB_DITHER_NONE,
	   x_framebuffer,
	   vid.width, x_cmap); */
	gdk_draw_rgb_image(x_win, x_gc, 5, 5, 53, 53, GDK_RGB_DITHER_NONE,
			   buf, 53 * 3);
	gdk_draw_rgb_image(x_icon, x_gc, 5, 5, 53, 53, GDK_RGB_DITHER_NONE,
			   buf, 53 * 3);
	rects = rects->pnext;
    }
}

static int dither;

void VID_DitherOn(void)
{
    if (dither == 0) {
	vid.recalc_refdef = 1;
	dither = 1;
    }
}

void VID_DitherOff(void)
{
    if (dither) {
	vid.recalc_refdef = 1;
	dither = 0;
    }
}

int Sys_OpenWindow(void)
{
    return 0;
}

void Sys_EraseWindow(int window)
{
}

void Sys_DrawCircle(int window, int x, int y, int r)
{
}

void Sys_DisplayWindow(int window)
{
}

void Sys_SendKeyEvents(void)
{
    if (gdk_initialized) {
	while (gdk_events_pending()) {
	    GetEvent();

	    while (keyq_head != keyq_tail) {
		Key_Event(keyq[keyq_tail].key, keyq[keyq_tail].down);
		keyq_tail = (keyq_tail + 1) & 63;
	    }
	}
    }
}

char *Sys_ConsoleInput(void)
{

    static char text[256];
    int len;
    fd_set readfds;
    int ready;
    struct timeval timeout;

    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    FD_ZERO(&readfds);
    FD_SET(0, &readfds);
    ready = select(1, &readfds, 0, 0, &timeout);

    if (ready > 0) {
	len = read(0, text, sizeof(text));
	if (len >= 1) {
	    text[len - 1] = 0;	// rip off the /n and terminate
	    return text;
	}
    }

    return 0;

}

void D_BeginDirectRect(int x, int y, byte * pbitmap, int width, int height)
{
// direct drawing of the "accessing disk" icon isn't supported under Linux
}

void D_EndDirectRect(int x, int y, int width, int height)
{
// direct drawing of the "accessing disk" icon isn't supported under Linux
}
