/******************************************************************************
 * $id: mapoglcontext.cpp 7725 2011-04-09 15:56:58Z toby $
 *
 * Project:  MapServer
 * Purpose:  Various template processing functions.
 * Author:   Steve Lime and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2008 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#ifdef USE_OGL

#include "mapserver.h"
#include "maperror.h"
#include "mapoglcontext.h"



#define _T(x) __TEXT(x)

ms_uint32 OglContext::MAX_MULTISAMPLE_SAMPLES = 16;
ms_uint32 OglContext::MAX_ANISOTROPY = 0;
ms_uint32 OglContext::MAX_TEXTURE_SIZE = 0;
ms_uint32 OglContext::MIN_TEXTURE_SIZE = 0;
OglContext* OglContext::current = NULL;

OglContext::OglContext(ms_uint32 width, ms_uint32 height)
  : valid(false)
{
  if (!window && !initWindow()) return;
  if (!sharingContext && !initSharingContext()) return;

  if (!(this->width = getTextureSize(GL_TEXTURE_WIDTH, width))) return;
  if (!(this->height = getTextureSize(GL_TEXTURE_HEIGHT, height))) return;

  if (!createPBuffer(this->width, this->height)) return;
  if (!makeCurrent()) return;
  valid = true;
}

ms_uint32 OglContext::getTextureSize(GLuint dimension, ms_uint32 value)
{
  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA,  value, value, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  GLint check = 0;
  glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, dimension, &check);

  if (glGetError() != GL_NO_ERROR) {
    msSetError(MS_OGLERR, "glGetTexLevelParameteriv failed. glError: %d", "OglContext::getTextureSize()", glGetError());
  }

  if (check == 0) {
    return MS_MAX(MS_MIN(NextPowerOf2(value), OglContext::MAX_TEXTURE_SIZE), OglContext::MIN_TEXTURE_SIZE);
  } else {
    msSetError(MS_OGLERR, "Unable to create opengl texture of map size", "OglContext::getTextureSize()");
    return value;
  }
}

GLuint OglContext::NextPowerOf2(GLuint in)
{
  in -= 1;

  in |= in >> 16;
  in |= in >> 8;
  in |= in >> 4;
  in |= in >> 2;
  in |= in >> 1;

  return in + 1;
}

#if defined(_WIN32) && !defined(__CYGWIN__)

HDC OglContext::window = NULL;
HGLRC OglContext::sharingContext = NULL;

LRESULT CALLBACK WndProc(HWND hWnd,
                         UINT message,
                         WPARAM wParam,
                         LPARAM lParam)
{
  return(1L);
}

OglContext::~OglContext()
{
  //TODO
}

bool OglContext::makeCurrent()
{
  if (current != this) {
    current = this;
    if (!wglMakeContextCurrentARB(hPBufferDC, hPBufferDC, hPBufferRC)) {
      msSetError(MS_OGLERR, "Can't Activate The GL Rendering pbuffer.", "OglContext::makeCurrent()");
      return FALSE;
    }
  }
  return true;
}

bool OglContext::initWindow()
{
  int windowPixelFormat;
  WNDCLASS wc;
  DWORD dwExStyle;
  DWORD dwStyle;
  RECT WindowRect;
  HMODULE hInstance = NULL;
  HWND hWnd = NULL;
  WindowRect.left=(long)0;
  WindowRect.right=(long)0;
  WindowRect.top=(long)0;
  WindowRect.bottom=(long)0;
  hInstance = GetModuleHandle(NULL);
  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = NULL;
  wc.lpszMenuName = NULL;
  wc.lpszClassName = _T("OpenGL");

  if (!RegisterClass(&wc)) {
    msSetError(MS_OGLERR, "Failed To Register The Window Class.", "OglContext::initWindow()");
    return FALSE;
  }
  dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
  dwStyle=WS_OVERLAPPEDWINDOW;

  AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);

  if (!(hWnd=CreateWindowEx( dwExStyle,
                             _T("OpenGL"),
                             _T("temp"),
                             dwStyle |
                             WS_CLIPSIBLINGS |
                             WS_CLIPCHILDREN,
                             0, 0,
                             WindowRect.right-WindowRect.left,
                             WindowRect.bottom-WindowRect.top,
                             NULL,
                             NULL,
                             hInstance,
                             NULL))) {
    msSetError(MS_OGLERR, "Window Creation Error.", "OglContext::initWindow()");
    return FALSE;
  }

  static PIXELFORMATDESCRIPTOR pfd= {
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW |
    PFD_SUPPORT_OPENGL |
    PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA,
    24, // Select Our Color Depth
    0, 0, 0, 0, 0, 0, // Color Bits Ignored
    8, // Alpha Buffer
    0, // Shift Bit Ignored
    0, // No Accumulation Buffer
    0, 0, 0, 0, // Accumulation Bits Ignored
    16, // 16Bit Z-Buffer (Depth Buffer)
    0, // No Stencil Buffer
    0, // No Auxiliary Buffer
    PFD_MAIN_PLANE, // Main Drawing Layer
    0, // Reserved
    0, 0, 0 // Layer Masks Ignored
  };

  if (!(window=GetDC(hWnd))) {
    msSetError(MS_OGLERR, "Can't Create A GL Device Context.. Last Error: %d", "OglContext::initWindow()", GetLastError());
    return FALSE;
  }

  if (!(windowPixelFormat=ChoosePixelFormat(window,&pfd))) {
    msSetError(MS_OGLERR, "Can't Find A Suitable windowPixelFormat. Last Error: %d", "OglContext::initWindow()", GetLastError());
    return FALSE;
  }

  if(!SetPixelFormat(window,windowPixelFormat,&pfd)) {
    msSetError(MS_OGLERR, "Can't Set The windowPixelFormat. Last Error: %d", "OglContext::initWindow()", GetLastError());
    return FALSE;
  }
  return TRUE;
}

bool OglContext::initSharingContext()
{
  if (!(sharingContext=wglCreateContext(window))) {
    msSetError(MS_OGLERR, "Can't Create A GL Rendering Context.", "OglContext::createContext()");
    return FALSE;
  }

  if(!wglMakeCurrent(window,sharingContext)) {
    msSetError(MS_OGLERR, "Can't Activate The GL Rendering Context.", "OglContext::createContext()");
    return FALSE;
  }

  if (!(wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB"))) {
    msSetError(MS_OGLERR, "unable to retreive wglChoosePixelFormatARB method.", "OglContext::createContext()");
    return FALSE;
  }

  if (!(wglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC)wglGetProcAddress("wglCreatePbufferARB"))) {
    msSetError(MS_OGLERR, "unable to retreive wglCreatePbufferARB method.", "OglContext::createContext()");
    return FALSE;
  }

  if (!(wglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC)wglGetProcAddress("wglGetPbufferDCARB"))) {
    msSetError(MS_OGLERR, "unable to retreive wglGetPbufferDCARB method.", "OglContext::createContext()");
    return FALSE;
  }

  if (!(wglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC)wglGetProcAddress("wglReleasePbufferDCARB"))) {
    msSetError(MS_OGLERR, "unable to retreive wglReleasePbufferDCARB method.", "OglContext::createContext()");
    return FALSE;
  }

  if (!(wglMakeContextCurrentARB = (PFNWGLMAKECONTEXTCURRENTARBPROC)wglGetProcAddress("wglMakeContextCurrentARB"))) {
    msSetError(MS_OGLERR, "unable to retreive wglMakeContextCurrentARB method.", "OglContext::createContext()");
    return FALSE;
  }

  glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*) &MAX_ANISOTROPY);
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&MAX_TEXTURE_SIZE);

  MIN_TEXTURE_SIZE = 8;


  return TRUE;
}

bool OglContext::createPBuffer(ms_uint32 width, ms_uint32 height)
{
  HPBUFFERARB hPBuffer = NULL;
  hPBufferDC = NULL;
  hPBufferRC = NULL;

  int pixelFormat;
  int valid = false;
  UINT numFormats = 0;
  float fAttributes[] = {0,0};
  int samples = MAX_MULTISAMPLE_SAMPLES;

  while ((!valid || numFormats == 0) && samples >= 0) {
    int iAttributes[] = {
      WGL_SAMPLES_ARB,samples,
      WGL_DRAW_TO_PBUFFER_ARB,GL_TRUE,
      WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
      WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
      WGL_COLOR_BITS_ARB,24,
      WGL_ALPHA_BITS_ARB,8,
      WGL_DEPTH_BITS_ARB,16,
      WGL_STENCIL_BITS_ARB,0,
      WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
      WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
      0,0
    };

    valid = wglChoosePixelFormatARB(window,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
    if (!valid || numFormats == 0) samples -= 2;
  }

  if(numFormats == 0) {
    msSetError(MS_OGLERR, "P-buffer Error: Unable to find an acceptable pixel format.", "OglContext::createPBuffer()");
    return FALSE;
  }

  if (!(hPBuffer = wglCreatePbufferARB(window, pixelFormat, width, height, 0))) {
    msSetError(MS_OGLERR, "P-buffer Error: Unable to create P-buffer. glError: %d", "OglContext::createPBuffer()", glGetError());
    return FALSE;
  }
  if (!(hPBufferDC = wglGetPbufferDCARB(hPBuffer))) {
    msSetError(MS_OGLERR, "P-buffer Error: Unable to get P-buffer DC. glError: %d", "OglContext::createPBuffer()", glGetError());
    return FALSE;
  }
  if (!(hPBufferRC = wglCreateContext(hPBufferDC))) {
    msSetError(MS_OGLERR, "P-buffer Error: Unable to get P-buffer DC. glError: %d", "OglContext::createPBuffer()", glGetError());
    return FALSE;
  }

  if (wglShareLists(sharingContext,hPBufferRC) == FALSE) {
    msSetError(MS_OGLERR, "P-buffer Error: Unable to share display lists. glError: %d", "OglContext::createPBuffer()", glGetError());
    return FALSE;
  }

  return TRUE;
}

PFNWGLCHOOSEPIXELFORMATARBPROC OglContext::wglChoosePixelFormatARB = NULL;
PFNWGLCREATEPBUFFERARBPROC OglContext::wglCreatePbufferARB = NULL;
PFNWGLGETPBUFFERDCARBPROC OglContext::wglGetPbufferDCARB = NULL;
PFNWGLRELEASEPBUFFERDCARBPROC OglContext::wglReleasePbufferDCARB = NULL;
PFNWGLMAKECONTEXTCURRENTARBPROC OglContext::wglMakeContextCurrentARB = NULL;

#else /* UNIX */

