#include <iostream>
#include <string>

#ifdef __unix__
#include <sys/time.h>
#endif

#include "../System/Debug.h"
#include "LGLXWindow.h"
#include "Game.h"

using std::string;

//typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display* dpy, GLXFBConfig config,
//                        GLXContext share_context, bool direct, const int* attrib_list);

unsigned int GetTickCount(void) {
  struct timeval t;
  gettimeofday(&t, NULL);

  unsigned long secs = t.tv_sec * 1000;
    secs += (t.tv_usec / 1000);
    return secs;
}

LGLXWindow::LGLXWindow(void) :
    _game(NULL),
    _isRunning(true),
    _lastTime(0),
    _display(NULL),
    _xWindow(0),
    _glContext(0),
    _screenID(0),
    _isFullscreen(false),
    _width(0),
    _height(0),
    _bpp(0),
    _GL3Supported(false)
    { CreateInput(); }

LGLXWindow::~LGLXWindow(void) {

}

bool LGLXWindow::Create(int width, int height, int bpp, bool fullscreen) {
  // Open the default display.
  _display = XOpenDisplay(0);
  if(!_display) {
    Debug::logger->message("Could not open the display.");
    return false;
  }

  // Get the default screen ID.
  _screenID = DefaultScreen(_display);

  int n = 0, modeNum = 0;
  // Get a frambuffer config useing the default attributes.
  GLXFBConfig framebufferConfig = (*glXChooseFBConfig(_display, DefaultScreen(_display), 0, &n));

  XF86VidModeModeInfo **modes;
  if(!XF86VidModeGetAllModeLines(_display, _screenID, &modeNum, &modes)) {
    Debug::logger->message("Could not query the video modes.");
    return false;
  }

  _XF86DeskMode = *modes[0];

  int bestMode = -1;
  for(int i = 0; i < modeNum; i++) {
    if((modes[i]->hdisplay == width) && (modes[i]->vdisplay == height)) {
      bestMode = i;
    }
  }

  if(bestMode == -1) {
    Debug::logger->message("Could not find a suitable graphics mode.");
    return false;
  }

  int doubleBufferedAttribList[] = {
    GLX_RGBA,       GLX_DOUBLEBUFFER,
    GLX_RED_SIZE,   4,
    GLX_GREEN_SIZE, 4,
    GLX_BLUE_SIZE,  4,
    GLX_DEPTH_SIZE, 16,
    None
  };

  XVisualInfo* vi = NULL;
  // Attempt to create a double buffered window.
  vi = glXChooseVisual(_display, _screenID, doubleBufferedAttribList);
  if(!vi) {
    Debug::logger->message("Could not create a double buffere window.. Sux..");
  }

  // Time to create a GL 2.1 context.
  GLXContext gl2Context = glXCreateContext(_display, vi, 0, GL_TRUE);
  if(!gl2Context) {
    Debug::logger->message("Could Not create a GL 2.1 context, check your darn graphics drivers");
    return false;
  }

  // Get a pointer to the GL 3.0 context creation.
  PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribs
        = (PFNGLXCREATECONTEXTATTRIBSARBPROC) glXGetProcAddress((GLubyte*)"glXCreateContextAttribsARB");
  if(glXCreateContextAttribs == NULL) {
    Debug::logger->message("OpenGL 3.0 is not supported, falling back to 2.1");
    _glContext = gl2Context;
    _GL3Supported = false;
  } else {
    // We create a GL 3.0 context..

    int attribs[] = {
      GLX_CONTEXT_MAJOR_VERSION_ARB, 3, // We want a 3.0 context.
      GLX_CONTEXT_MINOR_VERSION_ARB, 0,
      0 // Zero indicates the end of the array.
    };

    _glContext = glXCreateContextAttribs(_display, framebufferConfig, 0, true, &attribs[0]);
    // We can destroy thr GL 2.0 context once the 3.0 one has been checked.
    glXDestroyContext(_display, gl2Context);
    _GL3Supported = true;
  }

  Colormap cmap = XCreateColormap(_display, RootWindow(_display, vi->screen), vi->visual, AllocNone);
  _XSetAttr.colormap      = cmap;
  _XSetAttr.border_pixel  = 0;
  _XSetAttr.event_mask    = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
                                        StructureNotifyMask;
  _XSetAttr.override_redirect = False;

  //unsigned long windowAttributes = CWBorderPixel | CWColormap | CWEventMask;

  if(fullscreen) {
    //windowAttributes = CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;

    XF86VidModeSwitchToMode(_display, _screenID, modes[bestMode]);
    XF86VidModeSetViewPort(_display, _screenID, 0, 0);
    _XSetAttr.override_redirect = True;
  }

  _xWindow = XCreateWindow(_display, RootWindow(_display, vi->screen),
                          0, 0, width, height, 0, vi->depth, InputOutput, vi->visual,
                          CWBorderPixel | CWColormap | CWEventMask, &_XSetAttr);
  string title = "LibD";

  if(fullscreen) {
    XWarpPointer(_display, None, _xWindow, 0, 0, 0, 0, 0, 0);
    XMapRaised(_display, _xWindow);
    XGrabKeyboard(_display, _xWindow, True, GrabModeAsync, GrabModeAsync, CurrentTime);
    XGrabPointer(_display, _xWindow, True, ButtonPressMask, GrabModeAsync, GrabModeAsync,
                       _xWindow, None, CurrentTime);
  } else {
    Atom wmDelete = XInternAtom(_display, "WM_DELETE_WINDOW", True);
    XSetWMProtocols(_display, _xWindow, &wmDelete, 1);
    XSetStandardProperties(_display, _xWindow, title.c_str(), None, 0, NULL, 0, NULL);
    XMapRaised(_display, _xWindow);
  }

  XFree(modes);

  glXMakeCurrent(_display, _xWindow, _glContext);

  int posX  = 0;
  int posY  = 0;
  Window winDummy;
  unsigned int borderDummy;

  _width  = (unsigned) width;
  _height = (unsigned) height;
  _bpp    = (unsigned) bpp;

  XGetGeometry(_display, _xWindow, &winDummy, &posX, &posY,
                &_width, &_height, &borderDummy, &_bpp);

  // Init the time.
  _lastTime = GetTickCount();
  return true;
}

