/*
    Raydium - CQFD Corp.
    http://raydium.org/
    Released under both BSD license and Lesser GPL library license.
    See "license.txt" file.
*/

#include "headers/myglut.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
//#include <X11/Xutil.h> // provided by glx

#include <GL/glx.h>

#ifdef HAVE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif

#ifdef HAVE_DPMS
#include <X11/extensions/dpms.h>
#endif

#ifdef HAVE_MOTIF
#include <X11/Xm/MwmUtil.h>
#else
/* bit definitions for MwmHints.flags */
#define MWM_HINTS_FUNCTIONS     (1L << 0)
#define MWM_HINTS_DECORATIONS   (1L << 1)
#define MWM_HINTS_INPUT_MODE    (1L << 2)
#define MWM_HINTS_STATUS        (1L << 3)

/* bit definitions for MwmHints.decorations */
#define MWM_DECOR_ALL           (1L << 0)
#define MWM_DECOR_BORDER        (1L << 1)
#define MWM_DECOR_RESIZEH       (1L << 2)
#define MWM_DECOR_TITLE         (1L << 3)
#define MWM_DECOR_MENU          (1L << 4)
#define MWM_DECOR_MINIMIZE      (1L << 5)
#define MWM_DECOR_MAXIMIZE      (1L << 6)

typedef struct
{
  unsigned long flags       ;
  unsigned long functions   ;
  unsigned long decorations ;
           long inputMode   ;
  unsigned long status      ;
} PropMotifWmHints ;

#define PROP_MOTIF_WM_HINTS_ELEMENTS    5

#endif

#ifndef GLX_SAMPLE_BUFFERS_ARB
#define GLX_SAMPLE_BUFFERS_ARB 100000
#define GLX_SAMPLES_ARB        100001
#endif


Display     *currDisplay ;
XVisualInfo *visualInfo  ;
Window       currHandle  ;
GLXContext   currContext ;
Window       rootWindow  ;
Atom         delWinAtom  ;

int currScreen;
int currConnect;

typedef struct PixelFormat
{
  int num_samples ;
  int bits_per_pixel ;
  int z_bits ;
  int stencil_bits ;
} PixelFormat;

signed char XineramaAndFullscreenFocusHack=0;
signed char FullscreenFlag=0;

PixelFormat preferred_pixel_formats [] =
{
  /* NumSamples, RGB_bits, Z_bits, Stencil */

  {  0, 24, 24,  1 },  /* Progressively nastier image formats */
  {  0, 16, 24,  1 },
  {  0, 16, 16,  1 },
  {  0, 16, 16,  0 },
  { -1, -1, -1, -1 }   /* Magic end marker */
} ;

void pwInit ( int x, int y, int w, int h, int multisample,
              char *title, int border, int num_samples, int window_style );
int raydium_init_cli_option(char *option, char *value);


// ---------------------- public API

void glutInit(int *argc, char **argv)
{
currDisplay = NULL ;
visualInfo  = NULL ;

currScreen  = 0;
currConnect = 0;

glutReshapeFuncCB=NULL;
glutKeyboardFuncCB=NULL;
glutSpecialUpFuncCB=NULL;
glutSpecialFuncCB=NULL;
glutMotionFuncCB=NULL;
glutPassiveMotionFuncCB=NULL;
glutMouseFuncCB=NULL;
glutDisplayFuncCB=NULL;
glutIdleFuncCB=NULL;

_glutMouseVisible=1;
}




void myglutCloseWindow(void)
{
  glXDestroyContext ( currDisplay, currContext ) ;
  XDestroyWindow    ( currDisplay, currHandle  ) ;
  XFlush            ( currDisplay ) ;
}



//glutSetCursor
void glutSetCursor(int cursor)
{
int currCursor;
Pixmap pix ;
char   blank_cursor[16*16];
XColor bcol ;

switch(cursor)
    {
    case GLUT_CURSOR_LEFT_ARROW:
        currCursor = XC_left_ptr;
        XDefineCursor( currDisplay, currHandle,
                 XCreateFontCursor ( currDisplay, currCursor ) ) ;
        _glutMouseVisible=1;
        break;
    case GLUT_CURSOR_NONE:
    default:
        memset(blank_cursor,0,16*16);

        pix = XCreateBitmapFromData ( currDisplay,
                                      rootWindow,
                                      blank_cursor, 16, 16 ) ;
        XDefineCursor ( currDisplay, currHandle,
                        XCreatePixmapCursor ( currDisplay,
                            pix, pix, &bcol, &bcol, 0, 0 ) ) ;
        XFreePixmap   ( currDisplay, pix ) ;
        _glutMouseVisible=1;
    }
}

