/* Copyright (C) 1989, 1995, 1996, 1998, 1999, 2000, 2001 artofcode LLC.  All rights reserved.
  
  This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevmswn.c,v 1.4.2.2.2.1 2003/01/17 00:49:01 giles Exp $ */
/*
 * Microsoft Windows 3.n driver for Ghostscript.
 *
 * Original version by Russell Lang and Maurice Castro with help from
 * Programming Windows, 2nd Ed., Charles Petzold, Microsoft Press;
 * created from gdevbgi.c and gnuplot/term/win.trm 5th June 1992.
 * Extensively modified by L. Peter Deutsch, Aladdin Enterprises.
 */
#include "gdevmswn.h"
#include "gp.h"
#include "gpcheck.h"
#include "gsparam.h"
#include "gdevpccm.h"
#include "gsdll.h"

/* Forward references */
private int win_set_bits_per_pixel(P2(gx_device_win *, int));

#define TIMER_ID 1

/* Open the win driver */
int
win_open(gx_device * dev)
{
    HDC hdc;
    int code;

    if (dev->width == INITIAL_WIDTH)
	dev->width = (int)(8.5 * dev->x_pixels_per_inch);
    if (dev->height == INITIAL_HEIGHT)
	dev->height = (int)(11.0 * dev->y_pixels_per_inch);

    if (wdev->BitsPerPixel == 0) {
	int depth;

	/* Set parameters that were unknown before opening device */
	/* Find out if the device supports color */
	/* We recognize 1, 4, 8, 16, 24 bit/pixel devices */
	hdc = GetDC(NULL);	/* get hdc for desktop */
	depth = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL);
	if (depth > 16) {
	    wdev->BitsPerPixel = 24;
	} else if (depth > 8) {
	    wdev->BitsPerPixel = 16;
	} else if (depth >= 8) {
	    wdev->BitsPerPixel = 8;
	} else if (depth >= 4) {
	    wdev->BitsPerPixel = 4;
	} else {
	    wdev->BitsPerPixel = 1;
	}
	ReleaseDC(NULL, hdc);
	wdev->mapped_color_flags = 0;
    }
    if ((code = win_set_bits_per_pixel(wdev, wdev->BitsPerPixel)) < 0)
	return code;

    if (wdev->nColors > 0) {
	/* create palette for display */
	if ((wdev->limgpalette = win_makepalette(wdev))
	    == (LPLOGPALETTE) NULL)
	    return win_nomemory();
	wdev->himgpalette = CreatePalette(wdev->limgpalette);
    }
    return 0;
}

/* Make the output appear on the screen. */
int
win_sync_output(gx_device * dev)
{
    if (pgsdll_callback)
	(*pgsdll_callback) (GSDLL_SYNC, (unsigned char *)wdev, 0);
    return (0);
}

/* Make the window visible, and display the output. */
int
win_output_page(gx_device * dev, int copies, int flush)
{
    if (pgsdll_callback)
	(*pgsdll_callback) (GSDLL_PAGE, (unsigned char *)wdev, 0);
    return gx_finish_output_page(dev, copies, flush);;
}

/* Close the win driver */
int
win_close(gx_device * dev)
{
    /* Free resources */
    if (wdev->nColors > 0) {
	gs_free(wdev->mapped_color_flags, 4096, 1, "win_set_bits_per_pixel");
	DeleteObject(wdev->himgpalette);
	gs_free((char *)(wdev->limgpalette), 1, sizeof(LOGPALETTE) +
		(1 << (wdev->color_info.depth)) * sizeof(PALETTEENTRY),
		"win_close");
    }
    return (0);
}

