/* WIN32GState - Implements graphic state drawing for MSWindows

   Copyright (C) 2002 Free Software Foundation, Inc.

   Written by: Fred Kiefer <FredKiefer@gmx.de>
   Additions by: Christopher Armstrong <carmstrong@fastmail.com.au>

   Date: March 2002
   
   This file is part of the GNU Objective C User Interface Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; see the file COPYING.LIB.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/

// Currently the use of alpha blending is switched off by default.
#define USE_ALPHABLEND

// Define this so we pick up AlphaBlend, when loading windows.h
#ifdef USE_ALPHABLEND
#if	!defined(WINVER)
#define WINVER 0x0500
#elif (WINVER < 0x0500)
#undef	WINVER
#define WINVER 0x0500
#endif
#endif

#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>

#import <AppKit/NSAffineTransform.h>
#import <AppKit/NSBezierPath.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSFont.h>
#import <AppKit/NSGraphics.h>

#import "winlib/WIN32GState.h"
#import "winlib/WIN32Context.h"
#import "winlib/WIN32FontInfo.h"
#import "win32/WIN32Server.h"

#include <math.h>
#include <limits.h>

#ifndef AC_SRC_ALPHA
// Missing definitions from wingdi.h
#define AC_SRC_ALPHA    0x01
#endif

static inline
int WindowHeight(HWND window)
{
  RECT rect;

  if (!window)
    {
      NSLog(@"No window for coordinate transformation.");
      return 0;
    }

  if (!GetClientRect(window, &rect))
    {
      NSLog(@"No window rectangle for coordinate transformation.");
      return 0;
    }

  return rect.bottom - rect.top;
}

static inline
POINT GSWindowPointToMS(WIN32GState *s, NSPoint p)
{
  POINT p1;

  p1.x = p.x - s->offset.x;
  p1.y = s->offset.y - p.y;
  
  return p1;
}

static inline
RECT GSWindowRectToMS(WIN32GState *s, NSRect r)
{
  RECT r1;

  r1.left = r.origin.x - s->offset.x;
  r1.bottom = s->offset.y - r.origin.y;
  r1.right = r1.left + r.size.width;
  r1.top = r1.bottom - r.size.height;

  return r1;
}

static inline
POINT GSViewPointToWin(WIN32GState *s, NSPoint p)
{
  p = [s->ctm transformPoint: p];
  return GSWindowPointToMS(s, p);
}

static inline
RECT GSViewRectToWin(WIN32GState *s, NSRect r)
{
  r = [s->ctm rectInMatrixSpace: r];
  return GSWindowRectToMS(s, r);
}

void* get_bits(HDC dc,
	       int w,
	       int h,
	       HBITMAP *bitmap)
{
  void          *bits = NULL;
  BITMAPINFO     info;
  HDC            cDC;

  info.bmiHeader.biSize = sizeof(BITMAPINFO);
  info.bmiHeader.biWidth = w;
  info.bmiHeader.biHeight = h;
  info.bmiHeader.biPlanes = 1;
  info.bmiHeader.biBitCount = 32;
  info.bmiHeader.biCompression = BI_RGB;
  info.bmiHeader.biSizeImage = 0;
  info.bmiHeader.biXPelsPerMeter = 0;
  info.bmiHeader.biYPelsPerMeter = 0;
  info.bmiHeader.biClrUsed = 0;
  info.bmiHeader.biClrImportant = 0;

  if(!(cDC = CreateCompatibleDC(dc)))
    {
      NSLog(@"Could not create compatible DC");
      return NULL;
    }
  
  if(!(*bitmap = CreateDIBSection(dc, (LPBITMAPINFO)&info, 
				  DIB_RGB_COLORS, &bits, 
				  NULL, 0)))
    {
      NSLog(@"Could not create bit map from DC");
      return NULL;
    }
  
  SelectObject(cDC, *bitmap);
  
  return bits;
}

BOOL alpha_blend_source_over(HDC destDC, 
			     HDC srcDC, 
			     RECT rectFrom, 
			     int x, int y, int w, int h, 
			     float delta)
{
  BOOL success = YES;

#ifdef USE_ALPHABLEND
  // Use (0..1) fraction to set a (0..255) alpha constant value
  BYTE SourceConstantAlpha = (BYTE)(delta * 255);
  BLENDFUNCTION blendFunc
    = {AC_SRC_OVER, 0, SourceConstantAlpha, AC_SRC_ALPHA};

  /* There is actually a very real chance this could fail, even on 
     computers that supposedly support it. It's not known why it
     fails though... */
  success = AlphaBlend(destDC,
		       x, y, w, h,
		       srcDC,
		       rectFrom.left, rectFrom.top,
		       w, h, blendFunc);
  // #else
  // HBITMAP    sbitmap;
  // HBITMAP    dbitmap; 
  // unsigned char *sbits = (unsigned char *)get_bits(srcDC,w,h,&sbitmap);
  // unsigned char *dbits = (unsigned char *)get_bits(destDC,w,h,&dbitmap);

#endif

  return success;
}

@interface WIN32GState (WinOps)
- (void) setStyle: (HDC)hDC;
- (void) restoreStyle: (HDC)hDC;
- (HDC) getHDC;
- (void) releaseHDC: (HDC)hDC;
@end

@implementation WIN32GState 

- (id) deepen
{
  [super deepen];

  if (clipRegion)
    {
      HRGN newClipRegion;

      newClipRegion = CreateRectRgn(0, 0, 1, 1);
      CombineRgn(newClipRegion, clipRegion, NULL, RGN_COPY);
      clipRegion = newClipRegion;
    }

  oldBrush = NULL;
  oldPen = NULL;
  oldClipRegion = NULL;

  return self;
}

- (void) dealloc
{
  DeleteObject(clipRegion);
  [super dealloc];
}

- (void) setWindow: (HWND)number
{
  window = number;
}

- (HWND) window
{
  return window;
}