//glutWarpPointer (move mouse)
void glutWarpPointer(int x, int y)
{
  XWarpPointer(currDisplay, None, currHandle, 0, 0, 0, 0, x, y);
  XFlush(currDisplay);
}

//glutSwapBuffers
void glutSwapBuffers(void)
{
  glFlush () ;
  glXSwapBuffers ( currDisplay, currHandle ) ;
  // get events ?
}


//glutMainLoop is generic (myglut.c)

// ------------- private part

void chooseVisual (PixelFormat *pf)
{
  int attribs [ 100 ] ;
  int n = 0 ;

  attribs [n++] = GLX_RGBA ;

  switch ( pf->bits_per_pixel )
  {
    case 3 :
      attribs [n++] = GLX_RED_SIZE   ; attribs [n++] = 1 ;
      attribs [n++] = GLX_GREEN_SIZE ; attribs [n++] = 1 ;
      attribs [n++] = GLX_BLUE_SIZE  ; attribs [n++] = 1 ;
      break ;

    case 16 :
      attribs [n++] = GLX_RED_SIZE   ; attribs [n++] = 5 ;
      attribs [n++] = GLX_GREEN_SIZE ; attribs [n++] = 6 ;
      attribs [n++] = GLX_BLUE_SIZE  ; attribs [n++] = 5 ;
      break ;

    case 24 :
      attribs [n++] = GLX_RED_SIZE   ; attribs [n++] = 8 ;
      attribs [n++] = GLX_GREEN_SIZE ; attribs [n++] = 8 ;
      attribs [n++] = GLX_BLUE_SIZE  ; attribs [n++] = 8 ;
      break ;
  }

  switch ( pf->z_bits )
  {
    case  1 : attribs [n++] = GLX_DEPTH_SIZE ; attribs [n++] =  1 ; break ;
    case 16 : attribs [n++] = GLX_DEPTH_SIZE ; attribs [n++] = 16 ; break ;
    case 24 : attribs [n++] = GLX_DEPTH_SIZE ; attribs [n++] = 24 ; break ;
    case 32 : attribs [n++] = GLX_DEPTH_SIZE ; attribs [n++] = 32 ; break ;
  }

  switch ( pf->stencil_bits )
  {
    case  1 : attribs [n++] = GLX_STENCIL_SIZE ; attribs [n++] = 1 ; break ;
    case  8 : attribs [n++] = GLX_STENCIL_SIZE ; attribs [n++] = 8 ; break ;
  }

  if ( pf->num_samples > 0 )
  {
    attribs [n++] = GLX_SAMPLE_BUFFERS_ARB ; attribs [n++] = 1 ;
    attribs [n++] = GLX_SAMPLES_ARB        ; attribs [n++] = pf->num_samples ;
  }

  attribs [n++] = GLX_DOUBLEBUFFER ;
  attribs [n++] = None ;

  visualInfo = glXChooseVisual ( currDisplay, currScreen, attribs ) ;
}



