
#include "stdafx.h"
#include <windows.h>

/**
 * @file  mdc_canvas_view_windows.cpp
 * @brief 
 */

#include "mdc_canvas_view_windows.h"
#include "mdc_canvas_view_image.h"
#include "mdc_rectangle.h"

#include <cairo-win32.h>

#ifndef GL_BGRA
#define GL_BGRA GL_BGRA_EXT
#endif

using namespace mdc;


WindowsGLCanvasView::WindowsGLCanvasView(HWND window, int width, int height)
  : OpenGLCanvasView(width, height), _window(window), _glrc(0), _hdc(0)
{
}

WindowsGLCanvasView::~WindowsGLCanvasView()
{
  ReleaseDC(_window, _hdc);
}


bool WindowsGLCanvasView::initialize()
{
  _hdc= GetWindowDC(_window);

  // setup pixel format

  PIXELFORMATDESCRIPTOR pfd = { 
    sizeof(PIXELFORMATDESCRIPTOR),    // size of this pfd 
    1,                                // version number 
    PFD_DRAW_TO_WINDOW |              // support window 
    PFD_SUPPORT_OPENGL |              // support OpenGL 
    // PFD_SUPPORT_GDI |                 // support GDI
    PFD_DOUBLEBUFFER,                 // double buffered 
    PFD_TYPE_RGBA,                    // RGBA type 
    32,                               // color with alpha
    0, 0, 0, 0, 0, 0,                 // no explicit color bits assignment
    0, 0,                             // no explicit alpha bits assignment
    0,                                // no accumulation buffer 
    0, 0, 0, 0,                       // accum bits ignored 
    0,                                // no z-buffer (depth testing disabled for 2D drawing)
    0,                                // no stencil buffer 
    0,                                // no auxiliary buffer 
    PFD_MAIN_PLANE,                   // main layer 
    0,                                // reserved 
    0, 0, 0                           // layer masks ignored 
  }; 
  int  iPixelFormat; 
   
  // get the device context's best, available pixel format match 
  iPixelFormat= ChoosePixelFormat(_hdc, &pfd); 
   
  // make that match the device context's current pixel format 
  SetPixelFormat(_hdc, iPixelFormat, &pfd); 

  // create context
  _glrc= wglCreateContext(_hdc);
  if (!_glrc)
  {
    throw canvas_error("could not initialize WGL context");
  }

  make_current();

  _crsurface= cairo_win32_surface_create(_hdc);

  _cairo= new CairoCtx(_crsurface);
  cairo_set_tolerance(_cairo->get_cr(), 0.1);

  update_offsets();
  set_needs_repaint();

  bool result= OpenGLCanvasView::initialize();
  remove_current();

  return result;
}

//----------------------------------------------------------------------------------------------------------------------

void WindowsGLCanvasView::make_current()
{
  wglMakeCurrent(_hdc, _glrc);
}

//----------------------------------------------------------------------------------------------------------------------

void WindowsGLCanvasView::remove_current()
{
  wglMakeCurrent(0, 0);
}

//----------------------------------------------------------------------------------------------------------------------

void WindowsGLCanvasView::swap_buffers()
{
  SwapBuffers(_hdc);
}

//----------------------------------------------------------------------------------------------------------------------

void WindowsGLCanvasView::update_view_size(int width, int height)
{
   if (_view_width != width || _view_height != height)
  {
    _view_width= width;
    _view_height= height;

    delete _cairo;
    if (_crsurface)
      cairo_surface_destroy(_crsurface);

    _crsurface= cairo_win32_surface_create(_hdc);

    _cairo= new CairoCtx(_crsurface);
    cairo_set_tolerance(_cairo->get_cr(), 0.1);

    update_offsets();
    set_needs_repaint();

    _viewport_changed_signal.emit();
  }
}



//-------------------------------------------------------------------------------------


WindowsCanvasView::WindowsCanvasView(HWND window, int width, int height)
  : CanvasView(width, height), _window(window)
{
  _hdc= 0;
  _crsurface= 0;

  // a surface used to get a cairo context outside of a begin_paint/end_paint block
  _offline_surface= cairo_win32_surface_create_with_dib(CAIRO_FORMAT_RGB24, 1, 1);
  _cairo= new CairoCtx(_offline_surface);
}


WindowsCanvasView::WindowsCanvasView(HDC context, int width, int height)
: CanvasView(width, height), _window(0)
{
  _hdc= context;
  _crsurface= 0;

  // a surface used to get a cairo context outside of a begin_paint/end_paint block
  _offline_surface= cairo_win32_surface_create_with_dib(CAIRO_FORMAT_RGB24, 1, 1);
  _cairo= new CairoCtx(_offline_surface);
}