void LGLXWindow::Destroy(void) {
  if(_glContext) {
    glXMakeCurrent(_display, None, NULL);
    glXDestroyContext(_display, _glContext);
    _glContext = NULL;
  }
  if(_isFullscreen) {
    XF86VidModeSwitchToMode(_display, _screenID, &_XF86DeskMode);
    XF86VidModeSetViewPort(_display, _screenID, 0, 0);
  }
  DestroyInput();
  XCloseDisplay(_display);
}

void LGLXWindow::ProcessEvents(void) {
  XEvent event;

  while(XPending(_display) > 0) {
    XNextEvent(_display, &event);
    switch(event.type) {
    case Expose:
      if(event.xexpose.count != 0)
        break;
      break;
    case ConfigureNotify:
    {
      int width   = event.xconfigure.width;
      int height  = event.xconfigure.height;
      GetAttachedGame()->OnResize(width, height);
    }
    break;
    case KeyPress:
    {
      if(XLookupKeysym(&event.xkey, 0) == XK_Escape) {
        _isRunning = false;
      }
      UpdateInput();
      // Register the key press with keyboard interface.
    }
    break;
    case KeyRelease:
    {
      // Code here NAW!
    }
    break;
    case ClientMessage:
      if(string(XGetAtomName(_display, event.xclient.message_type)) == string("WM_PROTOCOLS")) {
        _isRunning = false;
      }
      break;
    default:
      break;
    }
  }
}

float LGLXWindow::GetElapsedSeconds(void) {
  unsigned int currentTime = GetTickCount();
  unsigned int diff = currentTime - _lastTime;
  _lastTime = currentTime;
  return float(diff) / 1000.0f;
}
