#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>

#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>

#include "common.h"
#include "doomkeys.h"

#include "i_system.h"
#include "m_argv.h"
#include "d_main.h"

#include "doomdef.h"

static int video_initialized = 0;

extern byte *screens[5];

typedef struct Color {
    unsigned char b;
    unsigned char g;
    unsigned char r;
    unsigned char a;
} Color;

static Color current_palette[256];
static char framebuffer[SCREENWIDTH*SCREENHEIGHT*4];

static int window_w;
static int window_h;
static int offset_x;

int multiply = 1;

static long frame_num = 0;

//static int mouse_captured = 0;

XEvent X_event;
Display *display;
Window window;
GC context;
XImage *image;

void I_ShutdownGraphics(void) {
    if (!video_initialized) return;

    // called from I_Quit also I_Error in i_system.c

    XDestroyImage(image);
    XCloseDisplay(display);
}

void I_StartFrame (void) {
    // called from d_main

    // er?

    /*if (WindowShouldClose()) I_Quit();*/
}
/*
struct joystick_state {
    int axis1;
    int axis2;
    int buttons;
};

struct joystick_state poll_joystick() {
    struct joystick_state js = {0,0,0};
    if (!IsGamepadAvailable(0)) return js;
    float a1 = GetGamepadAxisMovement(0, 0);
    js.axis1 = a1 < -0.1 ? -1 : (a1 > 0.1 ? 1 : 0);
    float a2 = GetGamepadAxisMovement(0, 1);
    js.axis2 = a2 < -0.1 ? -1 : (a2 > 0.1 ? 1 : 0);
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_RIGHT_FACE_LEFT) ? 1 : 0;
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_RIGHT_FACE_DOWN) ? 2 : 0;
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_RIGHT_FACE_UP) ? 4 : 0;
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT) ? 8 : 0;
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_TRIGGER_1) ? 16 : 0;
    js.buttons |= IsGamepadButtonDown(0, GAMEPAD_BUTTON_RIGHT_TRIGGER_1) ? 32 : 0;
    return js;
}

struct joystick_state joystate = {0,0,0};
*/
int xlatekey()
{
    int rc;

    switch(rc = XKeycodeToKeysym(display, X_event.xkey.keycode, 0))
    {
      case XK_Left:	rc = KEY_LEFTARROW;	break;
      case XK_Right:	rc = KEY_RIGHTARROW;	break;
      case XK_Down:	rc = KEY_DOWNARROW;	break;
      case XK_Up:	rc = KEY_UPARROW;	break;
      case XK_Escape:	rc = KEY_ESCAPE;	break;
      case XK_Return:	rc = KEY_ENTER;		break;
      case XK_Tab:	rc = KEY_TAB;		break;
      case XK_F1:	rc = KEY_F1;		break;
      case XK_F2:	rc = KEY_F2;		break;
      case XK_F3:	rc = KEY_F3;		break;
      case XK_F4:	rc = KEY_F4;		break;
      case XK_F5:	rc = KEY_F5;		break;
      case XK_F6:	rc = KEY_F6;		break;
      case XK_F7:	rc = KEY_F7;		break;
      case XK_F8:	rc = KEY_F8;		break;
      case XK_F9:	rc = KEY_F9;		break;
      case XK_F10:	rc = KEY_F10;		break;
      case XK_F11:	rc = KEY_F11;		break;
      case XK_F12:	rc = KEY_F12;		break;
	
      case XK_BackSpace:
      case XK_Delete:	rc = KEY_BACKSPACE;	break;

      case XK_Pause:	rc = KEY_PAUSE;		break;

      case XK_KP_Equal:
      case XK_equal:	rc = KEY_EQUALS;	break;

      case XK_KP_Subtract:
      case XK_minus:	rc = KEY_MINUS;		break;

      case XK_Shift_L:
      case XK_Shift_R:
	rc = KEY_RSHIFT;
	break;
	
      case XK_Control_L:
      case XK_Control_R:
	rc = KEY_RCTRL;
	break;
	
      case XK_Alt_L:
      case XK_Meta_L:
      case XK_Alt_R:
      case XK_Meta_R:
	rc = KEY_RALT;
	break;
	
      default:
	if (rc >= XK_space && rc <= XK_asciitilde)
	    rc = rc - XK_space + ' ';
	if (rc >= 'A' && rc <= 'Z')
	    rc = rc - 'A' + 'a';
	break;
    }

    return rc;
}