Display* OglContext::window = NULL;
GLXContext OglContext::sharingContext = NULL;
GLXFBConfig* OglContext::configs = NULL;

OglContext::~OglContext()
{
  //TODO
}


bool OglContext::makeCurrent()
{
  if (current != this) {
    current = this;
    if (!glXMakeCurrent(window, pbuffer, sharingContext)) {
      msSetError(MS_OGLERR, "glXMakeCurrent failed. glError: %d", "OglContext::makeCurrent()", glGetError());
      return false;
    }
  }
  return true;
}

bool OglContext::initSharingContext()
{
  int fb_attributes[] = {
    GLX_SAMPLES_ARB, MAX_MULTISAMPLE_SAMPLES,
    GLX_RENDER_TYPE, GLX_RGBA_BIT,
    GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
    GLX_RED_SIZE, 8,
    GLX_GREEN_SIZE, 8,
    GLX_BLUE_SIZE, 8,
    GLX_ALPHA_SIZE, 8,
    GLX_DEPTH_SIZE, 16,
    GLX_DOUBLEBUFFER, 0,
    GLX_SAMPLE_BUFFERS_ARB, 1,
    0
  };

  int num_configs = 0;
  while (num_configs == 0 && fb_attributes[1] >= 0) {
    configs = glXChooseFBConfig(window, DefaultScreen(window), fb_attributes, &num_configs);
    fb_attributes[1] -= 2;
  }

  if (configs == NULL || num_configs == 0) {
    msSetError(MS_OGLERR, "glXChooseFBConfig could not find any configs. Likely your video card or drivers are not supported. glError: %d", "OglContext::init()", glGetError());
    return false;
  }

  sharingContext = glXCreateNewContext(window, *configs, GLX_RGBA_TYPE, NULL, 1);
  if (sharingContext == NULL) {
    msSetError(MS_OGLERR, "glXCreateNewContext failed. glError: %d", "OglContext::initSharingContext()", glGetError());
    return false;
  }

  return true;
}

