/**************************************************************************
 * 
 * Copyright 2010 VMware, Inc.
 * All Rights Reserved.
 * 
 * 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, sub license, 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 (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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.
 * 
 **************************************************************************/

#include <windows.h>

#define WGL_WGLEXT_PROTOTYPES

#include <GL/gl.h>
#include <GL/wglext.h>

#include "pipe/p_defines.h"
#include "pipe/p_screen.h"

#include "util/u_debug.h"

#include "stw_device.h"
#include "stw_pixelformat.h"
#include "stw_framebuffer.h"


#define LARGE_WINDOW_SIZE 60000


static LRESULT CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    MINMAXINFO *pMMI;
    switch (uMsg) {
    case WM_GETMINMAXINFO:
        // Allow to create a window bigger than the desktop
        pMMI = (MINMAXINFO *)lParam;
        pMMI->ptMaxSize.x = LARGE_WINDOW_SIZE;
        pMMI->ptMaxSize.y = LARGE_WINDOW_SIZE;
        pMMI->ptMaxTrackSize.x = LARGE_WINDOW_SIZE;
        pMMI->ptMaxTrackSize.y = LARGE_WINDOW_SIZE;
        break;
    default:
        break;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}


HPBUFFERARB WINAPI
wglCreatePbufferARB(HDC hCurrentDC,
                    int iPixelFormat,
                    int iWidth,
                    int iHeight,
                    const int *piAttribList)
{
   static boolean first = TRUE;
   const int *piAttrib;
   int useLargest = 0;
   const struct stw_pixelformat_info *info;
   struct stw_framebuffer *fb;
   DWORD dwExStyle;
   DWORD dwStyle;
   RECT rect;
   HWND hWnd;
   HDC hDC;
   int iDisplayablePixelFormat;
   PIXELFORMATDESCRIPTOR pfd;
   BOOL bRet;
   int textureFormat = WGL_NO_TEXTURE_ARB;
   int textureTarget = WGL_NO_TEXTURE_ARB;
   BOOL textureMipmap = FALSE;

   info = stw_pixelformat_get_info(iPixelFormat - 1);
   if (!info) {
      SetLastError(ERROR_INVALID_PIXEL_FORMAT);
      return 0;
   }

   if (iWidth <= 0 || iHeight <= 0) {
      SetLastError(ERROR_INVALID_DATA);
      return 0;
   }

   if (piAttribList) {
      for (piAttrib = piAttribList; *piAttrib; piAttrib++) {
         switch (*piAttrib) {
         case WGL_PBUFFER_LARGEST_ARB:
            piAttrib++;
            useLargest = *piAttrib;
            break;
          case WGL_TEXTURE_FORMAT_ARB:
             /* WGL_ARB_render_texture */
             piAttrib++;
             textureFormat = *piAttrib;
             if (textureFormat != WGL_TEXTURE_RGB_ARB &&
                textureFormat != WGL_TEXTURE_RGBA_ARB &&
                textureFormat != WGL_NO_TEXTURE_ARB) {
                SetLastError(ERROR_INVALID_DATA);
                return 0;
             }
             break;
          case WGL_TEXTURE_TARGET_ARB:
             /* WGL_ARB_render_texture */
             piAttrib++;
             textureTarget = *piAttrib;
             if (textureTarget != WGL_TEXTURE_CUBE_MAP_ARB &&
                 textureTarget != WGL_TEXTURE_1D_ARB &&
                 textureTarget != WGL_TEXTURE_2D_ARB &&
                 textureTarget != WGL_NO_TEXTURE_ARB) {
                SetLastError(ERROR_INVALID_DATA);
                return 0;
             }
             break;
         case WGL_MIPMAP_TEXTURE_ARB:
            /* WGL_ARB_render_texture */
            piAttrib++;
            textureMipmap = !!*piAttrib;
            break;
         default:
            SetLastError(ERROR_INVALID_DATA);
            debug_printf("wgl: Unsupported attribute 0x%x in %s\n",
                         *piAttrib, __func__);
            return 0;
         }
      }
   }

   if (iWidth > stw_dev->max_2d_length) {
      if (useLargest) {
         iWidth = stw_dev->max_2d_length;
      } else {
         SetLastError(ERROR_NO_SYSTEM_RESOURCES);
         return 0;
      }
   }

   if (iHeight > stw_dev->max_2d_length) {
      if (useLargest) {
         iHeight = stw_dev->max_2d_length;
      } else {
         SetLastError(ERROR_NO_SYSTEM_RESOURCES);
         return 0;
      }
   }

   /*
    * Implement pbuffers through invisible windows
    */

   if (first) {
      WNDCLASS wc;
      memset(&wc, 0, sizeof wc);
      wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
      wc.lpfnWndProc = WndProc;
      wc.lpszClassName = "wglpbuffer";
      wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
      RegisterClass(&wc);
      first = FALSE;
   }

   dwExStyle = 0;
   dwStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

   if (0) {
      /*
       * Don't hide the window -- useful for debugging what the application is
       * drawing
       */

      dwStyle |= WS_VISIBLE | WS_OVERLAPPEDWINDOW;
   } else {
      dwStyle |= WS_POPUPWINDOW;
   }

   rect.left = 0;
   rect.top = 0;
   rect.right = rect.left + iWidth;
   rect.bottom = rect.top + iHeight;

   /*
    * The CreateWindowEx parameters are the total (outside) dimensions of the
    * window, which can vary with Windows version and user settings.  Use
    * AdjustWindowRect to get the required total area for the given client area.
    *
    * AdjustWindowRectEx does not accept WS_OVERLAPPED style (which is defined
    * as 0), which means we need to use some other style instead, e.g.,
    * WS_OVERLAPPEDWINDOW or WS_POPUPWINDOW as above.
    */

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

   hWnd = CreateWindowEx(dwExStyle,
                         "wglpbuffer", /* wc.lpszClassName */
                         NULL,
                         dwStyle,
                         CW_USEDEFAULT, /* x */
                         CW_USEDEFAULT, /* y */
                         rect.right - rect.left, /* width */
                         rect.bottom - rect.top, /* height */
                         NULL,
                         NULL,
                         NULL,
                         NULL);
   if (!hWnd) {
      return 0;
   }

#ifdef DEBUG
   /*
    * Verify the client area size matches the specified size.
    */

   GetClientRect(hWnd, &rect);
   assert(rect.left == 0);
   assert(rect.top == 0);
   assert(rect.right - rect.left == iWidth);
   assert(rect.bottom - rect.top == iHeight);
#endif

   hDC = GetDC(hWnd);
   if (!hDC) {
      return 0;
   }

   /*
    * We can't pass non-displayable pixel formats to GDI, which is why we
    * create the framebuffer object before calling SetPixelFormat().
    */
   fb = stw_framebuffer_create(hDC, iPixelFormat);
   if (!fb) {
      SetLastError(ERROR_NO_SYSTEM_RESOURCES);
      return NULL;
   }

   fb->bPbuffer = TRUE;

   /* WGL_ARB_render_texture fields */
   fb->textureTarget = textureTarget;
   fb->textureFormat = textureFormat;
   fb->textureMipmap = textureMipmap;

   iDisplayablePixelFormat = fb->iDisplayablePixelFormat;

   stw_framebuffer_unlock(fb);

   /*
    * We need to set a displayable pixel format on the hidden window DC
    * so that wglCreateContext and wglMakeCurrent are not overruled by GDI.
    */
   bRet = SetPixelFormat(hDC, iDisplayablePixelFormat, &pfd);
   assert(bRet);

   return (HPBUFFERARB)fb;
}