- (void) setColor: (device_color_t *)acolor state: (color_state_t)cState
{
  device_color_t color;
  [super setColor: acolor state: cState];
  color = *acolor;
  gsColorToRGB(&color);
  if (cState & COLOR_FILL)
    wfcolor = RGB(color.field[0]*255, color.field[1]*255, color.field[2]*255);
  if (cState & COLOR_STROKE)
    wscolor = RGB(color.field[0]*255, color.field[1]*255, color.field[2]*255);
}

// NOTE: Ideally this code should be moved to -DPSinitgraphics and not called 
// directly, but this would imply to rewrite WIN32GState more extensively since 
// the current code expects the GDI base coordinates in many methods.
- (void) setUpAppKitBaseCoordinatesForHDC: (HDC)hDC
{
  int surfaceHeight = WindowHeight(window);
  int bottomOffset = offset.y - surfaceHeight;
  XFORM xForm;

  // NOTE: The world transform is the GDI CTM.
  GetWorldTransform(hDC, &oldWorldTransform);

  // Enables the use of transforms
  SetGraphicsMode(hDC, GM_ADVANCED);
  // Maps units to pixels
  SetMapMode(hDC, MM_TEXT);

  /* Flip the GDI base coordinate to match the AppKit because GDI draws the 
     other way around with MM_TEXT.
     Also offset the drawing area to take in account the window border. */

  xForm.eM11 = (FLOAT)1; 
  xForm.eM12 = (FLOAT)0; 
  xForm.eM21 = (FLOAT)0; 
  xForm.eM22 = (FLOAT)-1; 
  xForm.eDx  = (FLOAT)-offset.x; 
  xForm.eDy  = (FLOAT)surfaceHeight + bottomOffset;

  SetWorldTransform(hDC, &xForm);
}

- (void) restoreGDIBaseCoordinatesForHDC: (HDC)hDC
{
  SetWorldTransform(hDC, &oldWorldTransform);
  SetGraphicsMode(hDC, GM_COMPATIBLE);
}

/* For debugging */
- (void) drawOrientationMarkersIn: (HDC)hDC
{
  RECT rect1 = { 0, 0, 20, 10 };
  RECT rect2 = { 0, 30, 20, 30 + 10 };
  FillRect(hDC, &rect1, CreateSolidBrush(RGB(255, 0, 255)));
  FillRect(hDC, &rect2, CreateSolidBrush(RGB(0, 255, 0)));
}

// FIXME: Drawing rotated images is broken (the origin is wrong).
// If rewritten, viewIsFlipped  check must be removed.
- (void) compositeGState: (WIN32GState *)source
		fromRect: (NSRect)sourceRect
		 toPoint: (NSPoint)destPoint
		      op: (NSCompositingOperation)op
		fraction: (float)delta
{
  HDC sourceDC;
  HDC hDC;
  RECT rectFrom;
  RECT rectTo;
  int h, w;
  int x;
  int y;
  NSRect destRect;
  BOOL success = NO;

  NSDebugLLog(@"WIN32GState",
    @"compositeGState: fromRect: %@ toPoint: %@ op: %d",
    NSStringFromRect(sourceRect), NSStringFromPoint(destPoint), op);

  if (viewIsFlipped && (self != source))
    {
      destPoint.y -= sourceRect.size.height;
    }

  destRect.origin = destPoint;
  destRect.size = sourceRect.size;
  [ctm boundingRectFor: destRect result: &destRect];
  rectTo = GSWindowRectToMS(self, destRect);
  x = rectTo.left;
  y = rectTo.bottom - sourceRect.size.height;

  {
    NSRect newRect;

    [source->ctm boundingRectFor: sourceRect result: &newRect];
    rectFrom = GSWindowRectToMS(source, newRect);
    y += (sourceRect.size.height - newRect.size.height); // adjust location for scaled source
  }
  h = rectFrom.bottom - rectFrom.top;
  w = rectFrom.right - rectFrom.left;

  sourceDC = [source getHDC];
  if (!sourceDC)
    {
      return;
    }

  if (self == source)
    {
      hDC = sourceDC;
    }
  else
    {
      hDC = [self getHDC];
      if (!hDC)
        {
          [source releaseHDC: sourceDC];
          return;
        }
    }

  switch (op)
    {
    case NSCompositeSourceOver:
      {
	success = alpha_blend_source_over(hDC, 
					  sourceDC, 
					  rectFrom, 
					  x, y, w, h, 
					  delta);
	if (success)
	  break;
      }
    case NSCompositeCopy:
      {
        success = BitBlt(hDC, x, y, w, h,
                         sourceDC, rectFrom.left, rectFrom.top, SRCCOPY);
        break;	      
      }
    case NSCompositeClear:
      {
        break;
      }
    default:
      success = BitBlt(hDC, x, y, w, h,
                       sourceDC, rectFrom.left, rectFrom.top, SRCCOPY);
      break;
    }

  if (!success)
    {
      NSLog(@"Blit operation failed %d", GetLastError());
      NSLog(@"Orig Copy Bits to %@ from %@", NSStringFromPoint(destPoint),
            NSStringFromRect(destRect));
      NSLog(@"Copy bits to {%d, %d} from {%d, %d} size {%d, %d}", x, y,
            rectFrom.left, rectFrom.top, w, h);
    }

  if (self != source)
    {
      [self releaseHDC: hDC];
    }
  [source releaseHDC: sourceDC];
}

- (void) compositerect: (NSRect)aRect
                    op: (NSCompositingOperation)op
{
  float gray;

  // FIXME: This is taken from the xlib backend
  [self DPScurrentgray: &gray];
  if (fabs(gray - 0.667) < 0.005)
    [self DPSsetgray: 0.333];
  else    
    [self DPSsetrgbcolor: 0.121 : 0.121 : 0];

  switch (op)
    {
      case   NSCompositeClear:
	break;
      case   NSCompositeHighlight:
	{
	  HDC hDC;
	  RECT rect = GSViewRectToWin(self, aRect);

	  hDC = [self getHDC];
	  if (!hDC)
	    {
	      return;
	    } 

	  InvertRect(hDC, &rect);
	  [self releaseHDC: hDC];
	  break;
	}
      case   NSCompositeCopy:
      // FIXME
      case   NSCompositeSourceOver:
      case   NSCompositeSourceIn:
      case   NSCompositeSourceOut:
      case   NSCompositeSourceAtop:
      case   NSCompositeDestinationOver:
      case   NSCompositeDestinationIn:
      case   NSCompositeDestinationOut:
      case   NSCompositeDestinationAtop:
      case   NSCompositeXOR:
      case   NSCompositePlusDarker:
      case   NSCompositePlusLighter:
      default:
	[self DPSrectfill: NSMinX(aRect) : NSMinY(aRect) 
	      : NSWidth(aRect) : NSHeight(aRect)];
	break;
    }
}

