/*
Copyright (C) 1996-1997 Id Software, Inc.

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; either version 2
of the License, or (at your option) any later version.

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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/vt.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>

#include "quakedef.h"

#include <GL/glx.h>

#include <X11/keysym.h>
#include <X11/cursorfont.h>

#ifdef USE_DGA
#include <X11/extensions/xf86dga.h>
#endif


#define WARP_WIDTH              320
#define WARP_HEIGHT             200

static Display *dpy = NULL;
static Window win;
static GLXContext ctx = NULL;

static float old_windowed_mouse = 0;

#define KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \
        PointerMotionMask)

#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask)

unsigned short  d_8to16table[256];
unsigned    d_8to24table[256];
unsigned char d_15to8table[65536];

cvar_t  _windowed_mouse = {"_windowed_mouse","0", true};
cvar_t  vid_mode = {"vid_mode","0",false};
 
static float   mouse_x, mouse_y;
static float  old_mouse_x, old_mouse_y;

cvar_t  m_filter = {"m_filter", "0"};

static int scr_width, scr_height;

/*-----------------------------------------------------------------------*/

//int   texture_mode = GL_NEAREST;
//int   texture_mode = GL_NEAREST_MIPMAP_NEAREST;
//int   texture_mode = GL_NEAREST_MIPMAP_LINEAR;
int   texture_mode = GL_LINEAR;
//int   texture_mode = GL_LINEAR_MIPMAP_NEAREST;
//int   texture_mode = GL_LINEAR_MIPMAP_LINEAR;

int   texture_extension_number = 1;

float   gldepthmin, gldepthmax;

cvar_t  gl_ztrick = {"gl_ztrick","1"};

const char *gl_vendor;
const char *gl_renderer;
const char *gl_version;
const char *gl_extensions;

qboolean is8bit = false;
qboolean isPermedia = false;
qboolean gl_mtexable = false;

/*-----------------------------------------------------------------------*/
void D_BeginDirectRect (int x, int y, byte *pbitmap, int width, int height)
{
}

void D_EndDirectRect (int x, int y, int width, int height)
{
}

static int XLateKey(XKeyEvent *ev)
{

  int key;
  char buf[64];
  KeySym keysym;

  key = 0;

  XLookupString(ev, buf, sizeof buf, &keysym, 0);

  switch(keysym)
  {
    case XK_KP_Page_Up:  
    case XK_Page_Up:   key = K_PGUP; break;

    case XK_KP_Page_Down: 
    case XK_Page_Down:   key = K_PGDN; break;

    case XK_KP_Home: 
    case XK_Home:  key = K_HOME; break;

    case XK_KP_End:  
    case XK_End:   key = K_END; break;

    case XK_KP_Left: 
    case XK_Left:  key = K_LEFTARROW; break;

    case XK_KP_Right: 
    case XK_Right:  key = K_RIGHTARROW;   break;

    case XK_KP_Down: 
    case XK_Down:  key = K_DOWNARROW; break;

    case XK_KP_Up:   
    case XK_Up:    key = K_UPARROW;  break;

    case XK_Escape: key = K_ESCAPE;   break;

    case XK_KP_Enter: 
    case XK_Return: key = K_ENTER;     break;

    case XK_Tab:    key = K_TAB;       break;

    case XK_F1:    key = K_F1;        break;

    case XK_F2:    key = K_F2;        break;

    case XK_F3:    key = K_F3;        break;

    case XK_F4:    key = K_F4;        break;

    case XK_F5:    key = K_F5;        break;

    case XK_F6:    key = K_F6;        break;

    case XK_F7:    key = K_F7;        break;

    case XK_F8:    key = K_F8;        break;

    case XK_F9:    key = K_F9;        break;

    case XK_F10:    key = K_F10;       break;

    case XK_F11:    key = K_F11;       break;

    case XK_F12:    key = K_F12;       break;

    case XK_BackSpace: key = K_BACKSPACE; break;

    case XK_KP_Delete: 
    case XK_Delete: key = K_DEL; break;

    case XK_Pause:  key = K_PAUSE;     break;

    case XK_Shift_L:
    case XK_Shift_R:  key = K_SHIFT;    break;

    case XK_Execute: 
    case XK_Control_L: 
    case XK_Control_R:  key = K_CTRL;    break;

    case XK_Alt_L:  
    case XK_Meta_L: 
    case XK_Alt_R:  
    case XK_Meta_R: key = K_ALT;      break;

    case XK_KP_Begin: key = '5';  break;

    case XK_KP_Insert: 
    case XK_Insert:key = K_INS; break;

    case XK_KP_Multiply: key = '*'; break;
    case XK_KP_Add:  key = '+'; break;
    case XK_KP_Subtract: key = '-'; break;
    case XK_KP_Divide: key = '/'; break;

#if 0
    case 0x021: key = '1';break;/* [!] */
    case 0x040: key = '2';break;/* [@] */
    case 0x023: key = '3';break;/* [#] */
    case 0x024: key = '4';break;/* [$] */
    case 0x025: key = '5';break;/* [%] */
    case 0x05e: key = '6';break;/* [^] */
    case 0x026: key = '7';break;/* [&] */
    case 0x02a: key = '8';break;/* [*] */
    case 0x028: key = '9';;break;/* [(] */
    case 0x029: key = '0';break;/* [)] */
    case 0x05f: key = '-';break;/* [_] */
    case 0x02b: key = '=';break;/* [+] */
    case 0x07c: key = '\'';break;/* [|] */
    case 0x07d: key = '[';break;/* [}] */
    case 0x07b: key = ']';break;/* [{] */
    case 0x022: key = '\'';break;/* ["] */
    case 0x03a: key = ';';break;/* [:] */
    case 0x03f: key = '/';break;/* [?] */
    case 0x03e: key = '.';break;/* [>] */
    case 0x03c: key = ',';break;/* [<] */
#endif

    default:
      key = *(unsigned char*)buf;
      if (key >= 'A' && key <= 'Z')
        key = key - 'A' + 'a';
      break;
  } 

  return key;
}

