/**
 ** colors.c ---- color management functions
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu]
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** 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.
 **
 **/

#include "libgrx.h"
#include "memcopy.h"
#include "memfill.h"

static void (*DACload)(int c,int r,int g,int b) = NULL;

static void loadcolor(int c,int r,int g,int b)
{
	CLRINFO->ctable[c].r = (r &= CLRINFO->mask[0]);
	CLRINFO->ctable[c].g = (g &= CLRINFO->mask[1]);
	CLRINFO->ctable[c].b = (b &= CLRINFO->mask[2]);
	if(DACload) (*DACload)(c,r,g,b);
}

static void setbits(char *prec,char *pos)
{
	int i,tmp;
	CLRINFO->norm = 0;
	for(i = 0; i < 3; i++,prec++,pos++) {
	    CLRINFO->prec[i]  = *prec;
	    CLRINFO->pos[i]   = *prec + *pos - 1;
	    CLRINFO->mask[i]  = (tmp = 0xff ^ (0xff >> *prec));
	    CLRINFO->round[i] = (tmp >> 1) & ~tmp;
	    CLRINFO->shift[i] = (tmp = CLRINFO->pos[i] - 7);
	    CLRINFO->norm     = imax(CLRINFO->norm,(-tmp));
	}
	CLRINFO->shift[0] += CLRINFO->norm;
	CLRINFO->shift[1] += CLRINFO->norm;
	CLRINFO->shift[2] += CLRINFO->norm;
}

void GrRefreshColors(void)
{
	int i;
	for(i = 0; i < (int)CLRINFO->ncolors; i++) {
	    if(CLRINFO->ctable[i].defined) loadcolor(
		(int)(i),
		CLRINFO->ctable[i].r,
		CLRINFO->ctable[i].g,
		CLRINFO->ctable[i].b
	    );
	}
}

/* _GR_firstFreeColor is normally zero but some systems may protect some
** colors for other programs (eg. X11). In this case we don't touch them.
** These variables are only used in palette modes
*/
#ifdef __XWIN__
int _GR_firstFreeColor =  0;
int _GR_lastFreeColor  = -1;
#else
#define _GR_firstFreeColor 0
#endif

int _GrResetColors(void)
{
#       define NSAVED 16
	static char *infosave = NULL;
	static int  firsttime = TRUE;
	int i;
	if(firsttime) {
		infosave = malloc(offsetof(struct _GR_colorInfo,ctable[NSAVED]));
		if ( infosave ) {
			memcpy(infosave,CLRINFO,offsetof(struct _GR_colorInfo,ctable[NSAVED]));
		}
	    firsttime = FALSE;
	}
	sttzero(CLRINFO);
	if(DRVINFO->actmode.extinfo->mode == GR_frameText) {
		if ( infosave ) {
//			memcpy(CLRINFO,infosave,sizeof(infosave));
			memcpy(CLRINFO,infosave,offsetof(struct _GR_colorInfo,ctable[NSAVED]));
			return TRUE;
		}
		return FALSE;
	}
	DACload = DRVINFO->actmode.extinfo->loadcolor;
	CLRINFO->black   = GrNOCOLOR;
	CLRINFO->white   = GrNOCOLOR;
	CLRINFO->ncolors = DRVINFO->actmode.bpp>=32 ? 0 : (1L << DRVINFO->actmode.bpp);
	if ( ((CLRINFO->ncolors-1)&GrCVALUEMASK) != (CLRINFO->ncolors-1) ) {
	    /* can happen on 32bpp systems. */
	    int cbpp = 0;
	    for(i=0; i < 3; ++i)
		cbpp += DRVINFO->actmode.extinfo->cprec[i];
	    CLRINFO->ncolors = 1L << cbpp;
	}
	setbits(
	    DRVINFO->actmode.extinfo->cprec,
	    DRVINFO->actmode.extinfo->cpos
	);
	switch(DRVINFO->actmode.bpp) {
	  case 4:
	  case 8:
#ifdef __XWIN__
	    if (_GR_lastFreeColor >= _GR_firstFreeColor)
	      CLRINFO->nfree = _GR_lastFreeColor - _GR_firstFreeColor + 1;
	    else
#endif
	      CLRINFO->nfree = CLRINFO->ncolors - _GR_firstFreeColor;
		if ( infosave ) {
	      for(i = 0; i < NSAVED; i++) {
		  loadcolor(
		      (i + _GR_firstFreeColor),
		      ((struct _GR_colorInfo *)(infosave))->ctable[i].r,
		      ((struct _GR_colorInfo *)(infosave))->ctable[i].g,
		      ((struct _GR_colorInfo *)(infosave))->ctable[i].b
		  );
		  CLRINFO->ctable[i].defined = TRUE;
		  }
	    }
	    break;
	  default:
	    CLRINFO->RGBmode = TRUE;
	    break;
	}
	return ((CLRINFO->ncolors-1)&GrCVALUEMASK) == CLRINFO->ncolors-1;
}
 