// FIXME: Drawing images with alpha blending is broken
- (void) drawGState: (WIN32GState *)source 
           fromRect: (NSRect)aRect 
            toPoint: (NSPoint)aPoint 
                 op: (NSCompositingOperation)op
           fraction: (float)delta
{
  HDC sourceDC;
  HDC hDC;
  XFORM xForm, xForm2;
  NSAffineTransformStruct tstruct = [ctm transformStruct];
  RECT rectFrom = GSWindowRectToMS(source, aRect);
  int x = aPoint.x;
  int y = aPoint.y;
  int w = aRect.size.width;
  int h = aRect.size.height;
  BOOL success = YES;

  sourceDC = [source getHDC];
  if (!sourceDC)
    {
      return;
    } 

  if (self == source)
    {
      hDC = sourceDC;
    }
  else
    {
      hDC = [self getHDC];
      if (!hDC)
        {
          [source releaseHDC: sourceDC];
          return;
        }
    }

  /* Set up the AppKit base coordinates as the World transform */

  [self setUpAppKitBaseCoordinatesForHDC: hDC];

  /* Apply the AppKit CTM */

  xForm.eM11 = (FLOAT)tstruct.m11; 
  xForm.eM12 = (FLOAT)tstruct.m12; 
  xForm.eM21 = (FLOAT)tstruct.m21; 
  xForm.eM22 = (FLOAT)tstruct.m22; 
  xForm.eDx  = (FLOAT)tstruct.tX;
  xForm.eDy  = (FLOAT)tstruct.tY;

  // Concat the transform by prepending it to the CTM
  ModifyWorldTransform(hDC, &xForm, MWT_LEFTMULTIPLY);

  /* Flip the CTM to compensate the fact that images are drawn upside down 
     by -DPSimage which uses the GDI top left origin coordinates. */

  xForm2.eM11 = (FLOAT)1;
  xForm2.eM12 = (FLOAT)0; 
  xForm2.eM21 = (FLOAT)0; 
  xForm2.eM22 = (FLOAT)-1; 
  xForm2.eDx  = (FLOAT)0; 
  xForm2.eDy  = (FLOAT)y * 2 + aRect.size.height;

  // Concat the flip transform by prepending it to the CTM
  ModifyWorldTransform(hDC, &xForm2, MWT_LEFTMULTIPLY);
  
  switch (op)
    {
    case NSCompositeSourceOver:
      {
	success = alpha_blend_source_over(hDC, 
					  sourceDC, 
					  rectFrom, 
					  x, y, w, h, 
					  delta);
	if (success)
	  break;
      }
    case NSCompositeCopy:
      {
        success = BitBlt(hDC, x, y, w, h,
                         sourceDC, rectFrom.left, rectFrom.top, SRCCOPY);
        break;	      
      }
    case NSCompositeClear:
      {
        break;
      }
    default:
      success = BitBlt(hDC, x, y, w, h,
                       sourceDC, rectFrom.left, rectFrom.top, SRCCOPY);
      break;
    }
  //[self drawOrientationMarkersIn: hDC];

  if (!success)
    {
      NSLog(@"Blit operation failed %d", GetLastError());
    }

  [self restoreGDIBaseCoordinatesForHDC: hDC];

  if (self != source)
    {
      [self releaseHDC: hDC];
    }
  [source releaseHDC: sourceDC];
}