/* Map a r-g-b color to the colors available under Windows */
gx_color_index
win_map_rgb_color(gx_device * dev, gx_color_value r, gx_color_value g,
		  gx_color_value b)
{
    switch (wdev->BitsPerPixel) {
	case 24:
	    return (((unsigned long)b >> (gx_color_value_bits - 8)) << 16) +
		(((unsigned long)g >> (gx_color_value_bits - 8)) << 8) +
		(((unsigned long)r >> (gx_color_value_bits - 8)));
	case 16:{
		gx_color_index color = ((r >> (gx_color_value_bits - 5)) << 11) +
				       ((g >> (gx_color_value_bits - 6)) << 5) +
				       (b >> (gx_color_value_bits - 5));
#if arch_is_big_endian
		ushort color16 = (ushort)color;
#else
		ushort color16 = (ushort)((color << 8) | (color >> 8));
#endif
		return color16;
	    }
	case 15:{
		gx_color_index color = ((r >> (gx_color_value_bits - 5)) << 10) +
				       ((g >> (gx_color_value_bits - 5)) << 5) +
				       (b >> (gx_color_value_bits - 5));
#if arch_is_big_endian
		ushort color15 = (ushort)color;
#else
		ushort color15 = (ushort)((color << 8) | (color >> 8));
#endif
		return color15;
	    }
	case 8:{
		int i;
		LPLOGPALETTE lpal = wdev->limgpalette;
		PALETTEENTRY *pep;
		byte cr, cg, cb;
		int mc_index;
		byte mc_mask;

		/* Check for a color in the palette of 64. */
		{
		    static const byte pal64[32] =
		    {
			1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			1
		    };

		    if (pal64[r >> (gx_color_value_bits - 5)] &&
			pal64[g >> (gx_color_value_bits - 5)] &&
			pal64[b >> (gx_color_value_bits - 5)]
			)
			return (gx_color_index) (
				   ((r >> (gx_color_value_bits - 2)) << 4) +
				   ((g >> (gx_color_value_bits - 2)) << 2) +
					    (b >> (gx_color_value_bits - 2))
			    );
		}

		/* map colors to 0->255 in 32 steps */
		cr = win_color_value(r);
		cg = win_color_value(g);
		cb = win_color_value(b);

		/* Search in palette, skipping the first 64. */
		mc_index = ((cr >> 3) << 7) + ((cg >> 3) << 2) + (cb >> 6);
		mc_mask = 0x80 >> ((cb >> 3) & 7);
		if (wdev->mapped_color_flags[mc_index] & mc_mask)
		    for (i = wdev->nColors, pep = &lpal->palPalEntry[i];
			 --pep, --i >= 64;
			) {
			if (cr == pep->peRed &&
			    cg == pep->peGreen &&
			    cb == pep->peBlue
			    )
			    return ((gx_color_index) i);	/* found it */
		    }
		/* next try adding it to palette */
		i = wdev->nColors;
		if (i < 220) {	/* allow 36 for windows and other apps */
		    LPLOGPALETTE lipal = wdev->limgpalette;

		    wdev->nColors = i + 1;

		    DeleteObject(wdev->himgpalette);
		    lipal->palPalEntry[i].peFlags = 0;
		    lipal->palPalEntry[i].peRed = cr;
		    lipal->palPalEntry[i].peGreen = cg;
		    lipal->palPalEntry[i].peBlue = cb;
		    lipal->palNumEntries = wdev->nColors;
		    wdev->himgpalette = CreatePalette(lipal);

		    wdev->mapped_color_flags[mc_index] |= mc_mask;
		    return ((gx_color_index) i);	/* return new palette index */
		}
		return (gx_no_color_index);	/* not found - dither instead */
	    }
	case 4:
	    if ((r == g) && (g == b) && (r >= gx_max_color_value / 3 * 2 - 1)
		&& (r < gx_max_color_value / 4 * 3))
		return ((gx_color_index) 8);	/* light gray */
	    return pc_4bit_map_rgb_color(dev, r, g, b);
    }
    return (gx_default_map_rgb_color(dev, r, g, b));
}

