/*
 * Copyright (c) 1999, 2000, 2001 Greg Haerr <greg@censoft.com>
 * Portions Copyright (c) 1991 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * Device-independent mid level screen device init routines
 *
 * These routines implement the smallest Microwindows engine level
 * interface to the screen driver.  By setting the NOFONTSORCLIPPING
 * config option, only these routines will be included, which can
 * be used to generate a low-level interface to the screen drivers
 * without dragging in any other GdXXX routines.
 */
#include <stdio.h>
#include <stdlib.h>
#include "device.h"
#include "swap.h"

#if MSDOS | ELKS
#define NOSTDPAL8
#endif

/*
 * The following define can change depending on the window manager
 * usage of colors and layout of the 8bpp palette devpal8.c.
 * Color entries below this value won't be overwritten by user
 * programs or bitmap display conversion tables.
 */
#define FIRSTUSERPALENTRY	24  /* first writable pal entry over 16 color*/

       MWPIXELVAL gr_foreground;      /* current foreground color */
       MWPIXELVAL gr_background;      /* current background color */
       MWBOOL 	gr_usebg;    	    /* TRUE if background drawn in pixmaps */
       int 	gr_mode = MWMODE_COPY; 	    /* drawing mode */
/*static*/ MWPALENTRY	gr_palette[256];    /* current palette*/
/*static*/ int	gr_firstuserpalentry;/* first user-changable palette entry*/
/*static*/ int 	gr_nextpalentry;    /* next available palette entry*/
static int	gr_pixtype;	    /* screen pixel format*/
static long	gr_ncolors;	    /* screen # colors*/

/*
 * Open low level graphics driver
 */
PSD
GdOpenScreen(void)
{
	PSD			psd;
	MWPALENTRY *		stdpal;
	MWSCREENINFO		sinfo;	    

	psd = scrdev.Open(&scrdev);
	if (!psd)
		return NULL;
	GdGetScreenInfo(psd, &sinfo);
	gr_pixtype = sinfo.pixtype;
	gr_ncolors = sinfo.ncolors;

	/* assume no user changable palette entries*/
	gr_firstuserpalentry = (int)psd->ncolors;

	/* set palette according to system colors and devpalX.c*/
	switch((int)psd->ncolors) {

#if !defined(NOSTDPAL1) /* don't require stdpal1 if not needed */
	case 2:		/* 1bpp*/
	{
		extern MWPALENTRY	mwstdpal1[2];
		stdpal = mwstdpal1;
	}
	break;
#endif

#if !defined(NOSTDPAL2) /* don't require stdpal2 if not needed */
	case 4:		/* 2bpp*/
	{
		extern MWPALENTRY	mwstdpal2[4];
		stdpal = mwstdpal2;
	}
	break;
#endif

#if !defined(NOSTDPAL4)
	/* don't require stdpal4 if not needed */
	case 8:		/* 3bpp - not fully supported*/
	case 16:	/* 4bpp*/
	{
		extern MWPALENTRY	mwstdpal4[16];
		stdpal = mwstdpal4;
	}
	break;
#endif

#if !defined(NOSTDPAL8) /* don't require large stdpal8 if not needed */
	case 256:	/* 8bpp*/
	{
		extern MWPALENTRY	mwstdpal8[256];
#if xxxALPHABLEND
		/* don't change uniform palette if alpha blending*/
		gr_firstuserpalentry = 256;
#else
		/* start after last system-reserved color*/
		gr_firstuserpalentry = FIRSTUSERPALENTRY;
#endif
		stdpal = mwstdpal8;
	} 
	break;
#endif	/* !defined(NOSTDPAL8)*/

	default:	/* truecolor*/
		/* no palette*/
		gr_firstuserpalentry = 0;
		stdpal = NULL;
	}

	/* reset next user palette entry, write hardware palette*/
	GdResetPalette();
	GdSetPalette(psd, 0, (int)psd->ncolors, stdpal);
#if xxxALPHABLEND
	/* one-time create alpha lookup table for 8bpp systems (takes ~1 sec)*/
	if(psd->ncolors == 256)
		init_alpha_lookup();
#endif

#if !NOFONTSORCLIPPING
	/* init local vars*/
	GdSetMode(MWMODE_COPY);
	GdSetForeground(GdFindColor(MWRGB(255, 255, 255)));	/* WHITE*/
	GdSetBackground(GdFindColor(MWRGB(0, 0, 0)));		/* BLACK*/
	GdSetUseBackground(TRUE);
	GdSetFont(GdCreateFont(psd, MWFONT_SYSTEM_VAR, 0, NULL));
#if DYNAMICREGIONS
	GdSetClipRegion(psd, 
		GdAllocRectRegion(0, 0, psd->xvirtres, psd->yvirtres));
#else
	GdSetClipRects(psd, 0, NULL);
#endif /* DYNAMICREGIONS*/
#endif /* NOFONTSORCLIPPING*/

	/* fill black (actually fill to first palette entry or truecolor 0*/
	psd->FillRect(psd, 0, 0, psd->xvirtres-1, psd->yvirtres-1, 0);
	return psd;
}