static
HBITMAP GSCreateBitmap(HDC hDC, int pixelsWide, int pixelsHigh,
		       int bitsPerSample, int samplesPerPixel,
		       int bitsPerPixel, int bytesPerRow,
		       BOOL isPlanar, BOOL hasAlpha,
		       NSString *colorSpaceName,
		       const unsigned char *const data[5])
{
  const unsigned char *bits = data[0];
  HBITMAP hbitmap;
  BITMAPINFO *bitmap;
  BITMAPINFOHEADER *bmih;
  int xres, yres;
  UINT fuColorUse;

  if (isPlanar
    || !([colorSpaceName isEqualToString: NSDeviceRGBColorSpace]
    || ![colorSpaceName isEqualToString: NSCalibratedWhiteColorSpace]
    || ![colorSpaceName isEqualToString: NSCalibratedBlackColorSpace]
    || [colorSpaceName isEqualToString: NSCalibratedRGBColorSpace]))
    {
      NSLog(@"Bitmap type currently not supported %d %@",
	isPlanar, colorSpaceName);
      return NULL;
    }

  // default is 8 bit grayscale 
  if (!bitsPerSample)
    bitsPerSample = 8;
  if (!samplesPerPixel)
    samplesPerPixel = 1;

  // FIXME - does this work if we are passed a planar image but no hints ?
  if (!bitsPerPixel)
    bitsPerPixel = bitsPerSample * samplesPerPixel;
  if (!bytesPerRow)
    bytesPerRow = (bitsPerPixel * pixelsWide) / 8;

  // make sure its sane - also handles row padding if hint missing
  while ((bytesPerRow * 8) < (bitsPerPixel * pixelsWide))
    bytesPerRow++;

  if (!(GetDeviceCaps(hDC, RASTERCAPS) &  RC_DI_BITMAP)) 
    {
      NSLog(@"Device %d does not support bitmap operations", hDC);
      return NULL;
    }

  hbitmap = CreateCompatibleBitmap(hDC, pixelsWide, pixelsHigh);
  if (!hbitmap)
    {
      NSLog(@"Failed to CreateCompatibleBitmap (%d, %d). Error %d", 
            pixelsWide, pixelsHigh, GetLastError());
      return NULL;
    }

  if (bitsPerPixel > 8)
    {
      bitmap = malloc(sizeof(BITMAPV4HEADER));
    }
  else 
    {
      // Leave some extra space for colour map. (Currently not used)
      bitmap = malloc(sizeof(BITMAPINFOHEADER) +  
			   (1 << bitsPerPixel) * sizeof(RGBQUAD));
    }
  if (!bitmap)
    {
       NSLog(@"Failed to allocate memory for bitmap. Error %d", GetLastError());
       DeleteObject(hbitmap);
       return NULL;
    }

  bmih = (BITMAPINFOHEADER*)bitmap;
  bmih->biSize = sizeof(BITMAPINFOHEADER);
  bmih->biWidth = pixelsWide;
  // Top down orientation
  bmih->biHeight = -pixelsHigh;
  bmih->biPlanes = 1;
  bmih->biBitCount = bitsPerPixel;
  bmih->biCompression = BI_RGB;
  bmih->biSizeImage = 0;
  xres = GetDeviceCaps(hDC, HORZRES) / GetDeviceCaps(hDC, HORZSIZE);
  yres = GetDeviceCaps(hDC, VERTRES) / GetDeviceCaps(hDC, VERTSIZE);
  bmih->biXPelsPerMeter = xres;
  bmih->biYPelsPerMeter = yres;
  bmih->biClrUsed = 0;
  bmih->biClrImportant = 0;
  fuColorUse = 0;

  if (bitsPerPixel == 8 && samplesPerPixel == 1)
    {
      unsigned char* tmp;
      unsigned int pixels = pixelsHigh * pixelsWide;
      unsigned int i = 0;
      unsigned int j = 0;
 
      bmih->biBitCount = 32;

      NSDebugLLog(@"WIN32GState", @"8bit greyscale picture with pixelsWide:%d "
	@"pixelsHigh:%d", pixelsWide, pixelsHigh);
      
      tmp = malloc(pixels * 4);
      if (!tmp)
        {
          NSLog(@"Failed to allocate temporary memory for bitmap. Error %d", 
                GetLastError());
          free(bitmap);
          DeleteObject(hbitmap);
          return NULL;
        }

      if ([colorSpaceName isEqualToString: NSCalibratedWhiteColorSpace])
        {
          while (i < (pixels*4))
            {
	      unsigned char pix;
	      pix = bits[j];
              tmp[i+0] = pix;
              tmp[i+1] = pix;
              tmp[i+2] = pix;
              tmp[i+3] = 0xFF;
	      i+=4;
              j++;
            }
	  }
      else if ([colorSpaceName isEqualToString: NSCalibratedBlackColorSpace])
        {
          while (i < (pixels*4))
            {
	      unsigned char pix;
	      pix = UCHAR_MAX - bits[j];
              tmp[i+0] = pix;
              tmp[i+1] = pix;
              tmp[i+2] = pix;
              tmp[i+3] = 0xFF;
	      i+=4;
              j++;
            }
	  }
      else
        {
          NSLog(@"Unexpected condition, greyscale which is neither white nor black calibrated");
          free(tmp);
          free(bitmap);
          DeleteObject(hbitmap);
          return NULL;
        }
      bits = tmp;
    }
  else if (bitsPerPixel == 32)
    {
      BITMAPV4HEADER	*bmih;
      unsigned char	*tmp;
      unsigned int	pixels = pixelsHigh * pixelsWide;
      unsigned int	i = 0;

      bmih = (BITMAPV4HEADER*)bitmap;
      bmih->bV4Size = sizeof(BITMAPV4HEADER);
      bmih->bV4V4Compression = BI_BITFIELDS;
      bmih->bV4BlueMask = 0x000000FF;
      bmih->bV4GreenMask = 0x0000FF00;
      bmih->bV4RedMask = 0x00FF0000;
      bmih->bV4AlphaMask = 0xFF000000;
      tmp = malloc(pixels * 4);
      if (!tmp)
        {
          NSLog(@"Failed to allocate temporary memory for bitmap. Error %d", 
                GetLastError());
          free(bitmap);
          DeleteObject(hbitmap);
          return NULL;
        }

      while (i < pixels*4)
	{
	  tmp[i+0] = bits[i+2];
	  tmp[i+1] = bits[i+1];
	  tmp[i+2] = bits[i+0];
	  tmp[i+3] = bits[i+3];
	  i += 4;
	}
      bits = tmp;
    }
  else if (bitsPerPixel == 16 && samplesPerPixel == 2) // 8 bit greyscale 8 bit alpha
    {
      BITMAPV4HEADER	*bmih;
      unsigned char	*tmp;
      unsigned int	pixels = pixelsHigh * pixelsWide;
      unsigned int	i = 0, j = 0;

      ((BITMAPINFOHEADER*)bitmap)->biBitCount = 32;

      bmih = (BITMAPV4HEADER*)bitmap;
      bmih->bV4Size = sizeof(BITMAPV4HEADER);
      bmih->bV4V4Compression = BI_BITFIELDS;
      bmih->bV4BlueMask = 0x000000FF;
      bmih->bV4GreenMask = 0x0000FF00;
      bmih->bV4RedMask = 0x00FF0000;
      bmih->bV4AlphaMask = 0xFF000000;
      tmp = malloc(pixels * 4);
      if (!tmp)
        {
          NSLog(@"Failed to allocate temporary memory for bitmap. Error %d", 
                GetLastError());
          free(bitmap);
          DeleteObject(hbitmap);
          return NULL;
        }

      while (i < pixels*4)
	{
	  tmp[i+0] = bits[j];
	  tmp[i+1] = bits[j];
	  tmp[i+2] = bits[j];
	  tmp[i+3] = bits[j + 1];
	  i += 4;
	  j += 2;
	}
      bits = tmp;
    }
  else if (bitsPerPixel == 24)
   {
      unsigned char* tmp;
      unsigned int pixels = pixelsHigh * pixelsWide;
      unsigned int i = 0, j = 0;
 
      bmih->biBitCount = 32;

      NSDebugLLog(@"WIN32GState", @"24bit picure with pixelsWide:%d "
	@"pixelsHigh:%d", pixelsWide, pixelsHigh);
      
      tmp = malloc(pixels * 4);
      if (!tmp)
        {
          NSLog(@"Failed to allocate temporary memory for bitmap. Error %d", 
                GetLastError());
          free(bitmap);
          DeleteObject(hbitmap);
          return NULL;
        }

      while (i < (pixels*4))
        {
          // We expand the bytes in a 24bit image into 32bits as Windows
	  // seems to handle it better (and I can't figure out the correct
	  // rearrangement for 24bit images anyway).
          tmp[i+0] = bits[j+2];
          tmp[i+1] = bits[j+1];
          tmp[i+2] = bits[j+0];
          tmp[i+3] = 0xFF;
	  i+=4;
          j+=3;
        }
      bits = tmp;
    }
  else
    {
      if (bitsPerPixel <= 8 && samplesPerPixel > 1)
        {
          // FIXME How to get a colour palette?
          NSLog(@"Need to define colour map for images with %d bits", bitsPerPixel);
          //bitmap->bmiColors;
          //fuColorUse = DIB_RGB_COLORS;
        }
      else
        {
          NSLog(@"Unsure how to handle images with %d bpp %d spp", bitsPerPixel, samplesPerPixel);
        }
      free(bitmap);
      DeleteObject(hbitmap);
      return NULL;
    }

  if (!SetDIBits(hDC, hbitmap, 0, pixelsHigh, bits, bitmap, fuColorUse))
    {
      NSLog(@"SetDIBits failed. Error %d", GetLastError());
      DeleteObject(hbitmap);
      hbitmap = NULL;
    }

  if (bits != data[0])
    {
      /* cast bits to Void Pointer to fix warning in compile */
      free((void *)(bits));
    }
  free(bitmap);
  return hbitmap;
}