HDC WINAPI
wglGetPbufferDCARB(HPBUFFERARB hPbuffer)
{
   struct stw_framebuffer *fb;
   HDC hDC;

   if (!hPbuffer) {
      SetLastError(ERROR_INVALID_HANDLE);
      return NULL;
   }

   fb = stw_framebuffer_from_HPBUFFERARB(hPbuffer);

   hDC = GetDC(fb->hWnd);

   return hDC;
}


int WINAPI
wglReleasePbufferDCARB(HPBUFFERARB hPbuffer,
                       HDC hDC)
{
   struct stw_framebuffer *fb;

   if (!hPbuffer) {
      SetLastError(ERROR_INVALID_HANDLE);
      return 0;
   }

   fb = stw_framebuffer_from_HPBUFFERARB(hPbuffer);

   return ReleaseDC(fb->hWnd, hDC);
}


BOOL WINAPI
wglDestroyPbufferARB(HPBUFFERARB hPbuffer)
{
   struct stw_framebuffer *fb;

   if (!hPbuffer) {
      SetLastError(ERROR_INVALID_HANDLE);
      return FALSE;
   }

   fb = stw_framebuffer_from_HPBUFFERARB(hPbuffer);

   /* This will destroy all our data */
   return DestroyWindow(fb->hWnd);
}


BOOL WINAPI
wglQueryPbufferARB(HPBUFFERARB hPbuffer,
                   int iAttribute,
                   int *piValue)
{
   struct stw_framebuffer *fb;

   if (!hPbuffer) {
      SetLastError(ERROR_INVALID_HANDLE);
      return FALSE;
   }

   fb = stw_framebuffer_from_HPBUFFERARB(hPbuffer);

   switch (iAttribute) {
   case WGL_PBUFFER_WIDTH_ARB:
      *piValue = fb->width;
      return TRUE;
   case WGL_PBUFFER_HEIGHT_ARB:
      *piValue = fb->height;
      return TRUE;
   case WGL_PBUFFER_LOST_ARB:
      /* We assume that no content is ever lost due to display mode change */
      *piValue = FALSE;
      return TRUE;
   /* WGL_ARB_render_texture */
   case WGL_TEXTURE_TARGET_ARB:
      *piValue = fb->textureTarget;
      return TRUE;
   case WGL_TEXTURE_FORMAT_ARB:
      *piValue = fb->textureFormat;
      return TRUE;
   case WGL_MIPMAP_TEXTURE_ARB:
      *piValue = fb->textureMipmap;
      return TRUE;
   case WGL_MIPMAP_LEVEL_ARB:
      *piValue = fb->textureLevel;
      return TRUE;
   case WGL_CUBE_MAP_FACE_ARB:
      *piValue = fb->textureFace + WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
      return TRUE;
   default:
      SetLastError(ERROR_INVALID_DATA);
      return FALSE;
   }
}