void GrResetColors(void)
{
	_GrResetColors();
}

void GrSetRGBcolorMode(void)
{
	if(!CLRINFO->RGBmode) {
	    GrColor c;
	    switch(CLRINFO->ncolors) {
		case 16L:  setbits("\1\2\1","\3\1\0"); break;
		case 256L: setbits("\3\3\2","\5\2\0"); break;
		default:   return;
	    }
	    CLRINFO->RGBmode = TRUE;
	    CLRINFO->nfree   = 0L;
	    CLRINFO->black   = 0L;
	    CLRINFO->white   = CLRINFO->ncolors - 1L;
	    for(c = 0; c < CLRINFO->ncolors; c++) loadcolor(
		(int)(c),
		(int)GrRGBcolorRed(c),
		(int)GrRGBcolorGreen(c),
		(int)GrRGBcolorBlue(c)
	    );
	}
}

#define ROUNDCOLORCOMP(x,n) (                                   \
    ((unsigned int)(x) >= CLRINFO->mask[n]) ?                   \
	CLRINFO->mask[n] :                                      \
	(((x) + CLRINFO->round[n]) & CLRINFO->mask[n])          \
)

GrColor GrAllocColor(int r,int g,int b)
{
        GrColor res;

        GRX_ENTER();
        res = GrNOCOLOR;
	r = ROUNDCOLORCOMP(r,0);
	g = ROUNDCOLORCOMP(g,1);
	b = ROUNDCOLORCOMP(b,2);
	if(CLRINFO->RGBmode) {
            res = GrBuildRGBcolorT(r,g,b);
	}
	else {
            GR_int32u minerr = 1000;
	    int i;
	    int free_ = (-1),allfree = (-1),best = (-1);
	    int ndef = (int)CLRINFO->ncolors - (int)CLRINFO->nfree;
            DBGPRINTF(DBG_COLOR,("Allocating color: r=%d, g=%d, b=%d\n",r,g,b));
	    for(i = 0; i < (int)CLRINFO->ncolors; i++) {
		if(CLRINFO->ctable[i].defined) {
		    if(!CLRINFO->ctable[i].writable) {
                        GR_int32u err = 0;
                        GR_int16u colerr;
                        colerr = iabs(r - CLRINFO->ctable[i].r);
                        colerr = colerr * colerr;
                        err += colerr;
                        colerr = iabs(g - CLRINFO->ctable[i].g);
                        colerr = colerr * colerr;
                        err += colerr;
                        colerr = iabs(b - CLRINFO->ctable[i].b);
                        colerr = colerr * colerr;
                        err += colerr;
			if(err < minerr) {
                            DBGPRINTF(DBG_COLOR,("New best color %d (err=%ld): r=%d, g=%d, b=%d\n", i, err, \
                              (int)CLRINFO->ctable[i].r,(int)CLRINFO->ctable[i].g,(int)CLRINFO->ctable[i].b));
			    best = i;
			    if((minerr = err) == 0) goto foundbest;
			}
			if((free_ <= 0) && !CLRINFO->ctable[i].nused) {
                            DBGPRINTF(DBG_COLOR,("First free color: r=%d\n", i));
			    free_ = i;
			}
		    }
		    if(CLRINFO->ctable[i].nused) ndef--;
		}
		else {
		    if(allfree < 0) allfree = i;
		}
		if((allfree >= 0) && (ndef <= 0)) {
                    DBGPRINTF(DBG_COLOR,("Found a usable color: allfree = %d, ndef = %d\n", allfree, ndef));
		    break;
		}
	    }
	    if(allfree >= 0) {
                DBGPRINTF(DBG_COLOR,("Using %d as free color (free=%d)\n", allfree, free_));
		free_ = allfree;
	    }
	    if(free_ >= 0) {
                DBGPRINTF(DBG_COLOR,("Allocating %d\n", free_));
		CLRINFO->ctable[free_].defined  = TRUE;
		CLRINFO->ctable[free_].writable = FALSE;
		CLRINFO->ctable[free_].nused    = 1;
		CLRINFO->nfree--;
		loadcolor(free_,r,g,b);
		res = free_;
                goto done;
	    }
	  foundbest:
	    if(best >= 0) {
                DBGPRINTF(DBG_COLOR,("Using best %d\n", best));
		if(!CLRINFO->ctable[best].nused) CLRINFO->nfree--;
		CLRINFO->ctable[best].nused++;
		res = best;
                goto done;
	    }
	}
done:   GRX_RETURN(res);
}