static void install_grabs(void)
{
  XGrabPointer(dpy, win,
         True,
         0,
         GrabModeAsync, GrabModeAsync,
         win,
         None,
         CurrentTime);

#ifdef USE_DGA
  XF86DGADirectVideo(dpy, DefaultScreen(dpy), XF86DGADirectMouse);
  dgamouse = 1;
#else
  XWarpPointer(dpy, None, win,
         0, 0, 0, 0,
         vid.width / 2, vid.height / 2);
#endif

  XGrabKeyboard(dpy, win,
          False,
          GrabModeAsync, GrabModeAsync,
          CurrentTime);

//  XSync(dpy, True);
}

static void uninstall_grabs(void)
{
#ifdef USE_DGA
  XF86DGADirectVideo(dpy, DefaultScreen(dpy), 0);
  dgamouse = 0;
#endif

  XUngrabPointer(dpy, CurrentTime);
  XUngrabKeyboard(dpy, CurrentTime);

//  XSync(dpy, True);
}

static void GetEvent(void)
{
  XEvent event;
  int b;

  if (!dpy)
    return;

  XNextEvent(dpy, &event);

  switch (event.type) {
  case KeyPress:
  case KeyRelease:
    Key_Event(XLateKey(&event.xkey), event.type == KeyPress);
    break;

  case MotionNotify:
#ifdef USE_DGA
    if (dgamouse && _windowed_mouse.value) {
      mouse_x = event.xmotion.x_root;
      mouse_y = event.xmotion.y_root;
    } else
#endif
    {
      if (_windowed_mouse.value) {
        mouse_x = (float) ((int)event.xmotion.x - (int)(vid.width/2));
        mouse_y = (float) ((int)event.xmotion.y - (int)(vid.height/2));

        /* move the mouse to the window center again */
        XSelectInput(dpy, win, X_MASK & ~PointerMotionMask);
        XWarpPointer(dpy, None, win, 0, 0, 0, 0, 
          (vid.width/2), (vid.height/2));
        XSelectInput(dpy, win, X_MASK);
      }
    }
    break;

  case ButtonPress:
    b=-1;
    if (event.xbutton.button == 1)
      b = 0;
    else if (event.xbutton.button == 2)
      b = 2;
    else if (event.xbutton.button == 3)
      b = 1;
    if (b>=0)
      Key_Event(K_MOUSE1 + b, true);
    break;

  case ButtonRelease:
    b=-1;
    if (event.xbutton.button == 1)
      b = 0;
    else if (event.xbutton.button == 2)
      b = 2;
    else if (event.xbutton.button == 3)
      b = 1;
    if (b>=0)
      Key_Event(K_MOUSE1 + b, false);
    break;
  }

  if (old_windowed_mouse != _windowed_mouse.value) {
    old_windowed_mouse = _windowed_mouse.value;

    if (!_windowed_mouse.value) {
      /* ungrab the pointer */
      uninstall_grabs();
    } else {
      /* grab the pointer */
      install_grabs();
    }
  }
}