/*
 * Close low level graphics driver
 */
void 
GdCloseScreen(PSD psd)
{
	psd->Close(psd);
}

/* Set dynamic screen portrait mode, return new mode*/
int
GdSetPortraitMode(PSD psd, int portraitmode)
{
	/* set portrait mode if supported*/
	if (psd->SetPortrait)
		psd->SetPortrait(psd, portraitmode);
	return psd->portrait;
}

/*
 * Return about the screen.
 */
void
GdGetScreenInfo(PSD psd, PMWSCREENINFO psi)
{
	psd->GetScreenInfo(psd, psi);
	GdGetButtonInfo(&psi->buttons);
	GdGetModifierInfo(&psi->modifiers, NULL);
	GdGetCursorPos(&psi->xpos, &psi->ypos);
}

/* reset palette to empty except for system colors*/
void
GdResetPalette(void)
{
	/* note: when palette entries are changed, all 
	 * windows may need to be redrawn
	 */
	gr_nextpalentry = gr_firstuserpalentry;
}

/* set the system palette section to the passed palette entries*/
void
GdSetPalette(PSD psd, int first, int count, MWPALENTRY *palette)
{
	int	i;

	/* no palette management needed if running truecolor*/
	if(psd->pixtype != MWPF_PALETTE)
		return;

	/* bounds check against # of device color entries*/
	if(first + count > (int)psd->ncolors)
		count = (int)psd->ncolors - first;
	if(count >= 0 && first < (int)psd->ncolors) {
		psd->SetPalette(psd, first, count, palette);

		/* copy palette for GdFind*Color*/
		for(i=0; i<count; ++i)
			gr_palette[i+first] = palette[i];
	}
}

/* get system palette entries, return count*/
int
GdGetPalette(PSD psd, int first, int count, MWPALENTRY *palette)
{
	int	i;

	/* no palette if running truecolor*/
	if(psd->pixtype != MWPF_PALETTE)
		return 0;

	/* bounds check against # of device color entries*/
	if(first + count > (int)psd->ncolors)
		if( (count = (int)psd->ncolors - first) <= 0)
			return 0;

	for(i=0; i<count; ++i)
		*palette++ = gr_palette[i+first];

	return count;
}

/*
 * Convert a palette-independent value to a hardware color
 */
MWPIXELVAL
GdFindColor(MWCOLORVAL c)
{
	/*
	 * Handle truecolor displays.  Note that the MWF_PALINDEX
	 * bit is ignored when running truecolor drivers.
	 */
	switch(gr_pixtype) {
	case MWPF_TRUECOLOR0888:
	case MWPF_TRUECOLOR888:
		/* create 24 bit 8/8/8 pixel (0x00RRGGBB) from RGB colorval*/
		/*RGB2PIXEL888(REDVALUE(c), GREENVALUE(c), BLUEVALUE(c))*/
		return COLOR2PIXEL888(c);

	case MWPF_TRUECOLOR565:
		/* create 16 bit 5/6/5 format pixel from RGB colorval*/
		/*RGB2PIXEL565(REDVALUE(c), GREENVALUE(c), BLUEVALUE(c))*/
		return COLOR2PIXEL565(c);

	case MWPF_TRUECOLOR555:
		/* create 16 bit 5/5/5 format pixel from RGB colorval*/
		/*RGB2PIXEL555(REDVALUE(c), GREENVALUE(c), BLUEVALUE(c))*/
		return COLOR2PIXEL555(c);

	case MWPF_TRUECOLOR332:
		/* create 8 bit 3/3/2 format pixel from RGB colorval*/
		/*RGB2PIXEL332(REDVALUE(c), GREENVALUE(c), BLUEVALUE(c))*/
		return COLOR2PIXEL332(c);

	case MWPF_TRUECOLOR233:
		/* create 8 bit 2/3/3 format pixel from RGB colorval*/
		/*RGB2PIXEL332(REDVALUE(c), GREENVALUE(c), BLUEVALUE(c))*/
		return COLOR2PIXEL233(c);
	}

	/* case MWPF_PALETTE: must be running 1, 2, 4 or 8 bit palette*/

	/*
	 * Check if color is a palette index.  Note that the index
	 * isn't error checked against the system palette, for speed.
	 */
	if(c & MWF_PALINDEX)
		return (c & 0xff);

	/* search palette for closest match*/
	return GdFindNearestColor(gr_palette, (int)gr_ncolors, c);
}