void I_GetEvent(void) {
    // was called from I_StartTic
    // get events and post them with D_PostEvent
    // also updated mousemoved

    event_t ev;

    /* cat mouse */
/*  Vector2 delta = GetMouseDelta();
    int motion = delta.x || delta.y;
    int button =
        IsMouseButtonPressed(0) ||
        IsMouseButtonPressed(1) ||
        IsMouseButtonPressed(2) ||
        IsMouseButtonReleased(0) ||
        IsMouseButtonReleased(1) ||
        IsMouseButtonReleased(2);

    if (mouse_captured && (button || motion)) {
        ev.type = ev_mouse;
        ev.data1 = IsMouseButtonDown(0) | (IsMouseButtonDown(1) ? 2 : 0) | (IsMouseButtonDown(2) ? 4 : 0);
        ev.data2 = (int)delta.x;
        ev.data3 = (int)-delta.y;
        D_PostEvent(&ev);
    }*/

    /* joy stick */
/*  struct joystick_state jcurrent = poll_joystick();
    int jchange =
        jcurrent.axis1 != joystate.axis1 ||
        jcurrent.axis2 != joystate.axis2 ||
        jcurrent.buttons != joystate.buttons;
    if(jchange) {
        joystate = jcurrent;
        ev.type = ev_joystick;
        ev.data3 = joystate.axis2;
        ev.data2 = joystate.axis1;
        ev.data1 = joystate.buttons;
        D_PostEvent(&ev);
    }*/

    /* kbd */
    if (XCheckWindowEvent(display,window,KeyPressMask |
    KeyReleaseMask,&X_event) != False)
    switch (X_event.xkey.type)
    {
      case KeyPress:
    ev.type = ev_keydown;
    ev.data1 = xlatekey();
    D_PostEvent(&ev);
    // fprintf(stderr, "k");
    break;
      case KeyRelease:
    ev.type = ev_keyup;
    ev.data1 = xlatekey();
    D_PostEvent(&ev);
    // fprintf(stderr, "ku");
    break;
    }

    /*if (rc==xlatekey()) { EnableCursor(); mouse_captured = false; }
    if (IsMouseButtonDown(1)) { DisableCursor(); mouse_captured = true; }*/

}

void I_StartTic (void) {
    // called from d_net, d_main
    // calls I_GetEvent repeatedly

    I_GetEvent();
}

void I_UpdateNoBlit (void) {
    // called from d_main
}

void I_FinishUpdate (void) {
    // called from d_main
    // copied image data from screens[0] into some shm image and synced
    // optionally expanding the picture 2x 3x ...

    Color *writing = framebuffer;
    for (int i = 0; i < SCREENWIDTH*SCREENHEIGHT; i++) {
        *writing++ = current_palette[screens[0][i]];
    }

    /* doesn't work. don't use */
    // scales the screen size before blitting it
    if (multiply == 2)
    {
	unsigned int *olineptrs[2];
	unsigned int *ilineptr;
	int x, y, i;
	unsigned int twoopixels;
	unsigned int twomoreopixels;
	unsigned int fouripixels;

	ilineptr = (unsigned int *) (screens[0]);
	for (i=0 ; i<2 ; i++)
	    olineptrs[i] = (unsigned int *) &image->data[i*window_w];

	y = SCREENHEIGHT;
	while (y--)
	{
	    x = SCREENWIDTH;
	    do
	    {
		fouripixels = *ilineptr++;
		twoopixels =	(fouripixels & 0xff000000)
		    |	((fouripixels>>8) & 0xffff00)
		    |	((fouripixels>>16) & 0xff);
		twomoreopixels =	((fouripixels<<16) & 0xff000000)
		    |	((fouripixels<<8) & 0xffff00)
		    |	(fouripixels & 0xff);
#ifdef __BIG_ENDIAN__
		*olineptrs[0]++ = twoopixels;
		*olineptrs[1]++ = twoopixels;
		*olineptrs[0]++ = twomoreopixels;
		*olineptrs[1]++ = twomoreopixels;
#else
		*olineptrs[0]++ = twomoreopixels;
		*olineptrs[1]++ = twomoreopixels;
		*olineptrs[0]++ = twoopixels;
		*olineptrs[1]++ = twoopixels;
#endif
	    } while (x-=4);
	    olineptrs[0] += window_w/4;
	    olineptrs[1] += window_w/4;
	}

    }
    else if (multiply == 3)
    {
	unsigned int *olineptrs[3];
	unsigned int *ilineptr;
	int x, y, i;
	unsigned int fouropixels[3];
	unsigned int fouripixels;

	ilineptr = (unsigned int *) (screens[0]);
	for (i=0 ; i<3 ; i++)
	    olineptrs[i] = (unsigned int *) &image->data[i*window_w];

	y = SCREENHEIGHT;
	while (y--)
	{
	    x = SCREENWIDTH;
	    do
	    {
		fouripixels = *ilineptr++;
		fouropixels[0] = (fouripixels & 0xff000000)
		    |	((fouripixels>>8) & 0xff0000)
		    |	((fouripixels>>16) & 0xffff);
		fouropixels[1] = ((fouripixels<<8) & 0xff000000)
		    |	(fouripixels & 0xffff00)
		    |	((fouripixels>>8) & 0xff);
		fouropixels[2] = ((fouripixels<<16) & 0xffff0000)
		    |	((fouripixels<<8) & 0xff00)
		    |	(fouripixels & 0xff);
#ifdef __BIG_ENDIAN__
		*olineptrs[0]++ = fouropixels[0];
		*olineptrs[1]++ = fouropixels[0];
		*olineptrs[2]++ = fouropixels[0];
		*olineptrs[0]++ = fouropixels[1];
		*olineptrs[1]++ = fouropixels[1];
		*olineptrs[2]++ = fouropixels[1];
		*olineptrs[0]++ = fouropixels[2];
		*olineptrs[1]++ = fouropixels[2];
		*olineptrs[2]++ = fouropixels[2];
#else
		*olineptrs[0]++ = fouropixels[2];
		*olineptrs[1]++ = fouropixels[2];
		*olineptrs[2]++ = fouropixels[2];
		*olineptrs[0]++ = fouropixels[1];
		*olineptrs[1]++ = fouropixels[1];
		*olineptrs[2]++ = fouropixels[1];
		*olineptrs[0]++ = fouropixels[0];
		*olineptrs[1]++ = fouropixels[0];
		*olineptrs[2]++ = fouropixels[0];
#endif
	    } while (x-=4);
	    olineptrs[0] += 2*window_w/4;
	    olineptrs[1] += 2*window_w/4;
	    olineptrs[2] += 2*window_w/4;
	}

    }

    XPutImage(display,window,context,image,0,0,0,0,window_w,window_h);

    I_Sleep(0.025); /* do? */

    frame_num++;
}