/* Map a color code to r-g-b. */
int
win_map_color_rgb(gx_device * dev, gx_color_index color,
		  gx_color_value prgb[3])
{
    gx_color_value one;
    ushort value;

    switch (wdev->BitsPerPixel) {
	case 24:
	    one = (gx_color_value) (gx_max_color_value / 255);
	    prgb[0] = ((color) & 255) * one;
	    prgb[1] = ((color >> 8) & 255) * one;
	    prgb[2] = ((color >> 16) & 255) * one;
	    break;
	case 16:
	    value = (color >> 11) & 0x1f;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + (value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color >> 5) & 0x3f;
	    prgb[1] = ((value << 10) + (value << 4) + (value >> 2)) >> (16 - gx_color_value_bits);
	    value = (color) & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + (value >> 4)) >> (16 - gx_color_value_bits);
	    break;
	case 15:
	    value = (color >> 10) & 0x1f;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + (value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color >> 5) & 0x1f;
	    prgb[1] = ((value << 11) + (value << 6) + (value << 1) + (value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color) & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + (value >> 4)) >> (16 - gx_color_value_bits);
	    break;
	case 8:
	    if (!dev->is_open)
		return -1;
	    one = (gx_color_value) (gx_max_color_value / 255);
	    prgb[0] = wdev->limgpalette->palPalEntry[(int)color].peRed * one;
	    prgb[1] = wdev->limgpalette->palPalEntry[(int)color].peGreen * one;
	    prgb[2] = wdev->limgpalette->palPalEntry[(int)color].peBlue * one;
	    break;
	case 4:
	    if (color == 8)	/* VGA light gray */
		prgb[0] = prgb[1] = prgb[2] = (gx_max_color_value / 4 * 3);
	    else
		pc_4bit_map_color_rgb(dev, color, prgb);
	    break;
	default:
	    prgb[0] = prgb[1] = prgb[2] =
		(int)color ? gx_max_color_value : 0;
    }
    return 0;
}

/* Get Win parameters */
int
win_get_params(gx_device * dev, gs_param_list * plist)
{
    int code = gx_default_get_params(dev, plist);

    return code;
}

/* Put parameters. */
/* Set window parameters -- size and resolution. */
/* We implement this ourselves so that we can do it without */
/* closing and opening the device. */
int
win_put_params(gx_device * dev, gs_param_list * plist)
{
    int ecode = 0, code;
    bool is_open = dev->is_open;
    int width = dev->width;
    int height = dev->height;
    int old_bpp = dev->color_info.depth;
    int bpp = old_bpp;
    byte *old_flags = wdev->mapped_color_flags;

    /* Handle extra parameters */

    switch (code = param_read_int(plist, "BitsPerPixel", &bpp)) {
	case 0:
	    if (dev->is_open && bpp != old_bpp)
		ecode = gs_error_rangecheck;
	    else {		/* Don't release existing mapped_color_flags. */
		if (bpp != 8)
		    wdev->mapped_color_flags = 0;
		code = win_set_bits_per_pixel(wdev, bpp);
		if (code < 0)
		    ecode = code;
		else
		    break;
	    }
	    goto bppe;
	default:
	    ecode = code;
	  bppe:param_signal_error(plist, "BitsPerPixel", ecode);
	case 1:
	    break;
    }

    if (ecode >= 0) {		/* Prevent gx_default_put_params from closing the device. */
	dev->is_open = false;
	ecode = gx_default_put_params(dev, plist);
	dev->is_open = is_open;
    }
    if (ecode < 0) {		/* If we allocated mapped_color_flags, release it. */
	if (wdev->mapped_color_flags != 0 && old_flags == 0)
	    gs_free(wdev->mapped_color_flags, 4096, 1,
		    "win_put_params");
	wdev->mapped_color_flags = old_flags;
	if (bpp != old_bpp)
	    win_set_bits_per_pixel(wdev, old_bpp);
	return ecode;
    }
    if (wdev->mapped_color_flags == 0 && old_flags != 0) {	/* Release old mapped_color_flags. */
	gs_free(old_flags, 4096, 1, "win_put_params");
    }
    /* Hand off the change to the implementation. */
    if (is_open && (bpp != old_bpp ||
		    dev->width != width || dev->height != height)
	) {
	int ccode;

	(*wdev->free_bitmap) (wdev);
	ccode = (*wdev->alloc_bitmap) (wdev, (gx_device *) wdev);
	if (ccode < 0) {	/* Bad news!  Some of the other device parameters */
	    /* may have changed.  We don't handle this. */
	    /* This is ****** WRONG ******. */
	    dev->width = width;
	    dev->height = height;
	    win_set_bits_per_pixel(wdev, old_bpp);
	    (*wdev->alloc_bitmap) (wdev, dev);
	    return ccode;
	}
    }
    return 0;
}

/* ------ Internal routines ------ */

#undef wdev



/* out of memory error message box */
int
win_nomemory(void)
{
    MessageBox((HWND) NULL, (LPSTR) "Not enough memory", (LPSTR) szAppName, MB_ICONSTOP);
    return gs_error_limitcheck;
}