void pwInit ( int x, int y, int w, int h, int multisample,
              char *title, int border, int num_samples, int window_style )
{
  char *displayName = NULL;
  int i;
  //int origin[2];
  //int size[2];
  int DispX,DispY; // X screen size

  XSetWindowAttributes attribs ;
  XTextProperty   textProperty ;
  XSizeHints         sizeHints ;
  XWMHints             wmHints ;
  unsigned int            mask ;
  PixelFormat               pf ;

  int interval, prefer_blank, allow_exp, timeout;

#ifdef HAVE_XINERAMA
  int i_d1, i_d2;
#endif

  PropMotifWmHints hints  ;
  Atom prop_t ;
  Atom prop   ;

  displayName=getenv ( "DISPLAY" ) ;

  if ( displayName == NULL ) displayName = ":0.0" ;

  currDisplay = XOpenDisplay ( displayName ) ;

  if ( currDisplay == NULL )
  {
    raydium_log("(my)glut: ERROR: Can't open display '%s'",
                            XDisplayName ( displayName ) ) ;
    exit ( 1 ) ;
  }

  /* OpenGL GLX extension availability? */

  if ( ! glXQueryExtension ( currDisplay, NULL, NULL ) )
  {
    raydium_log("(my)glut: ERROR: GLX extension not available on display '%s'",
                                             XDisplayName ( displayName ) ) ;
    exit ( 1 ) ;
  }

  currScreen   = DefaultScreen    ( currDisplay ) ;
  rootWindow   = RootWindow       ( currDisplay, currScreen ) ;
  currConnect  = ConnectionNumber ( currDisplay ) ;
  delWinAtom   = XInternAtom      ( currDisplay, "WM_DELETE_WINDOW", 0 ) ;

  DispX = DisplayWidth  ( currDisplay, currScreen ) ;
  DispY = DisplayHeight ( currDisplay, currScreen ) ;

#ifdef HAVE_XINERAMA
  if(XineramaQueryExtension( currDisplay, &i_d1, &i_d2 )
  && XineramaIsActive( currDisplay ) )
  {
  XineramaScreenInfo *screens;
  int num_screens;
  int selected;
  char str[RAYDIUM_MAX_NAME_LEN];


  screens = XineramaQueryScreens(currDisplay,&num_screens);
  raydium_log("Xinerama detected with %i screens:",num_screens);

  for(i=0;i<num_screens;i++)
    {
    raydium_log("*** screen %i : %ix%i at (%i,%i)",i,screens[i].width,screens[i].height,screens[i].x_org,screens[i].y_org);
    }

  if(raydium_init_cli_option("xinerama-fullscreen",NULL))
    {
    raydium_log("... but using Xinerama fullscreen anyway !");
    }
    else
    {
    if(raydium_init_cli_option("xinerama-screen",str))
        selected=atoi(str);
    else
        selected=0;

    if(selected<0 || selected >=num_screens)
        {
        raydium_log("invalid screen id !");
        selected=0;
        }
    raydium_log("using Xinerama screen %i",selected);

    x+=screens[selected].x_org;
    y+=screens[selected].y_org;
    DispX=screens[selected].width;
    DispY=screens[selected].height;
    if(w==-1 && h==-1) XineramaAndFullscreenFocusHack=1;
    }

  XFree(screens);
  } // end Xinerama
  else
  {
  raydium_log("no Xinerama on this display");
  }
#else
  raydium_log("no Xinerama support. See config.h for HAVE_XINERAMA symbol");
#endif

  if ( w == -1 && h == -1)
  {
  w = DispX;
  h = DispY;
  FullscreenFlag = 1;
  }

  /*origin [ 0 ] = x ;
  origin [ 1 ] = y ;
  size   [ 0 ] = w ;
  size   [ 1 ] = h ;*/

  for (i = 0 ; preferred_pixel_formats [ i ] . num_samples >= 0 ; i++ )
  {
    pf = preferred_pixel_formats [ i ] ;
    pf . num_samples = num_samples ;

    chooseVisual ( &pf ) ;

    if ( visualInfo != NULL )
      break ;
  }

  if ( visualInfo == NULL )
  {
    num_samples = 0 ;

    for (i = 0 ; preferred_pixel_formats [ i ] . num_samples >= 0 ; i++ )
    {
      pf = preferred_pixel_formats [ i ] ;
      pf . num_samples = num_samples ;

      chooseVisual ( &pf ) ;

      if ( visualInfo != NULL )
        break ;
    }

    if ( visualInfo == NULL )
    {
      raydium_log("(my)glut: ERROR: Unable to open a suitable window");
      exit ( 1 ) ;
    }
  }


  attribs.event_mask = StructureNotifyMask | ExposureMask         |
                       ButtonPressMask     | ButtonReleaseMask    |
                       KeyPressMask        | KeyReleaseMask       |
                       EnterWindowMask     | LeaveWindowMask      |
                       PointerMotionMask   | ButtonMotionMask     |
                       VisibilityChangeMask ;

  attribs.background_pixmap = None ;
  attribs.background_pixel  = 0    ;
  attribs.border_pixel      = 0    ;

  attribs.colormap = XCreateColormap ( currDisplay, rootWindow,
                                       visualInfo->visual, AllocNone ) ;

  mask = CWBackPixmap | CWBorderPixel | CWColormap | CWEventMask;

  if(FullscreenFlag)
    {
    attribs.override_redirect = True;
    mask |= CWOverrideRedirect;
    }

  currHandle = XCreateWindow ( currDisplay, rootWindow,
                           x, y, w, h, 0, visualInfo->depth,
                           InputOutput, visualInfo->visual,
                           mask, &attribs ) ;

  currContext = glXCreateContext ( currDisplay, visualInfo, NULL, 1 ) ;

  glXMakeCurrent ( currDisplay, currHandle, currContext ) ;

  if ( ! glXIsDirect ( currDisplay, glXGetCurrentContext() ) )
  {
    raydium_log("(my)glut: WARNING: This is an *INDIRECT* rendering context.") ;
  }

  sizeHints.flags = 0 ;

  if ( x >= 0 && y >= 0 )
    sizeHints.flags |= USPosition ;

  sizeHints.flags |= USSize ;

  sizeHints.x      = x ; sizeHints.y      = y ;
  sizeHints.width  = w ; sizeHints.height = h ;

  if(FullscreenFlag || window_style==RAYDIUM_RENDERING_WINDOW_FIXED)
    {
    // make this window unresizable
    sizeHints.flags |= PMinSize;
    sizeHints.flags |= PMaxSize;
    sizeHints.min_width=w;
    sizeHints.max_width=w;
    sizeHints.min_height=h;
    sizeHints.max_height=h;
    }


  wmHints.flags = StateHint;
  wmHints.initial_state = NormalState ;


  hints . flags = MWM_HINTS_DECORATIONS ;
  hints . decorations = border ? MWM_DECOR_ALL : 0 ;

  prop_t = prop = XInternAtom ( currDisplay, "_MOTIF_WM_HINTS", True ) ;

  if ( prop != 0 )
    XChangeProperty ( currDisplay, currHandle, prop, prop_t, 32,
                      PropModeReplace, (unsigned char *) &hints,
                      PROP_MOTIF_WM_HINTS_ELEMENTS) ;

  XStringListToTextProperty ( (char **) &title, 1, &textProperty ) ;

  XSetWMProperties ( currDisplay, currHandle,
                          &textProperty, &textProperty, 0, 0,
                          &sizeHints, &wmHints, NULL ) ;
  XSetWMProtocols  ( currDisplay, currHandle, &delWinAtom  , 1 );
  XMapWindow       ( currDisplay, currHandle ) ;
  glXMakeCurrent   ( currDisplay, currHandle, currContext ) ;

  glutSetCursor(GLUT_CURSOR_LEFT_ARROW);


  glClear ( GL_COLOR_BUFFER_BIT ) ;
  glutSwapBuffers();
  glClear ( GL_COLOR_BUFFER_BIT ) ;
  glutSwapBuffers();

  raydium_log("Found %ix%i with %i bpp color and %i bits zbuffer (stencil is %i)",sizeHints.width,sizeHints.height,pf.bits_per_pixel,pf.z_bits,pf.stencil_bits);

  _glutWindowSize[0]=sizeHints.width;
  _glutWindowSize[1]=sizeHints.height;
  _glutWindowInverseRatio=0;

  if(FullscreenFlag)
       XGrabKeyboard(currDisplay,currHandle,False,GrabModeAsync,GrabModeAsync,CurrentTime);
//     XSetInputFocus(currDisplay,currHandle,RevertToNone,CurrentTime);

  // Disable screensaver (should we save SS state and set it back on exit ?)
#ifdef HAVE_DPMS
  DPMSDisable(currDisplay);
#endif
  XGetScreenSaver(currDisplay, &timeout, &interval, &prefer_blank, &allow_exp);
    if (timeout)
        XSetScreenSaver(currDisplay, 0, interval, prefer_blank, allow_exp);
}