WindowsCanvasView::~WindowsCanvasView()
{
  delete _cairo;

  if (_offline_surface)
    cairo_surface_destroy(_offline_surface);

  if (_crsurface)
    cairo_surface_destroy(_crsurface);

  if (_window && _hdc)
    ReleaseDC(_window, _hdc);
}


bool WindowsCanvasView::initialize()
{
  return CanvasView::initialize();
}


void WindowsCanvasView::update_view_size(int width, int height)
{
  if (_view_width != width || _view_height != height)
  {
    _view_width= width;
    _view_height= height;

    update_offsets();
    set_needs_repaint();

    _viewport_changed_signal.emit();
  }
}


void WindowsCanvasView::begin_repaint(int x, int y, int w, int h)
{
  if (_window)
    BeginPaint(_window, &_paint);

  delete _cairo;

  if (_crsurface)
    cairo_surface_destroy(_crsurface);

  if (_window)
  {
    if (_hdc)
      ReleaseDC(_window, _hdc);
    _hdc= GetWindowDC(_window);
  }

  _crsurface= cairo_win32_surface_create(_hdc);

  _cairo= new CairoCtx(_crsurface);
  cairo_set_tolerance(_cairo->get_cr(), 0.1);
}


void WindowsCanvasView::end_repaint()
{
  if (_window)
  {
    EndPaint(_window, &_paint);

    ReleaseDC(_window, _hdc);
    _hdc= 0;
  }

  _cairo= new CairoCtx(_offline_surface);
}



//-------------------------------------------------------------------------------------


BufferedWindowsCanvasView::BufferedWindowsCanvasView(HWND window, int width, int height)
  : CanvasView(width, height), _window(window)
{
  _hdc= GetWindowDC(window);

  _crsurface= cairo_win32_surface_create_with_ddb(_hdc, CAIRO_FORMAT_RGB24, 
      _view_width, _view_height);

  _cairo= new CairoCtx(_crsurface);
  cairo_set_tolerance(_cairo->get_cr(), 0.1);
}


BufferedWindowsCanvasView::~BufferedWindowsCanvasView()
{
  delete _cairo;

  if (_crsurface)
    cairo_surface_destroy(_crsurface);

  if (_hdc)
    ReleaseDC(_window, _hdc);
}


bool BufferedWindowsCanvasView::initialize()
{
  return CanvasView::initialize();
}


void BufferedWindowsCanvasView::update_view_size(int width, int height)
{
  if (_view_width != width || _view_height != height)
  {
    _view_width= width;
    _view_height= height;

    delete _cairo;
    if (_crsurface)
      cairo_surface_destroy(_crsurface);
    
    if (_hdc)
      ReleaseDC(_window, _hdc);
    _hdc= GetWindowDC(_window);

    _crsurface= cairo_win32_surface_create_with_ddb(_hdc, CAIRO_FORMAT_RGB24, 
              _view_width, _view_height);

    _cairo= new CairoCtx(_crsurface);
    cairo_set_tolerance(_cairo->get_cr(), 0.1);


    update_offsets();
    set_needs_repaint();

    _viewport_changed_signal.emit();
  }
}