void VID_Shutdown(void)
{
  if (!ctx)
    return;

  glXDestroyContext(dpy, ctx);
}

void signal_handler(int sig)
{
  printf("Received signal %d, exiting...\n", sig);
  Sys_Quit();
  exit(0);
}

void InitSig(void)
{
  signal(SIGHUP, signal_handler);
  signal(SIGINT, signal_handler);
  signal(SIGQUIT, signal_handler);
  signal(SIGILL, signal_handler);
  signal(SIGTRAP, signal_handler);
  signal(SIGIOT, signal_handler);
  signal(SIGBUS, signal_handler);
  signal(SIGFPE, signal_handler);
  signal(SIGSEGV, signal_handler);
  signal(SIGTERM, signal_handler);
}

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

void  VID_SetPalette (unsigned char *palette)
{
  byte  *pal;
  unsigned r,g,b;
  unsigned v;
  int     r1,g1,b1;
  int   k;
  unsigned short i;
  unsigned  *table;
  FILE *f;
  char s[255];
  float dist, bestdist;
  static qboolean palflag = false;

//
// 8 8 8 encoding
//
  Con_Printf("Converting 8to24\n");

  pal = palette;
  table = d_8to24table;
  for (i=0 ; i<256 ; i++)
  {
    r = pal[0];
    g = pal[1];
    b = pal[2];
    pal += 3;
    
//    v = (255<<24) + (r<<16) + (g<<8) + (b<<0);
//    v = (255<<0) + (r<<8) + (g<<16) + (b<<24);
    v = (255<<24) + (r<<0) + (g<<8) + (b<<16);
    *table++ = v;
  }
  d_8to24table[255] &= 0xffffff;  // 255 is transparent

  // JACK: 3D distance calcs - k is last closest, l is the distance.
  // FIXME: Precalculate this and cache to disk.
  if (palflag)
    return;
  palflag = true;

  COM_FOpenFile("glquake/15to8.pal", &f);
  if (f) {
    fread(d_15to8table, 1<<15, 1, f);
    fclose(f);
  } else {
    for (i=0; i < (1<<15); i++) {
      /* Maps
      000000000000000
      000000000011111 = Red  = 0x1F
      000001111100000 = Blue = 0x03E0
      111110000000000 = Grn  = 0x7C00
      */
      r = ((i & 0x1F) << 3)+4;
      g = ((i & 0x03E0) >> 2)+4;
      b = ((i & 0x7C00) >> 7)+4;
      pal = (unsigned char *)d_8to24table;
      for (v=0,k=0,bestdist=10000.0; v<256; v++,pal+=4) {
        r1 = (int)r - (int)pal[0];
        g1 = (int)g - (int)pal[1];
        b1 = (int)b - (int)pal[2];
        dist = sqrt(((r1*r1)+(g1*g1)+(b1*b1)));
        if (dist < bestdist) {
          k=v;
          bestdist = dist;
        }
      }
      d_15to8table[i]=k;
    }
    sprintf(s, "%s/glquake", com_gamedir);
    Sys_mkdir (s);
    sprintf(s, "%s/glquake/15to8.pal", com_gamedir);
    if ((f = fopen(s, "wb")) != NULL) {
      fwrite(d_15to8table, 1<<15, 1, f);
      fclose(f);
    }
  }
}