/*
 * Search a palette to find the nearest color requested.
 * Uses a weighted squares comparison.
 */
MWPIXELVAL
GdFindNearestColor(MWPALENTRY *pal, int size, MWCOLORVAL cr)
{
	MWPALENTRY *	rgb;
	int		r, g, b;
	int		R, G, B;
	long		diff = 0x7fffffffL;
	long		sq;
	int		best = 0;

	r = REDVALUE(cr);
	g = GREENVALUE(cr);
	b = BLUEVALUE(cr);
	for(rgb=pal; diff && rgb < &pal[size]; ++rgb) {
		R = rgb->r - r;
		G = rgb->g - g;
		B = rgb->b - b;
#if 1
		/* speedy linear distance method*/
		sq = abs(R) + abs(G) + abs(B);
#else
		/* slower distance-cubed with luminance adjustment*/
		/* gray is .30R + .59G + .11B*/
		/* = (R*77 + G*151 + B*28)/256*/
		sq = (long)R*R*30*30 + (long)G*G*59*59 + (long)B*B*11*11;
#endif

		if(sq < diff) {
			best = rgb - pal;
			if((diff = sq) == 0)
				return best;
		}
	}
	return best;
}

#if !VXWORKS
#include <unistd.h>
#include <fcntl.h>
/*
 * Create .bmp file from framebuffer data
 *
 * 1, 4, 8, 16, 24 and 32 bpp supported
 */
#define BI_RGB		0L
#define BI_RLE8		1L
#define BI_RLE4		2L
#define BI_BITFIELDS	3L

typedef unsigned char	BYTE;
typedef unsigned short	WORD;
typedef unsigned long	DWORD;
typedef long		LONG;

#pragma pack(1)
/* windows style bmp*/
typedef struct {
	/* BITMAPFILEHEADER*/
	BYTE	bfType[2];
	DWORD	bfSize;
	WORD	bfReserved1;
	WORD	bfReserved2;
	DWORD	bfOffBits;
	/* BITMAPINFOHEADER*/
	DWORD	BiSize;
	LONG	BiWidth;
	LONG	BiHeight;
	WORD	BiPlanes;
	WORD	BiBitCount;
	DWORD	BiCompression;
	DWORD	BiSizeImage;
	LONG	BiXpelsPerMeter;
	LONG	BiYpelsPerMeter;
	DWORD	BiClrUsed;
	DWORD	BiClrImportant;
} BMPHEAD;
#pragma pack()

/* r/g/b masks for non-palette bitmaps*/
#define RMASK332	0xe0
#define GMASK332	0x1c
#define BMASK332	0x03
#define RMASK233	0x07
#define GMASK233	0x38
#define BMASK233	0xC0
#define RMASK555	0x7c00
#define GMASK555	0x03e0
#define BMASK555	0x001f
#define RMASK565	0xf800
#define GMASK565	0x07e0
#define BMASK565	0x001f
#define RMASK888	0xff0000
#define GMASK888	0x00ff00
#define BMASK888	0x0000ff

#if defined(HAVE_FILEIO)
static void
putsw(unsigned long dw, FILE *ofp)
{
	/* little-endian storage of shortword*/
	putc((unsigned char)dw, ofp);
	dw >>= 8;
	putc((unsigned char)dw, ofp);
}

static void
putdw(unsigned long dw, FILE *ofp)
{
	/* little-endian storage of longword*/
	putc((unsigned char)dw, ofp);
	dw >>= 8;
	putc((unsigned char)dw, ofp);
	dw >>= 8;
	putc((unsigned char)dw, ofp);
	dw >>= 8;
	putc((unsigned char)dw, ofp);
}
#endif /* HAVE_FILEIO*/