void BufferedWindowsCanvasView::scroll_to(const Point &offs)
{
  Point new_offset;
  Size viewable_size(get_viewable_size());
  Size total_size(get_total_view_size());

  new_offset= offs.round();
  new_offset.x= std::max(0.0, std::min(new_offset.x, total_size.width - viewable_size.width));
  new_offset.y= std::max(0.0, std::min(new_offset.y, total_size.height - viewable_size.height));

  if (new_offset != _offset)
  {
    Rect vrefresh, hrefresh;
    Rect copy_area;
    Point copy_target;
    bool blit= false;

    copy_target= _offset;
    copy_area.pos= _offset;
    copy_area.size= viewable_size;

    if (new_offset.x > _offset.x)
    {
      if (new_offset.x-_offset.x < viewable_size.width)
      {
        copy_area.pos.x= new_offset.x;
        copy_area.size.width= _offset.x + viewable_size.width - new_offset.x;
        copy_target.x= _offset.x;
        blit= true;

        vrefresh.pos.x= copy_area.xmax();
        vrefresh.pos.y= _offset.y;
        vrefresh.size.width= viewable_size.width - copy_area.width();
        vrefresh.size.height= viewable_size.height;
      }
    }
    else if (new_offset.x < _offset.x)
    {
      if (_offset.x-new_offset.x < viewable_size.width)
      {
        copy_area.pos.x= _offset.x;
        copy_area.size.width= viewable_size.width - (_offset.x - new_offset.x);
        copy_target.x= _offset.x + (_offset.x - new_offset.x);
        blit= true;

        vrefresh.pos.x= new_offset.x;
        vrefresh.pos.y= _offset.y;
        vrefresh.size.width= viewable_size.width - copy_area.width();
        vrefresh.size.height= viewable_size.height;
      }
    }

    if (new_offset.y > _offset.y)
    {
      if (new_offset.y-_offset.y < viewable_size.height)
      {
        copy_area.pos.y= new_offset.y;
        copy_area.size.height= _offset.y + viewable_size.height - new_offset.y;
        copy_target.y= _offset.y;
        blit= true;

        hrefresh.pos.y= copy_area.ymax();
        hrefresh.pos.x= _offset.x;
        hrefresh.size.height= viewable_size.height - copy_area.height();
        hrefresh.size.width= viewable_size.width;
      }
    }
    else if (new_offset.y < _offset.y)
    {
      if (_offset.y-new_offset.y < viewable_size.height)
      {
        copy_area.pos.y= _offset.y;
        copy_area.size.height= viewable_size.height - (_offset.y - new_offset.y);
        copy_target.y= _offset.y + (_offset.y - new_offset.y);
        blit= true;

        hrefresh.pos.y= new_offset.y;
        hrefresh.pos.x= _offset.x;
        hrefresh.size.height= viewable_size.height - copy_area.height();
        hrefresh.size.width= viewable_size.width;
      }
    }

    if (blit)
    {
      int x, y, w, h;
      int tx, ty;

      canvas_to_window(copy_area, x, y, w, h);
      canvas_to_window(copy_target, tx, ty);

      BitBlt(_hdc, tx, ty, w, h, _hdc, x, y, SRCCOPY);

      _offset= new_offset;

      if (hrefresh.width() > 0 && hrefresh.height() > 0)
      {
        canvas_to_window(hrefresh, x, y, w, h);
        
        //char s[1000];
        //sprintf(s, "H %s %i %i %i %i\n", hrefresh.str().c_str(), x, y, w, h);
        //OutputDebugStringA(s);

        repaint_area(hrefresh, x, y, w, h);
      }
      if (vrefresh.width() > 0 && vrefresh.height() > 0)
      {
        canvas_to_window(vrefresh, x, y, w, h);

        //char s[1000];
        //sprintf(s, "V (%s) %i %i %i %i\n", vrefresh.str().c_str(), x, y, w, h);
        //OutputDebugStringA(s);

        repaint_area(vrefresh, x, y, w, h);
      }
    }
    else
    {
      _offset= new_offset;
      set_needs_repaint();
    }

    update_offsets();

    _viewport_changed_signal.emit();
  }
}


Surface *BufferedWindowsCanvasView::create_temp_surface(const Size &size) const
{
  cairo_surface_t *surf= cairo_win32_surface_create_with_ddb(_hdc, CAIRO_FORMAT_ARGB32, 
    (int)size.width, (int)size.height);
  Surface *s= new Surface(surf);
  cairo_surface_destroy(surf);

  return s;
}


void BufferedWindowsCanvasView::begin_repaint(int x, int y, int w, int h)
{
  _clip_x= x;
  _clip_y= y;
  _clip_w= w;
  _clip_h= h;

  BeginPaint(_window, &_paint);
}


void BufferedWindowsCanvasView::end_repaint()
{
  BitBlt(_hdc, _clip_x, _clip_y, _clip_w, _clip_h,
    cairo_win32_surface_get_dc(_crsurface),
    _clip_x, _clip_y, SRCCOPY);

  EndPaint(_window, &_paint);
}


//-------------------------------------------------------------------------------------