/*
===============
GL_Init
===============
*/
void GL_Init (void)
{
  gl_vendor = glGetString (GL_VENDOR);
  Con_Printf ("GL_VENDOR: %s\n", gl_vendor);
  gl_renderer = glGetString (GL_RENDERER);
  Con_Printf ("GL_RENDERER: %s\n", gl_renderer);

  gl_version = glGetString (GL_VERSION);
  Con_Printf ("GL_VERSION: %s\n", gl_version);
  gl_extensions = glGetString (GL_EXTENSIONS);
  Con_Printf ("GL_EXTENSIONS: %s\n", gl_extensions);

//  Con_Printf ("%s %s\n", gl_renderer, gl_version);

  glClearColor (1,0,0,0);
  glCullFace(GL_FRONT);
  glEnable(GL_TEXTURE_2D);

  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_GREATER, 0.666);

  glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
  glShadeModel (GL_FLAT);

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

//  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

/*
=================
GL_BeginRendering

=================
*/
void GL_BeginRendering (int *x, int *y, int *width, int *height)
{
  extern cvar_t gl_clear;

  *x = *y = 0;
  *width = scr_width;
  *height = scr_height;

//    if (!wglMakeCurrent( maindc, baseRC ))
//    Sys_Error ("wglMakeCurrent failed");

//  glViewport (*x, *y, *width, *height);
}


void GL_EndRendering (void)
{
  glFlush();
  glXSwapBuffers(dpy, win);
}

qboolean VID_Is8bit(void)
{
  return is8bit;
}

#ifdef GL_EXT_SHARED
void VID_Init8bitPalette() 
{
  // Check for 8bit Extensions and initialize them.
  int i;
  char thePalette[256*3];
  char *oldPalette, *newPalette;

  if (strstr(gl_extensions, "GL_EXT_shared_texture_palette") == NULL)
    return;

  Con_SafePrintf("8-bit GL extensions enabled.\n");
  glEnable( GL_SHARED_TEXTURE_PALETTE_EXT );
  oldPalette = (char *) d_8to24table; //d_8to24table3dfx;
  newPalette = thePalette;
  for (i=0;i<256;i++) {
    *newPalette++ = *oldPalette++;
    *newPalette++ = *oldPalette++;
    *newPalette++ = *oldPalette++;
    oldPalette++;
  }
  glColorTableEXT(GL_SHARED_TEXTURE_PALETTE_EXT, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, (void *) thePalette);
  is8bit = true;
}

#else
extern void gl3DfxSetPaletteEXT(GLuint *pal);

void VID_Init8bitPalette(void) 
{
  // Check for 8bit Extensions and initialize them.
  int i;
  GLubyte table[256][4];
  char *oldpal;

  if (strstr(gl_extensions, "3DFX_set_global_palette") == NULL)
    return;

  Con_SafePrintf("8-bit GL extensions enabled.\n");
  glEnable( GL_SHARED_TEXTURE_PALETTE_EXT );
  oldpal = (char *) d_8to24table; //d_8to24table3dfx;
  for (i=0;i<256;i++) {
    table[i][2] = *oldpal++;
    table[i][1] = *oldpal++;
    table[i][0] = *oldpal++;
    table[i][3] = 255;
    oldpal++;
  }
  gl3DfxSetPaletteEXT((GLuint *)table);
  is8bit = true;
}
#endif

void VID_Init(unsigned char *palette)
{
  int i;
  int attrib[] = {
    GLX_RGBA,
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_DOUBLEBUFFER,
    GLX_DEPTH_SIZE, 1,
    None
  };
  char  gldir[MAX_OSPATH];
  int width = 640, height = 480;
  int scrnum;
  XSetWindowAttributes attr;
  unsigned long mask;
  Window root;
  XVisualInfo *visinfo;

  S_Init();

  Cvar_RegisterVariable (&vid_mode);
  Cvar_RegisterVariable (&gl_ztrick);
  Cvar_RegisterVariable (&_windowed_mouse);
  
  vid.maxwarpwidth = WARP_WIDTH;
  vid.maxwarpheight = WARP_HEIGHT;
  vid.colormap = host_colormap;
  vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048));

// interpret command-line params