// NOTE: Draws the image with the GDI top left coordinate origin rather than 
// the AppKit bottom left coordinate origin. Hence drawing from an image cache 
// window into an AppKit base coordinate space results in an upside down image.
// Ideally we should draw image in their cache windows with the AppKit base 
// coordinates to prevent extra flip transforms to be required later on (see 
// drawGState:fromRect:toPoint:op:fraction).
// For some unknown reason, invoking -setUpAppKitBaseCoordinatesForHDC: in 
// -DPSimage: can cause images to be drawn with a 1px horizontal line cut at 
// the top. That's why -DPSimage we still use the GDI top left coordinates.
- (void)DPSimage: (NSAffineTransform*) matrix 
		: (int) pixelsWide : (int) pixelsHigh
		: (int) bitsPerSample : (int) samplesPerPixel 
		: (int) bitsPerPixel : (int) bytesPerRow : (BOOL) isPlanar
		: (BOOL) hasAlpha : (NSString *) colorSpaceName
		: (const unsigned char *const [5]) data
{
  NSAffineTransform *old_ctm = nil;
  HDC hDC;
  HBITMAP hbitmap;
  HGDIOBJ old;
  HDC hDC2;
  POINT pa[3];

/*
  NSDebugLLog(@"WIN32GState", @"DPSImage : pixelsWide = %d : pixelsHigh = %d"
	      ": bitsPerSample = %d : samplesPerPixel = %d"
	      ": bitsPerPixel = %d : bytesPerRow = %d "
	      ": isPlanar = %d"
	      ": hasAlpha = %d : colorSpaceName = %@",
	      pixelsWide, pixelsHigh,
	      bitsPerSample, samplesPerPixel,
	      bitsPerPixel, bytesPerRow,
	      isPlanar, hasAlpha, colorSpaceName);
*/

  if (window == NULL)
    {
      NSLog(@"No window in DPSImage");
      return;
    }

  hDC = GetDC((HWND)window);
  if (!hDC)
    {
      NSLog(@"No DC for window %d in DPSImage. Error %d", 
	    (int)window, GetLastError());
      return;
    }

  hbitmap = GSCreateBitmap(hDC, pixelsWide, pixelsHigh,
			   bitsPerSample, samplesPerPixel,
			   bitsPerPixel, bytesPerRow,
			   isPlanar, hasAlpha,
			   colorSpaceName, data);
  if (!hbitmap)
    {
      NSLog(@"Created bitmap failed %d", GetLastError());
      ReleaseDC((HWND)window, hDC);
      return;
    }

  hDC2 = CreateCompatibleDC(hDC); 
  if (!hDC2)
    {
      NSLog(@"No Compatible DC for window %d in DPSImage. Error %d", 
	    (int)window, GetLastError());
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hDC);
      return;
    }

  old = SelectObject(hDC2, hbitmap);
  if (!old)
    {
      NSLog(@"SelectObject failed for window %d in DPSImage. Error %d", 
	    (int)window, GetLastError());
      DeleteDC(hDC2);
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hDC);
      return;
    }

  //SetMapMode(hDC2, GetMapMode(hDC));
  ReleaseDC((HWND)window, hDC);

  hDC = [self getHDC];

  // Apply the additional transformation
  if (matrix)
    {
      old_ctm = [ctm copy];
      [ctm prependTransform: matrix];
    }

  pa[0] = GSViewPointToWin(self, NSMakePoint(0, pixelsHigh));
  pa[1] = GSViewPointToWin(self, NSMakePoint(pixelsWide, pixelsHigh));
  pa[2] = GSViewPointToWin(self, NSMakePoint(0, 0));

  /*if (viewIsFlipped)
    {
      pa[0].y += pixelsHigh;
      pa[1].y += pixelsHigh;
      pa[2].y += pixelsHigh;
    }*/

  if (old_ctm != nil)
    {
      RELEASE(ctm);
      // old_ctm is already retained
      ctm = old_ctm;
    }

  if ((GetDeviceCaps(hDC, RASTERCAPS) &  RC_BITBLT)) 
    {
      SetStretchBltMode(hDC, COLORONCOLOR);

      if (!PlgBlt(hDC, pa, hDC2, 0, 0, pixelsWide, pixelsHigh, 0, 0, 0))
        {
          NSLog(@"Copy bitmap failed %d", GetLastError());
          NSLog(@"DPSimage with %d %d %d %d to %d, %d", pixelsWide, pixelsHigh, 
	        bytesPerRow, bitsPerPixel, pa[0].x, pa[0].y);
        }
    }
  
  [self releaseHDC: hDC];

  SelectObject(hDC2, old);
  DeleteDC(hDC2);
  DeleteObject(hbitmap);
}