LPLOGPALETTE
win_makepalette(gx_device_win * wdev)
{
    int i, val;
    LPLOGPALETTE logpalette;

    logpalette = (LPLOGPALETTE) gs_malloc(1, sizeof(LOGPALETTE) +
		     (1 << (wdev->color_info.depth)) * sizeof(PALETTEENTRY),
					  "win_makepalette");
    if (logpalette == (LPLOGPALETTE) NULL)
	return (0);
    logpalette->palVersion = 0x300;
    logpalette->palNumEntries = wdev->nColors;
    for (i = 0; i < wdev->nColors; i++) {
	logpalette->palPalEntry[i].peFlags = 0;
	switch (wdev->nColors) {
	    case 64:
		/* colors are rrggbb */
		logpalette->palPalEntry[i].peRed = ((i & 0x30) >> 4) * 85;
		logpalette->palPalEntry[i].peGreen = ((i & 0xC) >> 2) * 85;
		logpalette->palPalEntry[i].peBlue = (i & 3) * 85;
		break;
	    case 16:
		/* colors are irgb */
		val = (i & 8 ? 255 : 128);
		logpalette->palPalEntry[i].peRed = i & 4 ? val : 0;
		logpalette->palPalEntry[i].peGreen = i & 2 ? val : 0;
		logpalette->palPalEntry[i].peBlue = i & 1 ? val : 0;
		if (i == 8) {	/* light gray */
		    logpalette->palPalEntry[i].peRed =
			logpalette->palPalEntry[i].peGreen =
			logpalette->palPalEntry[i].peBlue = 192;
		}
		break;
	    case 2:
		logpalette->palPalEntry[i].peRed =
		    logpalette->palPalEntry[i].peGreen =
		    logpalette->palPalEntry[i].peBlue = (i ? 255 : 0);
		break;
	}
    }
    return (logpalette);
}


private int
win_set_bits_per_pixel(gx_device_win * wdev, int bpp)
{
    static const gx_device_color_info win_24bit_color = dci_color(24, 255, 255);
    static const gx_device_color_info win_16bit_color = dci_color(16, 255, 255);
    static const gx_device_color_info win_8bit_color = dci_color(8, 31, 4);
    static const gx_device_color_info win_ega_color = dci_pc_4bit;
    static const gx_device_color_info win_vga_color = dci_pc_4bit;
    static const gx_device_color_info win_mono_color = dci_black_and_white;
    /* remember old anti_alias info */
    gx_device_anti_alias_info anti_alias = wdev->color_info.anti_alias;
    HDC hdc;

    switch (bpp) {
	case 24:
	    wdev->color_info = win_24bit_color;
	    wdev->nColors = -1;
	    break;
	case 16:
	case 15:
	    wdev->color_info = win_16bit_color;
	    wdev->nColors = -1;
	    break;
	case 8:
	    /* use 64 static colors and 166 dynamic colors from 8 planes */
	    wdev->color_info = win_8bit_color;
	    wdev->nColors = 64;
	    break;
	case 4:
	    hdc = GetDC(NULL);
	    if (GetDeviceCaps(hdc, VERTRES) <= 350)
		wdev->color_info = win_ega_color;
	    else
		wdev->color_info = win_vga_color;
	    ReleaseDC(NULL, hdc);
	    wdev->nColors = 16;
	    break;
	case 1:
	    wdev->color_info = win_mono_color;
	    wdev->nColors = 2;
	    break;
	default:
	    return (gs_error_rangecheck);
    }
    wdev->BitsPerPixel = bpp;

    /* If necessary, allocate and clear the mapped color flags. */
    if (bpp == 8) {
	if (wdev->mapped_color_flags == 0) {
	    wdev->mapped_color_flags = gs_malloc(4096, 1, "win_set_bits_per_pixel");
	    if (wdev->mapped_color_flags == 0)
		return_error(gs_error_VMerror);
	}
	memset(wdev->mapped_color_flags, 0, 4096);
    } else {
	gs_free(wdev->mapped_color_flags, 4096, 1, "win_set_bits_per_pixel");
	wdev->mapped_color_flags = 0;
    }
    /* restore old anti_alias info */
    wdev->color_info.anti_alias = anti_alias;
    return 0;
}