void I_ReadScreen (byte* scr) {
    // called from f_wipe to read the screen
    memcpy (scr, screens[0], SCREENWIDTH*SCREENHEIGHT);
}

void I_SetPalette (byte* palette) {
    // called from m_menu, d_main, st_stuff
    // to set the palette

    for (int i = 0; i < 256; i++) {
        current_palette[i].r = palette[3*i + 0];
        current_palette[i].g = palette[3*i + 1];
        current_palette[i].b = palette[3*i + 2];
        current_palette[i].a = 255;
    }

}

void I_InitGraphics(void) {
    // called from d_main

    // set up video backend and possibly override screen[0]
    // which is where rendering ultimately writes to
    // before FinishUpdate presents it, whatever is in screen[0]

/*  if (M_CheckParm("-fullscreen")) {
        int monitor = GetCurrentMonitor();
        window_h = GetMonitorHeight(monitor);
        window_w = window_h * 4 / 3;
        offset_x = GetMonitorWidth(monitor)/2 - window_w/2;
        SetWindowSize(window_w, window_h);
        ToggleBorderlessWindowed();
    }*/
    /*else {*/
    /*if (M_CheckParm("-1")) multiply = 1;  useless */
    if (M_CheckParm("-2")) multiply = 2;
    else if (M_CheckParm("-3")) multiply = 3;
    /*else if (M_CheckParm("-4")) multiply = 4;
    else if (M_CheckParm("-5")) multiply = 5;
    else if (M_CheckParm("-10")) multiply = 10;*/ /* wtf */
    /* that'd be 3200x2400... you have a 4K capitalist mon or smth? */

    window_w = SCREENWIDTH * multiply;
    window_h = SCREENHEIGHT * multiply;
    offset_x = 0;

    /* based on Licar git.coom.tech/drummyfish/licar */
    display = XOpenDisplay(0);
    int screen = DefaultScreen(display);
  
    window = XCreateSimpleWindow(display,RootWindow(display,screen),10,10,
      window_w,window_h,1,
      BlackPixel(display,screen),WhitePixel(display,screen));
XSizeHints *sizeHints = XAllocSizeHints();
    sizeHints->flags = PMaxSize | PMinSize;
    sizeHints->max_width = window_w;
    sizeHints->min_width = window_w;
    sizeHints->max_height = window_h;
    sizeHints->min_height = window_h;
    XSetWMNormalHints(display,window,sizeHints);
    XFree(sizeHints);

    XMapWindow(display,window);

    XSelectInput(display,window,KeyPressMask | KeyReleaseMask);

    context = DefaultGC(display,screen);
          
    XStoreName(display,window,"DOOM");

    /*
      Hardcoded constants here may perhaps cause trouble on some platforms, but
      doing X11 "the right way" would mean 1 billion lines of code, so fuck it.
    */
    image = XCreateImage(display,DefaultVisual(display,screen),
      /*DefaultDepth(display,screen)*/24,ZPixmap,0,framebuffer,
      window_w,window_h,8,0);

    /* forking autorepeat */
    XkbSetDetectableAutoRepeat(display,True,0);

    video_initialized = 1;

    // two obscure controllers I have
    /*SetGamepadMappings("03000000790000004e95000011010000,DragonRise Inc. NGC USB Gamepad,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:a4,rightx:a5,righty:a2~,start:b9,x:b2,y:b3,platform:Linux,");
    SetGamepadMappings("03000000790000001100000010010000,!NNEXT Gamepad,a:b2,b:b1,x:b3,y:b0,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftx:a0,lefty:a1,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0");*/
}