GrColor GrAllocCell(void)
{
	if(!CLRINFO->RGBmode && CLRINFO->nfree) {
	    int i,free_ = (-1);
	    for(i = 0; i < (int)CLRINFO->ncolors; i++) {
		if(!CLRINFO->ctable[i].defined) {
		    free_ = i;
		    break;
		}
		if(!CLRINFO->ctable[i].nused) {
		    if(free_ < 0) free_ = i;
		}
	    }
	    if(free_ >= 0) {
		CLRINFO->ctable[free_].defined  = TRUE;
		CLRINFO->ctable[free_].writable = TRUE;
		CLRINFO->ctable[free_].nused    = 1;
		CLRINFO->nfree--;
		loadcolor(free_,0,0,0);
		return((GrColor)(free_));
	    }
	}
	return(GrNOCOLOR);
}

void GrFreeColor(GrColor c)
{
	if(!CLRINFO->RGBmode && ((GrColor)(c) < CLRINFO->ncolors) &&
	    !CLRINFO->ctable[(int)(c)].writable &&
	    CLRINFO->ctable[(int)(c)].defined &&
	    (--CLRINFO->ctable[(int)(c)].nused == 0)) {
		CLRINFO->nfree++;
		CLRINFO->ctable[(int)(c)].defined  = FALSE;
		CLRINFO->ctable[(int)(c)].writable = FALSE;
		CLRINFO->ctable[(int)(c)].nused    = 0;
	    }
}

void GrFreeCell(GrColor c)
{
        GRX_ENTER();
	if(!CLRINFO->RGBmode && ((GrColor)(c) < CLRINFO->ncolors)) {
	    if(CLRINFO->ctable[(int)(c)].writable) {
		CLRINFO->nfree++;
		CLRINFO->ctable[(int)(c)].defined  = FALSE;
		CLRINFO->ctable[(int)(c)].writable = FALSE;
		CLRINFO->ctable[(int)(c)].nused    = 0;
	    }
	}
        GRX_LEAVE();
}

void GrSetColor(GrColor c,int r,int g,int b)
{
        GRX_ENTER();
	if(!CLRINFO->RGBmode && ((GrColor)(c) < CLRINFO->ncolors)) {
	    if(!CLRINFO->ctable[(int)(c)].defined) {
		CLRINFO->ctable[(int)(c)].defined  = TRUE;
		CLRINFO->ctable[(int)(c)].nused    = 0;
	    }
	    if(!CLRINFO->ctable[(int)(c)].nused) {
		CLRINFO->ctable[(int)(c)].writable = TRUE;
		CLRINFO->ctable[(int)(c)].nused    = 1;
		CLRINFO->nfree--;
	    }
	    if(CLRINFO->ctable[(int)(c)].writable) loadcolor(
		(int)(c),
		(int)ROUNDCOLORCOMP(r,0),
		(int)ROUNDCOLORCOMP(g,1),
		(int)ROUNDCOLORCOMP(b,2)
	    );
	}
        GRX_LEAVE();
}

void GrQueryColor(GrColor c,int *r,int *g,int *b)
{
        GRX_ENTER();
	GrQueryColorID(c,r,g,b);
        GRX_LEAVE();
}

void GrQueryColor2(GrColor c,long *hcolor)
{
        GRX_ENTER();
        GrQueryColor2ID(c,hcolor);
        GRX_LEAVE();
}

#define CSAVE_MAGIC     0x7abf5698UL

typedef struct {
    GrColor magic;
    GrColor nc;
    struct _GR_colorInfo info;
} colorsave;

int GrColorSaveBufferSize(void)
{
	return(sizeof(colorsave));
}

void GrSaveColors(void *buffer)
{
	colorsave *cp = (colorsave *)buffer;
	cp->magic = CSAVE_MAGIC;
	cp->nc    = GrNumColors();
	sttcopy(&cp->info,CLRINFO);
}

void GrRestoreColors(void *buffer)
{
	colorsave *cp = (colorsave *)buffer;
	if((cp->magic == CSAVE_MAGIC) && (cp->nc == GrNumColors())) {
	    sttcopy(CLRINFO,&cp->info);
	    GrRefreshColors();
	}
}