@end

@implementation WIN32GState (PathOps)

- (void) _paintPath: (ctxt_object_t) drawType
{
  unsigned count;
  HDC hDC;

  hDC = [self getHDC];
  if (!hDC)
    {
      return;
    } 

  count = [path elementCount];
  if (count)
    {
      NSBezierPathElement type;
      NSPoint   points[3];
      unsigned	j, i = 0;
      POINT p;

      BeginPath(hDC);

      for (j = 0; j < count; j++) 
        {
	  type = [path elementAtIndex: j associatedPoints: points];
	  switch(type) 
	    {
	    case NSMoveToBezierPathElement:
	      p = GSWindowPointToMS(self, points[0]);
	      MoveToEx(hDC, p.x, p.y, NULL);
	      break;
	    case NSLineToBezierPathElement:
	      p = GSWindowPointToMS(self, points[0]);
	      // FIXME This gives one pixel too few
	      LineTo(hDC, p.x, p.y);
	      break;
	    case NSCurveToBezierPathElement:
	      {
		POINT bp[3];
		
		for (i = 0; i < 3; i++)
		  {
		    bp[i] = GSWindowPointToMS(self, points[i]);
		  }
		PolyBezierTo(hDC, bp, 3);
	      }
	      break;
	    case NSClosePathBezierPathElement:
	      CloseFigure(hDC);
	      break;
	    default:
	      break;
	    }
	}  
      EndPath(hDC);

      // Now operate on the path
      switch (drawType)
	{
	case path_stroke:
	  if (strokeColor.field[AINDEX] != 0.0)
	    {
	      StrokePath(hDC);
	    }
	  break;
	case path_eofill:
	  if (fillColor.field[AINDEX] != 0.0)
	    {
	      SetPolyFillMode(hDC, ALTERNATE);
	      FillPath(hDC);
	    }
	  break;
	case path_fill:
	  if (fillColor.field[AINDEX] != 0.0)
	    {
	      SetPolyFillMode(hDC, WINDING);
	      FillPath(hDC);
	    }
	  break;
	case path_eoclip:
	  {
	    HRGN region;

	    SetPolyFillMode(hDC, ALTERNATE);
	    region = PathToRegion(hDC);
	    if (clipRegion)
              {
                CombineRgn(clipRegion, clipRegion, region, RGN_AND);
                DeleteObject(region);
              }
	    else
	      {
		clipRegion = region;
	      }
	    break;
	  }
	case path_clip:
	  {
	    HRGN region;

	    SetPolyFillMode(hDC, WINDING);
	    region = PathToRegion(hDC);
	    if (clipRegion)
              {
                CombineRgn(clipRegion, clipRegion, region, RGN_AND);
                DeleteObject(region);
              }
	    else
	      {
		clipRegion = region;
	      }
	    break;
	  }
	default:
	  break;
	}
    }
  [self releaseHDC: hDC];

  /*
   * clip does not delete the current path, so we only clear the path if the
   * operation was not a clipping operation.
   */
  if ((drawType != path_clip) && (drawType != path_eoclip))
    {
      [path removeAllPoints];
    }
}

- (void)DPSclip 
{
  [self _paintPath: path_clip];
}

- (void)DPSeoclip 
{
  [self _paintPath: path_eoclip];
}

- (void)DPSeofill 
{
  if (pattern != nil)
    {
      [self eofillPath: path withPattern: pattern];
      return;
    }
  [self _paintPath: path_eofill];
}

- (void)DPSfill 
{
  if (pattern != nil)
    {
      [self fillPath: path withPattern: pattern];
      return;
    }

  [self _paintPath: path_fill];
}

- (void)DPSstroke 
{
  [self _paintPath: path_stroke];
}

- (void) DPSinitclip;
{
  if (clipRegion)
    {
      DeleteObject(clipRegion);
      clipRegion = NULL;
    }
}

- (void)DPSshow: (const char *)s 
{
  NSPoint current = [path currentPoint];
  POINT p;
  HDC hDC;

  hDC = [self getHDC];
  if (!hDC)
    {
      return;
    } 

  p = GSWindowPointToMS(self, current);
  [(WIN32FontInfo*)font draw: s length:  strlen(s)
		   onDC: hDC at: p];
  [self releaseHDC: hDC];
}


- (void) GSShowGlyphsWithAdvances: (const NSGlyph *)glyphs : (const NSSize *)advances : (size_t) length
{
  // FIXME: Currently advances is ignored
  NSPoint current = [path currentPoint];
  POINT p;
  HDC hDC;

  hDC = [self getHDC];
  if (!hDC)
    {
      return;
    } 

  p = GSWindowPointToMS(self, current);
  [(WIN32FontInfo*)font drawGlyphs: glyphs
			    length: length
			      onDC: hDC
				at: p];
  [self releaseHDC: hDC];
}
@end

@implementation WIN32GState (GStateOps)

- (void)DPSinitgraphics 
{
  [super DPSinitgraphics];
}


- (void) DPSsetdash: (const float*)thePattern : (int)count : (float)phase
{
  if (!path)
    {
      path = [NSBezierPath new];
    }

  // FIXME: Convert to ctm first
  [path setLineDash: thePattern count: count phase: phase];
}

- (void)DPScurrentmiterlimit: (float *)limit 
{
  *limit = miterlimit;
}

- (void)DPSsetmiterlimit: (float)limit 
{
  // FIXME: Convert to ctm first
  miterlimit = limit;
}

- (void)DPScurrentlinecap: (int *)linecap 
{
  *linecap = lineCap;
}