bool OglContext::createPBuffer(ms_uint32 width, ms_uint32 height)
{
  int maxHeight, maxWidth;

  glXGetFBConfigAttrib(window, *configs, GLX_MAX_PBUFFER_WIDTH, &maxWidth);
  glXGetFBConfigAttrib(window, *configs, GLX_MAX_PBUFFER_HEIGHT, &maxHeight);

  ms_uint32 uMaxHeight = maxHeight, uMaxWidth = maxWidth;

  this->width = MS_MIN(width, uMaxWidth);
  this->height = MS_MIN(height, uMaxHeight);

  int iPbufferAttributes[] = {
    GLX_PBUFFER_WIDTH, this->width,
    GLX_PBUFFER_HEIGHT, this->height,
    GLX_LARGEST_PBUFFER, false,
    0, 0
  };

  pbuffer = glXCreatePbuffer(window, *configs, iPbufferAttributes);
  if (pbuffer == 0) {
    msSetError(MS_OGLERR, "glXCreatePbuffer failed. glError: %d", "OglContext::init()", glGetError());
    return false;
  }

  return true;
}

bool OglContext::initWindow()
{
  const char* const display_name = getenv("DISPLAY");
  if (!display_name) {
    msSetError(MS_OGLERR, "DISPLAY environment variable not set", "OglContext::init()");
    return false;
  }

  window = XOpenDisplay(display_name);
  if (!window) {
    msSetError(MS_OGLERR, "XOpenDisplay() failed.", "OglContext::init()");
    return false;
  }

  const int fb_attributes[] = {
    GLX_RENDER_TYPE, GLX_RGBA_BIT,
    GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
    GLX_RED_SIZE, 8,
    GLX_GREEN_SIZE, 8,
    GLX_BLUE_SIZE, 8,
    GLX_ALPHA_SIZE, 8,
    GLX_DEPTH_SIZE, 16,
    GLX_DOUBLEBUFFER, 0,
    GLX_SAMPLE_BUFFERS_ARB, 1,
    0
  };

  int num_configs = 0;
  GLXFBConfig* tempConfigs = glXChooseFBConfig(window, DefaultScreen(window), fb_attributes, &num_configs);

  if (tempConfigs == NULL || num_configs == 0) {
    msSetError(MS_OGLERR, "glXChooseFBConfig could not find any configs. Likely your video card or drivers are not supported.", "OglContext::initWindow()");
    return false;
  }

  GLXContext tempContext = glXCreateNewContext(window, *tempConfigs, GLX_RGBA_TYPE, NULL, 1);
  if (tempContext == NULL) {
    msSetError(MS_OGLERR, "glXCreateNewContext failed.", "OglContext::initWindow()");
    return false;
  }

  int iPbufferAttributes[] =    {0, 0};

  GLXPbuffer tempBuffer = glXCreatePbuffer(window, *tempConfigs, iPbufferAttributes);
  if (tempBuffer == 0) {
    msSetError(MS_OGLERR, "glXCreatePbuffer failed.", "OglContext::initWindow()");
    return false;
  }

  if (!glXMakeCurrent(window, tempBuffer, tempContext)) {
    msSetError(MS_OGLERR, "glXMakeCurrent failed.", "OglContext::initWindow()");
    return false;
  }

  glGetIntegerv(GL_MAX_SAMPLES_EXT, (GLint*)&MAX_MULTISAMPLE_SAMPLES);
  glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*) &MAX_ANISOTROPY);
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&MAX_TEXTURE_SIZE);

  MIN_TEXTURE_SIZE = 8;

  return true;
}

#endif /* UNIX */

#endif /* USE_OGL */