/*
static LPDIRECTDRAW _ddraw= 0;

DDBufferedWindowsCanvasView::DDBufferedWindowsCanvasView(HWND window, int width, int height)
: CanvasView(width, height), _window(window)
{
  if (!_ddraw)
  {
    if (DirectDrawCreate(NULL, &_ddraw, NULL) != DD_OK)
      throw std::runtime_error("Could not initialize DirectDraw");
  }

  if (_ddraw->SetCooperativeLevel(window, DDSCL_NORMAL) != DD_OK)
    throw std::runtime_error("Error on SetCooperativeLevel for DirectDraw");
}


DDBufferedWindowsCanvasView::~DDBufferedWindowsCanvasView()
{
}


bool DDBufferedWindowsCanvasView::initialize()
{
  HRESULT ddres;
  DDSURFACEDESC ddsd;
  DDSCAPS ddsc;

  memset(&ddsd, 0, sizeof(ddsd));
  ddsd.dwSize= sizeof(ddsd);
  ddsd.dwFlags= DDSD_CAPS;
  ddsd.ddsCaps.dwCaps= DDSCAPS_PRIMARYSURFACE;

  ddres= _ddraw->CreateSurface(&ddsd, &_ddsurface, NULL);
  if (ddres != DD_OK)
    throw std::runtime_error("Error creating DirectDraw surface");

  ddres= _ddraw->CreateClipper(0, &_clipper, NULL);
  if (ddres != DD_OK)
  {
    _ddsurface->Release();
    _ddsurface= 0;
    throw std::runtime_error("Error creating DirectDraw clipper");
  }

  if (_clipper->SetHWnd(0, _window) != DD_OK)
  {
    _ddsurface->Release();
    _ddsurface= 0;
    _clipper->Release();
    _clipper= 0;
    throw std::runtime_error("Error creating DirectDraw clipper");
  }

  if (_ddsurface->SetClipper(_clipper) != DD_OK)
  {
     _ddsurface->Release();
    _ddsurface= 0;
    _clipper->Release();
    _clipper= 0;
    throw std::runtime_error("Error creating DirectDraw clipper");
  }

  memset(&ddsd, 0, sizeof(ddsd));
  ddsd.dwSize= sizeof(ddsd);
  ddsd.dwFlags= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
  ddsd.ddsCaps.dwCaps= DDSCAPS_OFFSCREENPLAIN;
  ddsd.dwWidth= _view_width;
  ddsd.dwHeight= _view_height;

  if (_ddraw->CreateSurface(&ddsd, &_ddback, NULL) != DD_OK)
  {
     _ddsurface->Release();
    _ddsurface= 0;
    _clipper->Release();
    _clipper= 0;
    throw std::runtime_error("Error creating DirectDraw backbuffer");
  }



  return CanvasView::initialize();
}


void DDBufferedWindowsCanvasView::update_view_size(int width, int height)
{
  
}


void DDBufferedWindowsCanvasView::begin_repaint(int x, int y, int w, int h)
{
}


void DDBufferedWindowsCanvasView::end_repaint()
{
  RECT srect, drect;
  POINT p;

  p.x= 0;
  p.y= 0;
  ClientToScreen(_window, &p);
  GetClientRect(_window, &drect);
  OffsetRect(&drect, p.x, p.y);
  SetRect(&srect, 0, 0, _view_width, _view_height);
  if (_ddsurface->Blt(&drect, _ddback, &srect, DDBLT_WAIT, NULL) != DD_OK)
    OutputDebugStringA("Error blitting into DirectDraw surface\n");
}
*/

//-------------------------------------------------------------------------------------







// Following code based on:
/*
 * Copyright © 2004 David Reveman
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the names of
 * David Reveman not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * David Reveman makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * DAVID REVEMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL DAVID REVEMAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Based on glx code by David Reveman
 *
 * Contributors:
 *  Tor Lillqvist <tml@iki.fi>
 *  Vladimir Vukicevic <vladimir@pobox.com>
 *
 */