- (void)DPSsetlinecap: (int)linecap 
{
  lineCap = linecap;
}

- (void)DPScurrentlinejoin: (int *)linejoin 
{
  *linejoin = joinStyle;
}

- (void)DPSsetlinejoin: (int)linejoin 
{
  joinStyle = linejoin;
}

- (void)DPScurrentlinewidth: (float *)width 
{
  *width = lineWidth;
}

- (void)DPSsetlinewidth: (float)width 
{
  // FIXME: Convert to ctm first
  lineWidth = width;
}

- (void)DPScurrentstrokeadjust: (int *)b 
{
}

- (void)DPSsetstrokeadjust: (int)b 
{
}

@end

@implementation WIN32GState (WinOps)

- (void) setStyle: (HDC)hDC
{
  HPEN pen;
  HBRUSH brush;
  LOGBRUSH br; 
  int join;
  int cap;
  DWORD penStyle;

  // Temporary variables for gathering pen information
  float* thePattern = NULL;
  DWORD* iPattern = NULL;
  int patternCount = 0;
  float phase = 0.0;
  
  SetBkMode(hDC, TRANSPARENT);
  br.lbStyle = BS_SOLID;
  br.lbColor = wfcolor;
  br.lbHatch = 0;
  /*
  brush = CreateBrushIndirect(&br);
  */
  brush = CreateSolidBrush(wfcolor);
  oldBrush = SelectObject(hDC, brush);

  switch (joinStyle)
    {
      case NSBevelLineJoinStyle:
	join = PS_JOIN_BEVEL;
	break;
      case NSMiterLineJoinStyle:
	join = PS_JOIN_MITER;
	break;
      case NSRoundLineJoinStyle:
	join = PS_JOIN_ROUND;
	break;
      default:
	join = PS_JOIN_MITER;
	break;
    }

  switch (lineCap)
    {
      case NSButtLineCapStyle:
	cap = PS_ENDCAP_FLAT;
	break;
      case NSSquareLineCapStyle:
	cap = PS_ENDCAP_SQUARE;
	break;
      case NSRoundLineCapStyle:
	cap = PS_ENDCAP_ROUND;
	break;
      default:
	cap = PS_ENDCAP_SQUARE;
	break;
    }
  
  // Get the size of the pen line dash
  [path getLineDash: NULL count: &patternCount phase: NULL];
  
  if (patternCount > 0)
    {
      penStyle = PS_GEOMETRIC | PS_USERSTYLE;

      // The user has defined a dash pattern for stroking on
      // the path. Note that we lose the floating point information
      // here, as windows only supports DWORD elements, not float.
      thePattern = malloc(sizeof(float) * patternCount);
      [path getLineDash: thePattern count: &patternCount phase: &phase];

      iPattern = malloc(sizeof(DWORD) * patternCount);
      int i  = 0;
      for (i = 0 ; i < patternCount; i ++)
	iPattern[i] = (DWORD)thePattern[i];
      free(thePattern);
      thePattern = NULL;
    }
  else
    {
      penStyle = PS_GEOMETRIC | PS_SOLID;
    }

  pen = ExtCreatePen(penStyle | join | cap, 
		     lineWidth,
		     &br,
		     patternCount, iPattern);

  if (iPattern)
    {
      free(iPattern);
      iPattern = NULL;
    }

  oldPen = SelectObject(hDC, pen);

  SetMiterLimit(hDC, miterlimit, NULL);

  SetTextColor(hDC, wfcolor);

  oldClipRegion = CreateRectRgn(0, 0, 1, 1);
  if (1 != GetClipRgn(hDC, oldClipRegion))
    {
      DeleteObject(oldClipRegion);
      oldClipRegion = NULL;
    }

  SelectClipRgn(hDC, clipRegion);
}

- (void) restoreStyle: (HDC)hDC
{
  HGDIOBJ old;

  SelectClipRgn(hDC, oldClipRegion);
  DeleteObject(oldClipRegion);
  oldClipRegion = NULL;

  old = SelectObject(hDC, oldBrush);
  DeleteObject(old);

  old = SelectObject(hDC, oldPen);
  DeleteObject(old);
}

- (HDC) getHDC
{
  WIN_INTERN *win;
  HDC hDC;

  if (NULL == window)
    {
      //NSLog(@"No window in getHDC");
      return NULL;
    }

  win = (WIN_INTERN *)GetWindowLong((HWND)window, GWL_USERDATA);
  if (win && win->useHDC)
    {
      hDC = win->hdc;
      //NSLog(@"getHDC found DC %d", hDC);
    }
  else
    {
      hDC = GetDC((HWND)window);    
      //NSLog(@"getHDC using window DC %d", hDC);
    }
  
  if (!hDC)
    {
      //NSLog(@"No DC in getHDC");
      return NULL;	
    }

  [self setStyle: hDC];
  return hDC;
}

- (void) releaseHDC: (HDC)hDC
{
  WIN_INTERN *win;

  if (NULL == window ||
      NULL == hDC)
    {
      return;
    }

  [self restoreStyle: hDC];
  win = (WIN_INTERN *)GetWindowLong((HWND)window, GWL_USERDATA);
  if (win && !win->useHDC)
    ReleaseDC((HWND)window, hDC);
}

@end


@implementation WIN32GState (PatternColor)

- (void *) saveClip
{
  if (clipRegion)
    {
      HRGN newClipRegion;

      newClipRegion = CreateRectRgn(0, 0, 1, 1);
      CombineRgn(newClipRegion, clipRegion, NULL, RGN_COPY);

      return newClipRegion;
    }
  return clipRegion;
}

- (void) restoreClip: (void *)savedClip
{
  if (clipRegion)
    {
      DeleteObject(clipRegion);
    }
  clipRegion = savedClip;
}

@end

@implementation WIN32GState (ReadRect)