void myglutGetEvents (void)
{
  static int size[2]={-1,-1};
  XEvent event ;

  XComposeStatus composeStatus ;
  char           asciiCode [ 32 ] ;
  KeySym         keySym ;

  int len,result;
  signed char special=0;

//  insideCallback = true ;

  while ( XPending ( currDisplay ) )
  {
    int updown = GLUT_DOWN ;

    XNextEvent ( currDisplay, &event ) ;

//    refreshModifiers ( &event ) ;

    switch ( event.type )
    {
      case ClientMessage   : exit(0) ; break ;
      case DestroyNotify   : exit(0) ; break ;

      case ConfigureNotify :
        if ( currHandle == event.xconfigure.window &&
              ( size[0] != event.xconfigure.width ||
                size[1] != event.xconfigure.height ) )
        {
          size[0] = event.xconfigure.width ;
          size[1] = event.xconfigure.height ;

          glXMakeCurrent ( currDisplay, currHandle, currContext ) ;
          glXWaitX    () ;

          if (glutReshapeFuncCB)
            glutReshapeFuncCB(size[0], size[1]);
        }
        break;

      case MappingNotify:
        XRefreshKeyboardMapping ( (XMappingEvent *) &event ) ;
        break;

      case EnterNotify     :
        if(XineramaAndFullscreenFocusHack)
            {
            XSetInputFocus(currDisplay,currHandle,RevertToParent,CurrentTime);
            XRaiseWindow(currDisplay,currHandle);
            }
        break;
      case LeaveNotify     :
      case VisibilityNotify:
      case Expose          : break ;

      case MotionNotify    :
        if (glutPassiveMotionFuncCB)
         glutPassiveMotionFuncCB( event.xmotion.x, event.xmotion.y);
        break ;

      case ButtonRelease   :
        updown = GLUT_UP ;
        // FALLTHROUGH

      case ButtonPress     :
        {
          if (glutMouseFuncCB)
            glutMouseFuncCB(event.xbutton.button-1, updown, event.xbutton.x, event.xbutton.y ) ;
        }
        break ;

      case KeyRelease      :
        updown = GLUT_UP ;
        // FALLTHROUGH

      case KeyPress        :

        len = XLookupString( &event.xkey, asciiCode, sizeof(asciiCode),
                                 &keySym, &composeStatus ) ;
        result = -1 ;

        if( len > 0 )
		{
		// be sure to capture "Return" and "KP_Enter" (some laptops sends the
		// same code for the regular Enter key and the numeric pad Enter key)
		if(len==1 && asciiCode[0]==0x0d)
			result = 0x0d;
		else
        	result = keySym;
		}
        else
        {
        special=1;
          switch( keySym )
          {
            case XK_F1:     result = GLUT_KEY_F1;     break;
            case XK_F2:     result = GLUT_KEY_F2;     break;
            case XK_F3:     result = GLUT_KEY_F3;     break;
            case XK_F4:     result = GLUT_KEY_F4;     break;
            case XK_F5:     result = GLUT_KEY_F5;     break;
            case XK_F6:     result = GLUT_KEY_F6;     break;
            case XK_F7:     result = GLUT_KEY_F7;     break;
            case XK_F8:     result = GLUT_KEY_F8;     break;
            case XK_F9:     result = GLUT_KEY_F9;     break;
            case XK_F10:    result = GLUT_KEY_F10;    break;
            case XK_F11:    result = GLUT_KEY_F11;    break;
            case XK_F12:    result = GLUT_KEY_F12;    break;

            case XK_KP_Left:
            case XK_Left:   result = GLUT_KEY_LEFT;   break;

            case XK_KP_Right:
            case XK_Right:  result = GLUT_KEY_RIGHT;  break;

            case XK_KP_Up:
            case XK_Up:     result = GLUT_KEY_UP;     break;

            case XK_KP_Down:
            case XK_Down:   result = GLUT_KEY_DOWN;   break;

            case XK_KP_Prior:
            case XK_Prior:  result = GLUT_KEY_PAGE_UP; break;
            case XK_KP_Next:
            case XK_Next:   result = GLUT_KEY_PAGE_DOWN; break;
            case XK_KP_Home:
            case XK_Home:   result = GLUT_KEY_HOME;   break;
            case XK_KP_End:
            case XK_End:    result = GLUT_KEY_END;    break;
            case XK_KP_Insert:
            case XK_Insert: result = GLUT_KEY_INSERT; break;
          }
        }

        if(result!=-1)
            {
            // check autorepeat with current KeyRelease and next event
            if (special && XEventsQueued(currDisplay, QueuedAfterReading))
            {
            XEvent ahead;
            XPeekEvent(currDisplay, &ahead);
            if (ahead.type == KeyPress && event.type == KeyRelease
                && ahead.xkey.window == event.xkey.window
                && ahead.xkey.keycode == event.xkey.keycode
                && ahead.xkey.time == event.xkey.time)
                {
                // repeating key. Discard event.
                break;
                }
            }

            // special down
            if(special && updown==GLUT_DOWN && glutSpecialFuncCB && !raydium_key[result%RAYDIUM_KEYBOARD_SIZE])
                glutSpecialFuncCB(result,event.xkey.x, event.xkey.y);

            // special up
            if(special && updown==GLUT_UP && glutSpecialUpFuncCB && raydium_key[result%RAYDIUM_KEYBOARD_SIZE])
                glutSpecialUpFuncCB(result,event.xkey.x, event.xkey.y);

            // normal
            if(!special && updown==GLUT_DOWN && glutKeyboardFuncCB)
                glutKeyboardFuncCB(result,event.xkey.x, event.xkey.y);

            }

        break ;
    }
  }

//  insideCallback = false ;

  glXMakeCurrent ( currDisplay, currHandle, currContext ) ;
}