/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2014 Pierre Ossman for Cendio AB
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#include <rfb_win32/DeviceContext.h>
#include <rfb_win32/CompatibleBitmap.h>
#include <rfb_win32/BitmapInfo.h>
#include <rdr/Exception.h>
#include <rfb/LogWriter.h>

using namespace rfb;
using namespace win32;


static LogWriter vlog("DeviceContext");

PixelFormat DeviceContext::getPF() const {
  return getPF(dc);
}

PixelFormat DeviceContext::getPF(HDC dc) {
  bool trueColour, bigEndian;
  int bpp, depth;
  int redMax, greenMax, blueMax;
  int redShift, greenShift, blueShift;

  CompatibleBitmap bitmap(dc, 1, 1);

  // -=- Get the bitmap format information
  BitmapInfo bi;
  memset(&bi, 0, sizeof(bi));
  bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bi.bmiHeader.biBitCount = 0;
  if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
    throw rdr::SystemException("unable to determine device pixel format", GetLastError());
  }
  if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
    throw rdr::SystemException("unable to determine pixel shifts/palette", GetLastError());
  }

  // Set the initial format information
  trueColour = bi.bmiHeader.biBitCount > 8;
  bigEndian = 0;
  bpp = bi.bmiHeader.biBitCount;

  if (trueColour) {
    DWORD rMask=0, gMask=0, bMask=0;

    // Which true colour format is the DIB section using?
    switch (bi.bmiHeader.biCompression) {
    case BI_RGB:
      // Default RGB layout
      switch (bi.bmiHeader.biBitCount) {
      case 16:
        // RGB 555 - High Colour
        vlog.info("16-bit High Colour");
        rMask = 0x7c00;
        bMask = 0x001f;
        gMask = 0x03e0;
        break;
      case 24:
      case 32:
        // RGB 888 - True Colour
        vlog.info("24/32-bit High Colour");
        rMask = 0xff0000;
        gMask = 0x00ff00;
        bMask = 0x0000ff;
        break;
      default:
        vlog.error("bits per pixel %u not supported", bi.bmiHeader.biBitCount);
        throw rdr::Exception("unknown bits per pixel specified");
      };
      break;
    case BI_BITFIELDS:
      // Custom RGB layout
      rMask = bi.mask.red;
      gMask = bi.mask.green;
      bMask = bi.mask.blue;
      vlog.info("%d-bit BitFields: (%lx, %lx, %lx)",
                 bi.bmiHeader.biBitCount, rMask, gMask, bMask);
      break;
    };

    // Convert the data we just retrieved
    initMaxAndShift(rMask, &redMax, &redShift);
    initMaxAndShift(gMask, &greenMax, &greenShift);
    initMaxAndShift(bMask, &blueMax, &blueShift);

    // Calculate the depth from the colour shifts
    depth = 0;
    Pixel bits = rMask | gMask | bMask;
    while (bits) {
      depth++;
      bits = bits >> 1;
    }

    // Check that the depth & bpp are valid
    if (depth > bpp) {
      vlog.error("depth exceeds bits per pixel!");
      bpp = depth;
    }

    // Correct the bits-per-pixel to something we're happy with
    if (bpp <= 16)
      bpp = 16;
    else if (bpp <= 32)
      bpp = 32;
  } else {
    // Palettised format - depth reflects number of colours,
    // but bits-per-pixel is ALWAYS 8
    depth = bpp;
    if (bpp < 8)
      bpp = 8;
    vlog.info("%d-colour palettised", 1<<depth);
    // Aren't really used, but set them to keep the compiler happy
    redMax = redShift = 0;
    greenMax = greenShift = 0;
    blueMax = blueShift = 0;
  }


  return PixelFormat(bpp, depth, bigEndian, trueColour,
		                 redMax, greenMax, blueMax,
		                 redShift, greenShift, blueShift);
}

Rect DeviceContext::getClipBox() const {
  return getClipBox(dc);
}

Rect DeviceContext::getClipBox(HDC dc) {
  // Get the display dimensions
  RECT cr;
  if (!GetClipBox(dc, &cr))
    throw rdr::SystemException("GetClipBox", GetLastError());
  return Rect(cr.left, cr.top, cr.right, cr.bottom);
}


DeviceDC::DeviceDC(const TCHAR* deviceName) {
  dc = ::CreateDC(_T("DISPLAY"), deviceName, NULL, NULL);
  if (!dc)
    throw rdr::SystemException("failed to create DeviceDC", GetLastError());
}

DeviceDC::~DeviceDC() {
  if (dc)
    DeleteDC(dc);
}


WindowDC::WindowDC(HWND wnd) : hwnd(wnd) {
  dc = GetDC(wnd);
  if (!dc)
    throw rdr::SystemException("GetDC failed", GetLastError());
}

WindowDC::~WindowDC() {
  if (dc)
    ReleaseDC(hwnd, dc);
}


CompatibleDC::CompatibleDC(HDC existing) {
  dc = CreateCompatibleDC(existing);
  if (!dc)
    throw rdr::SystemException("CreateCompatibleDC failed", GetLastError());
}

CompatibleDC::~CompatibleDC() {
  if (dc)
    DeleteDC(dc);
}


BitmapDC::BitmapDC(HDC hdc, HBITMAP hbitmap) : CompatibleDC(hdc){
  oldBitmap = (HBITMAP)SelectObject(dc, hbitmap);
  if (!oldBitmap)
    throw rdr::SystemException("SelectObject to CompatibleDC failed",
    GetLastError());
}

BitmapDC::~BitmapDC() {
  SelectObject(dc, oldBitmap);
}