- (NSDictionary *) GSReadRect: (NSRect)r
{
  NSMutableDictionary *dict;
  NSAffineTransform *matrix;
  double x, y;
  NSMutableData *data;
  unsigned char *cdata;
  unsigned char *bits;
  int i = 0;
  HDC hDC;
  HDC hdcMemDC = NULL;
  HBITMAP hbitmap = NULL;
  BITMAP bmpCopied;
  HGDIOBJ old;
  RECT rcClient;
  DWORD dwBmpSize;
  HANDLE hDIB;
  LPBITMAPV4HEADER lpbi;

  if (window == NULL)
    {
      NSLog(@"No window in GSReadRect");
      return nil;
    }

  r = [ctm rectInMatrixSpace: r];
  x = NSWidth(r);
  y = NSHeight(r);

  dict = [NSMutableDictionary dictionary];
  [dict setObject: NSDeviceRGBColorSpace forKey: @"ColorSpace"];

  [dict setObject: [NSNumber numberWithUnsignedInt: 8]
	   forKey: @"BitsPerSample"];
  [dict setObject: [NSNumber numberWithUnsignedInt: 32]
	   forKey: @"Depth"];
  [dict setObject: [NSNumber numberWithUnsignedInt: 4] 
           forKey: @"SamplesPerPixel"];
  [dict setObject: [NSNumber numberWithUnsignedInt: 1]
           forKey: @"HasAlpha"];

  matrix = [self GSCurrentCTM];
  [matrix translateXBy: -r.origin.x - offset.x 
		   yBy: r.origin.y + NSHeight(r) - offset.y];
  [dict setObject: matrix forKey: @"Matrix"];

  hDC = GetDC((HWND)window);
  if (!hDC)
    {
      NSLog(@"No DC for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      return nil;
    }

  // Create a compatible DC which is used in a BitBlt from the window DC
  hdcMemDC = CreateCompatibleDC(hDC); 
  if (!hdcMemDC)
    {
      NSLog(@"No Compatible DC for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      ReleaseDC((HWND)window, hDC);
      return nil;
    }

  ReleaseDC((HWND)window, hDC);
  hDC = [self getHDC];

  // Get the client area for size calculation
  rcClient = GSWindowRectToMS(self, r);

  // Create a compatible bitmap from the Window DC
  hbitmap = CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left, 
  rcClient.bottom - rcClient.top);
  if (!hbitmap)
    {
      NSLog(@"No Compatible bitmap for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      ReleaseDC((HWND)window, hdcMemDC);
      [self releaseHDC: hDC];
      return nil;
    }

  // Select the compatible bitmap into the compatible memory DC.
  old = SelectObject(hdcMemDC, hbitmap);
  if (!old)
    {
      NSLog(@"SelectObject failed for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hdcMemDC);
      [self releaseHDC: hDC];
      return nil;
    }

  // Bit block transfer into our compatible memory DC.
  if (!BitBlt(hdcMemDC, 0, 0, 
    rcClient.right - rcClient.left, 
    rcClient.bottom - rcClient.top, 
    hDC, 
    0, 0,
    SRCCOPY))
    {
      NSLog(@"BitBlt failed for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hdcMemDC);
      [self releaseHDC: hDC];
      return nil;
    }

  // Get the BITMAP from the HBITMAP
  GetObject(hbitmap, sizeof(BITMAP), &bmpCopied);

  dwBmpSize = bmpCopied.bmWidth * 4 * bmpCopied.bmHeight;

  hDIB = GlobalAlloc(GHND, dwBmpSize + sizeof(BITMAPV4HEADER)); 
  lpbi = (LPBITMAPV4HEADER)GlobalLock(hDIB);    
  lpbi->bV4Size = sizeof(BITMAPV4HEADER);
  lpbi->bV4V4Compression = BI_BITFIELDS;
  lpbi->bV4BlueMask = 0x000000FF;
  lpbi->bV4GreenMask = 0x0000FF00;
  lpbi->bV4RedMask = 0x00FF0000;
  lpbi->bV4AlphaMask = 0xFF000000;
  lpbi->bV4Width = bmpCopied.bmWidth;    
  lpbi->bV4Height = bmpCopied.bmHeight;  
  lpbi->bV4Planes = 1;    
  lpbi->bV4BitCount = 32;    
  lpbi->bV4SizeImage = 0;  
  lpbi->bV4XPelsPerMeter = 0;    
  lpbi->bV4YPelsPerMeter = 0;    
  lpbi->bV4ClrUsed = 0;    
  lpbi->bV4ClrImportant = 0;
  bits = (unsigned char *)lpbi + sizeof(BITMAPV4HEADER);

  // Gets the "bits" from the bitmap and copies them into a buffer 
  // which is pointed to by lpbi
  if (GetDIBits(hdcMemDC, hbitmap, 0, (UINT)bmpCopied.bmHeight, bits,
    (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0)
    {
      NSLog(@"GetDIBits failed for window %d in GSReadRect. Error %d", 
	(int)window, GetLastError());
      GlobalUnlock(hDIB);    
      GlobalFree(hDIB);
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hdcMemDC);
      [self releaseHDC: hDC];
      return nil;
    }

  data = [NSMutableData dataWithLength: dwBmpSize];
  if (data == nil)
    {
      GlobalUnlock(hDIB);    
      GlobalFree(hDIB);
      DeleteObject(hbitmap);
      ReleaseDC((HWND)window, hdcMemDC);
      [self releaseHDC: hDC];
      return nil;
    }

  // Copy to data
  cdata = [data mutableBytes];
  while (i < dwBmpSize)
    {
      cdata[i+0] = bits[i+2];
      cdata[i+1] = bits[i+1];
      cdata[i+2] = bits[i+0];
      cdata[i+3] = bits[i+3];
      i += 4;
    }


  //Unlock and Free the DIB from the heap
  GlobalUnlock(hDIB);    
  GlobalFree(hDIB);

  //Clean up
  DeleteObject(hbitmap);
  ReleaseDC((HWND)window, hdcMemDC);
  [self releaseHDC: hDC];

  [dict setObject: [NSValue valueWithSize: NSMakeSize(bmpCopied.bmWidth,
  bmpCopied.bmHeight)]
  forKey: @"Size"];
  [dict setObject: data forKey: @"Data"];

  return dict;
}

@end