// set vid parameters
  if ((i = COM_CheckParm("-width")) != 0)
    width = atoi(com_argv[i+1]);
  if ((i = COM_CheckParm("-height")) != 0)
    height = atoi(com_argv[i+1]);

  if ((i = COM_CheckParm("-conwidth")) != 0)
    vid.conwidth = Q_atoi(com_argv[i+1]);
  else
    vid.conwidth = 640;

  vid.conwidth &= 0xfff8; // make it a multiple of eight

  if (vid.conwidth < 320)
    vid.conwidth = 320;

  // pick a conheight that matches with correct aspect
  vid.conheight = vid.conwidth*3 / 4;

  if ((i = COM_CheckParm("-conheight")) != 0)
    vid.conheight = Q_atoi(com_argv[i+1]);
  if (vid.conheight < 200)
    vid.conheight = 200;

  if (!(dpy = XOpenDisplay(NULL))) {
    fprintf(stderr, "Error couldn't open the X display\n");
    exit(1);
  }

  scrnum = DefaultScreen(dpy);
  root = RootWindow(dpy, scrnum);

  visinfo = glXChooseVisual(dpy, scrnum, attrib);
  if (!visinfo) {
    fprintf(stderr, "qkHack: Error couldn't get an RGB, Double-buffered, Depth visual\n");
    exit(1);
  }
  /* window attributes */
  attr.background_pixel = 0;
  attr.border_pixel = 0;
  attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone);
  attr.event_mask = X_MASK;
  mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;

  win = XCreateWindow(dpy, root, 0, 0, width, height,
            0, visinfo->depth, InputOutput,
            visinfo->visual, mask, &attr);
  XMapWindow(dpy, win);

  XMoveWindow(dpy, win, 0, 0);

  XFlush(dpy);

  ctx = glXCreateContext(dpy, visinfo, NULL, True);

  glXMakeCurrent(dpy, win, ctx);

  scr_width = width;
  scr_height = height;

  if (vid.conheight > height)
    vid.conheight = height;
  if (vid.conwidth > width)
    vid.conwidth = width;
  vid.width = vid.conwidth;
  vid.height = vid.conheight;

  vid.aspect = ((float)vid.height / (float)vid.width) * (320.0 / 240.0);
  vid.numpages = 2;

  InitSig(); // trap evil signals

  GL_Init();

  sprintf (gldir, "%s/glquake", com_gamedir);
  Sys_mkdir (gldir);

  VID_SetPalette(palette);

  // Check for 3DFX Extensions and initialize them.
  VID_Init8bitPalette();

  Con_SafePrintf ("Video mode %dx%d initialized.\n", width, height);

  vid.recalc_refdef = 1;        // force a surface cache flush
}

void Sys_SendKeyEvents(void)
{
  if (dpy) {
    while (XPending(dpy)) 
      GetEvent();
  }
}

void Force_CenterView_f (void)
{
  cl.viewangles[PITCH] = 0;
}

void IN_Init(void)
{
}

void IN_Shutdown(void)
{
}

/*
===========
IN_Commands
===========
*/
void IN_Commands (void)
{
}

/*
===========
IN_Move
===========
*/
void IN_MouseMove (usercmd_t *cmd)
{
  if (m_filter.value)
  {
    mouse_x = (mouse_x + old_mouse_x) * 0.5;
    mouse_y = (mouse_y + old_mouse_y) * 0.5;
  }
  old_mouse_x = mouse_x;
  old_mouse_y = mouse_y;

  mouse_x *= sensitivity.value;
  mouse_y *= sensitivity.value;

// add mouse X/Y movement to cmd
  if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) ))
    cmd->sidemove += m_side.value * mouse_x;
  else
    cl.viewangles[YAW] -= m_yaw.value * mouse_x;
  
  if (in_mlook.state & 1)
    V_StopPitchDrift ();
    
  if ( (in_mlook.state & 1) && !(in_strafe.state & 1))
  {
    cl.viewangles[PITCH] += m_pitch.value * mouse_y;
    if (cl.viewangles[PITCH] > 80)
      cl.viewangles[PITCH] = 80;
    if (cl.viewangles[PITCH] < -70)
      cl.viewangles[PITCH] = -70;
  }
  else
  {
    if ((in_strafe.state & 1) && noclip_anglehack)
      cmd->upmove -= m_forward.value * mouse_y;
    else
      cmd->forwardmove -= m_forward.value * mouse_y;
  }
  mouse_x = mouse_y = 0.0;
}

void IN_Move (usercmd_t *cmd)
{
  IN_MouseMove(cmd);
}


void VID_UnlockBuffer() {}
void VID_LockBuffer() {}