/* create .bmp file from framebuffer data*/
int
GdCaptureScreen(char *path)
{
#if defined(HAVE_FILEIO)
	int	ifd, i, j;
	FILE *	ofp;
	int	cx, cy, extra, bpp, bytespp, ncolors, sizecolortable;
	unsigned long rmask, gmask, bmask;
	unsigned char *cptr;
	unsigned short *sptr;
	unsigned long *lptr;
	BMPHEAD	bmp;
	unsigned char buf[2048*4];

	ofp = fopen(path, "wb");
	if (!ofp)
		return 1;
	ifd = open("/dev/fb0", 0);

	cx = scrdev.xvirtres;
	cy = scrdev.yvirtres;
	bpp = scrdev.bpp;
	bytespp = (bpp+7)/8;

	/* dword right padded*/
	extra = (cx*bytespp) & 3;
	if (extra)
		extra = 4 - extra;
	ncolors = (bpp <= 8)? (1<<bpp): 0;
	/* color table is either palette or 3 longword r/g/b masks*/
	sizecolortable = ncolors? ncolors*4: 3*4;
	if (bpp == 24)
		sizecolortable = 0;	/* special case 24bpp has no table*/

	/* fill out bmp header*/
	memset(&bmp, 0, sizeof(bmp));
	bmp.bfType[0] = 'B';
	bmp.bfType[1] = 'M';
	bmp.bfSize = dwswap(sizeof(bmp) + sizecolortable + (long)(cx+extra)*cy*bytespp);
	bmp.bfOffBits = dwswap(sizeof(bmp) + sizecolortable);
	bmp.BiSize = dwswap(40);
	bmp.BiWidth = dwswap(cx);
	bmp.BiHeight = dwswap(cy);
	bmp.BiPlanes = wswap(1);
	bmp.BiBitCount = wswap(bpp);
	bmp.BiCompression = dwswap((bpp==16 || bpp==32)? BI_BITFIELDS: BI_RGB);
	bmp.BiSizeImage = dwswap((long)(cx+extra)*cy*bytespp);
	bmp.BiClrUsed = dwswap((bpp <= 8)? ncolors: 0);
	/*bmp.BiClrImportant = 0;*/

	/* write header*/
	fwrite(&bmp, sizeof(bmp), 1, ofp);

	/* write colortable*/
	if (sizecolortable) {
		if(bpp <= 8) {
			/* write palette*/
			for(i=0; i<ncolors; i++) {
				putc(gr_palette[i].b, ofp);
				putc(gr_palette[i].g, ofp);
				putc(gr_palette[i].r, ofp);
				putc(0, ofp);
			}
		} else {
			/* write 3 r/g/b masks*/
			switch (gr_pixtype) {
			case MWPF_TRUECOLOR0888:
			default:
				rmask = RMASK888;
				gmask = GMASK888;
				bmask = BMASK888;
				break;
			case MWPF_TRUECOLOR565:
				rmask = RMASK565;
				gmask = GMASK565;
				bmask = BMASK565;
				break;
			case MWPF_TRUECOLOR555:
				rmask = RMASK555;
				gmask = GMASK555;
				bmask = BMASK555;
				break;
			case MWPF_TRUECOLOR332:
				rmask = RMASK332;
				gmask = GMASK332;
				bmask = BMASK332;
				break;
			case MWPF_TRUECOLOR233:
				rmask = RMASK233;
				gmask = GMASK233;
				bmask = BMASK233;
				break;
			}
			putdw(rmask, ofp);
			putdw(gmask, ofp);
			putdw(bmask, ofp);
		}
	}

	/* write image data, upside down ;)*/
	for(i=cy-1; i>=0; --i) {
		long base = sizeof(bmp) + sizecolortable + (long)i*cx*bytespp;
		fseek(ofp, base, SEEK_SET);
		read(ifd, buf, cx*bytespp);
		switch (bpp) {
		case 32:
			lptr = (unsigned long *)buf;
			for(j=0; j<cx; ++j)
				putdw(*lptr++, ofp);
			break;
		case 24:
			cptr = (unsigned char *)buf;
			for(j=0; j<cx; ++j) {
				putc(*cptr++, ofp);
				putc(*cptr++, ofp);
				putc(*cptr++, ofp);
			}
			break;
		case 16:
			sptr = (unsigned short *)buf;
			for(j=0; j<cx; ++j)
				putsw(*sptr++, ofp);
			break;
		default:
			cptr = (unsigned char *)buf;
			for(j=0; j<cx; ++j)
				putc(*cptr++, ofp);
			break;
		}
		for(j=0; j<extra; ++j)
			putc(0, ofp);		/* DWORD pad each line*/
	}

	fclose(ofp);
	close(ifd);
#endif /* HAVE_FILEIO*/
	return 0;
}
#endif /* !VXWORKS*/