static int ChoosePixelFormatEx(HDC hdc,int *p_bpp,int *p_depth,int *p_dbl,int *p_acc)
{ int wbpp; if (p_bpp==NULL) wbpp=-1; else wbpp=*p_bpp;
  int wdepth; if (p_depth==NULL) wdepth=16; else wdepth=*p_depth;
  int wdbl; if (p_dbl==NULL) wdbl=-1; else wdbl=*p_dbl;
  int wacc; if (p_acc==NULL) wacc=1; else wacc=*p_acc;
  PIXELFORMATDESCRIPTOR pfd; ZeroMemory(&pfd,sizeof(pfd)); pfd.nSize=sizeof(pfd); pfd.nVersion=1;
  int num=DescribePixelFormat(hdc,1,sizeof(pfd),&pfd);
  if (num==0) return 0;
  unsigned int maxqual=0; int maxindex=0;
  int max_bpp, max_depth, max_dbl, max_acc;
  for (int i=1; i<=num; i++)
  { ZeroMemory(&pfd,sizeof(pfd)); pfd.nSize=sizeof(pfd); pfd.nVersion=1;
    DescribePixelFormat(hdc,i,sizeof(pfd),&pfd);
    int bpp=pfd.cColorBits;
    int depth=pfd.cDepthBits;
    bool pal=(pfd.iPixelType==PFD_TYPE_COLORINDEX);
    bool mcd=((pfd.dwFlags & PFD_GENERIC_FORMAT) && (pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool soft=((pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool icd=(!(pfd.dwFlags & PFD_GENERIC_FORMAT) && !(pfd.dwFlags & PFD_GENERIC_ACCELERATED));
    bool opengl=(pfd.dwFlags & PFD_SUPPORT_OPENGL)!=0;
    bool window=(pfd.dwFlags & PFD_DRAW_TO_WINDOW)!=0;
    bool bitmap=(pfd.dwFlags & PFD_DRAW_TO_BITMAP)!=0;
    bool dbuff=(pfd.dwFlags & PFD_DOUBLEBUFFER)!=0;
    //
    unsigned int q=0;
    if (opengl && window) q=q+0x8000;
    if (wdepth==-1 || (wdepth>0 && depth>0)) q=q+0x4000;
    if (wdbl==-1 || (wdbl==0 && !dbuff) || (wdbl==1 && dbuff)) q=q+0x2000;
    if (wacc==-1 || (wacc==0 && soft) || (wacc==1 && (mcd || icd))) q=q+0x1000;
    if (mcd || icd) q=q+0x0040; if (icd) q=q+0x0002;
    if (wbpp==-1 || (wbpp==bpp)) q=q+0x0800;
    if (bpp>=16) q=q+0x0020; if (bpp==16) q=q+0x0008;
    if (wdepth==-1 || (wdepth==depth)) q=q+0x0400;
    if (depth>=16) q=q+0x0010; if (depth==16) q=q+0x0004;
    if (!pal) q=q+0x0080;
    if (bitmap) q=q+0x0001;
    if (q>maxqual) {maxqual=q; maxindex=i;max_bpp=bpp; max_depth=depth; max_dbl=dbuff?1:0; max_acc=soft?0:1;}
  }
  if (maxindex==0) return maxindex;
  if (p_bpp!=NULL) *p_bpp=max_bpp;
  if (p_depth!=NULL) *p_depth=max_depth;
  if (p_dbl!=NULL) *p_dbl=max_dbl;
  if (p_acc!=NULL) *p_acc=max_acc;
  return maxindex;
}


std::string mdc::detect_opengl_version()
{
  std::string version;

  WNDCLASSEX wcl;
  ATOM klass;

  static PIXELFORMATDESCRIPTOR pfd = {
    sizeof (PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL,
    PFD_TYPE_RGBA,
    32,
    0, 0, 0,
    0, 0, 0,
    0, 0,
    0, 0, 0, 0, 0,
    0,
    0,
    0,
    PFD_MAIN_PLANE,
    0,
    0,
    0,
    0
  };

  int pixel_format;

  wcl.cbSize = sizeof (wcl);
  wcl.style = 0;
  wcl.lpfnWndProc = DefWindowProc;
  wcl.cbClsExtra = 0;
  wcl.cbWndExtra = 0;
  wcl.hInstance = GetModuleHandle (NULL);
  wcl.hIcon = NULL;
  wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
  wcl.hbrBackground = NULL;
  wcl.lpszMenuName = NULL;
  wcl.lpszClassName = L"glitz-wgl-root-window-class"; //!
  wcl.hIconSm = NULL;

  klass = RegisterClassEx (&wcl);

  if (!klass)
    return "";

  HWND root_window =
	CreateWindowEx (0, (LPCTSTR) (DWORD) klass, L"glitz-wgl-root-window", //!
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT,
      100, 100,
      GetDesktopWindow (),
      NULL, GetModuleHandle (NULL), NULL);

  HDC root_dc = GetDC (root_window);

  {
    int bpp = -1; // don't care. (or a positive integer)
    int depth = -1; // don't care. (or a positive integer)
    int dbl = -1; // we want double-buffering. (or -1 for 'don't care', or 0 for 'none')
    int acc = 1; // we want acceleration. (or -1 or 0)
    pixel_format = ChoosePixelFormatEx (root_dc, &bpp, &depth, &dbl, &acc);
  }

  if (pixel_format != 0) 
  {
    SetPixelFormat (root_dc, pixel_format, &pfd);

    HGLRC root_context = wglCreateContext (root_dc);

    wglMakeCurrent (root_dc, root_context);

    version = glGetString(GL_VERSION) ? (const char*)glGetString(GL_VERSION) : "";

    wglMakeCurrent (NULL, NULL);

    wglDeleteContext(root_context);
  }


  if (root_window && root_dc)
    ReleaseDC(root_window, root_dc);

  if (root_window)
    DestroyWindow(root_window);

  return version;
}
