/*
 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xcms.h>
#include <X11/Xmu/XmuSolaris.h>
#include "Xcmsint.h"
#include "Cv.h"

#define XSOLARIS_STD_GAMMA	2.22

#define MAX_SAMPLES            	16

/*
** The following three routines are derived from code in bin/xcmsdb/xcmsdb.c.
*/

static int
QueryTableType0 (unsigned int maxcolor, int format, char **pChar, unsigned long *pCount, 
		 unsigned int *nelem, unsigned short **pph, unsigned int **ppf)
{
    unsigned int nElements;
    unsigned short *ph = NULL;
    unsigned int   *pf = NULL;

    nElements = _XcmsGetElement(format, pChar, pCount) + 1;
    *nelem = nElements;
    if (!(ph = (unsigned short *) malloc(nElements * sizeof(unsigned short))) ||
        !(pf = (unsigned int *) malloc(nElements * sizeof(unsigned int)))) {
	goto Bad;
    }
    *ppf = pf;
    *pph = ph;

    switch (format) {

      case 8:
	while (nElements--) {
	    /* 0xFFFF/0xFF = 0x101 */
	    *ph++ = _XcmsGetElement (format, pChar, pCount) * 0x101;
	    *pf++ = (_XcmsGetElement (format, pChar, pCount)
		    / (XcmsFloat)255.0) * maxcolor;
	}
	break;

      case 16:
	while (nElements--) {
	    *ph++ = (unsigned short)_XcmsGetElement (format, pChar, pCount);
	    *pf++ = (_XcmsGetElement (format, pChar, pCount)
		    / (XcmsFloat)65535.0) * maxcolor;
	}
	break;

      case 32:
	while (nElements--) {
	    *ph++ = (unsigned short)_XcmsGetElement (format, pChar, pCount);
	    *pf++ = (_XcmsGetElement (format, pChar, pCount)
	            / (XcmsFloat)4294967295.0) * maxcolor;
	}
	break;

      default:
	goto Bad;
    }

    return (1);

Bad:
    if (ph) { 
	free(ph);
    }
    if (pf) {
	free(pf);
    }
    *pph = NULL;
    *ppf = NULL;
    return (0);
}

static int
QueryTableType1 (unsigned int maxcolor, int format, char **pChar, unsigned long *pCount, 
		 unsigned int *nelem, unsigned short **pph, unsigned int **ppf)
{
    int count;
    unsigned int max_index;
    unsigned int nElements;
    unsigned short *ph = NULL;
    unsigned int   *pf = NULL;

    max_index = _XcmsGetElement(format, pChar, pCount);
    nElements = max_index + 1;
    *nelem = nElements;

    if (!(ph = (unsigned short *) malloc(nElements * sizeof(unsigned short))) ||
        !(pf = (unsigned int *) malloc(nElements * sizeof(unsigned int)))) {
	goto Bad;
    }
    *ppf = pf;
    *pph = ph;

    switch (format) {

      case 8:
	for (count = 0; count < nElements; count++) {
	    *ph++ = count;
	    *pf++ = ((XcmsFloat) _XcmsGetElement(format, pChar, pCount) / (XcmsFloat)255.0) * 
		     (XcmsFloat)maxcolor;
	}
	break;

      case 16:
	for (count = 0; count < nElements; count++) {
	    *ph++ = count;
	    *pf++ = ((XcmsFloat) _XcmsGetElement(format, pChar, pCount) / (XcmsFloat)65535.0) * 
	            (XcmsFloat)maxcolor;
	}
	break;

      case 32:
	for (count = 0; count < nElements; count++) {
	    *ph++ = count;
	    *pf++ = ((XcmsFloat) _XcmsGetElement (format, pChar, pCount) / (XcmsFloat)4294967295.0) 
	            * (XcmsFloat)maxcolor;
	}
	break;

      default:
	goto Bad;
    }

    return (1);

Bad:
    if (ph) { 
	free(ph);
    }
    if (pf) {
	free(pf);
    }
    *pph = NULL;
    *ppf = NULL;
    return (0);
}


/*
** If the given n pairs (hi, fi) approximate a power function with the
** relation h = f**exp, the exponent exp is returned.
*/

static double
exponentOfPowerFunc (unsigned int maxh, unsigned int maxf, int n, unsigned short *ph, 
		     unsigned int *pf)
{
    unsigned short h;
    unsigned int   f;
    double logh, logf;
    int    i, nsamples, incr;
    double sum, logmaxh, logmaxf, denom;

    incr = n / MAX_SAMPLES;
    if (incr < 1) {
	incr = 1;
    }
    logmaxh = log((double)maxh);
    logmaxf = log((double)maxf);
    sum = 0;
    nsamples = 0;
    
    for (i = (incr>>1); i < n; i += incr) {
	h = ph[i];
	f = pf[i];
	if (h == 0 || f == 0) {
	    continue;
	}
	logh = log((double) h);
	logf = log((double) f);
	denom = logh - logmaxh;
	if (denom) {
	    sum += (logf - logmaxf) / denom;
	    nsamples++;
	}
    }

    if (nsamples == 0) {
	/* Note: as good as anything else */
	return(XSOLARIS_STD_GAMMA);
    } else {
	return(sum/nsamples);
    }
}


/* 
** Updates the current prop element pointer to point beyond the current channel.
** Upon entry, pChar should point to the length element of the channel.
**
** Returns 0 if the property contents are invalid, 1 otherwise.
*/

static int
skipChannel (int cType, int format, char **pChar, unsigned long *pCount)
{
    unsigned int nElements, length;

    if ((long)*pCount <= 0) {
	return (0);
    }
    length = _XcmsGetElement(format, pChar, pCount) + 1;
    
    nElements = ((cType == 0) ? 2 : 1) * length;
    if ((long)nElements > *pCount) {
	return (0);
    }

    while (nElements--) {
	(void) _XcmsGetElement (format, pChar, pCount);
    }

    return (1);
}

/* 
** Updates the current prop element pointer to point beyond the current visual.
** Upon entry, pChar should point to the type element immediately following
** the visualID elemnt.
**
** Returns 0 if the property contents are invalid, 1 otherwise.
*/

static int
skipVisual (int format, char **pChar, unsigned long *pCount)
{
    int cType, nTables;

    /* Get table type */
    if ((long)*pCount <= 0) {
	return (0);
    }
    cType = (int)_XcmsGetElement(format, pChar, pCount);
    if (cType != 0 && cType != 1) {
	return (0);
    }

    /* Get number of channels */
    if ((long)*pCount <= 0) {
	return (0);
    }
    nTables = (int)_XcmsGetElement(format, pChar, pCount);
    if (nTables != 1 && nTables != 3) {
	return (0);
    }

    /* Skip red channel */
    if (!skipChannel(cType, format, pChar, pCount)) {
	return (0);
    }

    if (nTables > 1) {

	/* Skip green channel */
	if (!skipChannel(cType, format, pChar, pCount)) {
	    return (0);
	}

	/* Skip blue channel */
	if (!skipChannel(cType, format, pChar, pCount)) {
	    return (0);
	}
    }

    return (1);
}


static int XSolarisGetVisualGammaCalledFlag = 0;

int
XSolarisGetVisualGammaCalled (void)
{
    return (XSolarisGetVisualGammaCalledFlag);
}

/*
** Note: this function ignores the green and blue intensity correction
** data; it assumes that these are the same as the red channel.
*/

Status
XSolarisGetVisualGamma (Display *dpy, int screen_number, Visual *visual,
			double *gamma)
{
    char *property_return = NULL, *pChar;
    int  count, format, cType, nTables;
    unsigned long nitems = 0;
    unsigned long nbytes_return;
    Atom CorrectAtom;
    VisualID visualID;
    unsigned int maxcolor, nelem;
    unsigned short *ph;
    unsigned int   *pf;

    XSolarisGetVisualGammaCalledFlag = 1;

    /*
     * Get Intensity Tables
     */
    CorrectAtom = XInternAtom (dpy, XDCCC_CORRECT_ATOM_NAME, False);
    if (CorrectAtom != None) {
	if (_XcmsGetProperty (dpy, XRootWindow(dpy, screen_number),
			      CorrectAtom, &format, &nitems, &nbytes_return,
			      &property_return) == XcmsFailure) {

	    *gamma = XSOLARIS_STD_GAMMA;
	    return (Success);

	} else if ((long)nitems <= 0) {

	    /* use standard gamma */
	    if (property_return) {
		XFree (property_return);
	    }

	    *gamma = XSOLARIS_STD_GAMMA;
	    return (Success);
	}
    }

    pChar = property_return;

    /* Note: nitems is decremented as a side-effect of calling getElement */
    while (nitems) {
	
	switch (format) {

	case 8:

	    /*
	     * Must have at least:
	     *		VisualID0
	     *		VisualID1
	     *		VisualID2
	     *		VisualID3
	     *		type
	     *		count
	     *		length
	     *		intensity1
	     *		intensity2
	     */
	    if (nitems < 9) {
		XFree (property_return);
		return (BadMatch);
	    }
	    count = 3;
	    break;

	      case 16:
	    /*
	     * Must have at least:
	     *		VisualID0
	     *		VisualID3
	     *		type
	     *		count
	     *		length
	     *		intensity1
	     *		intensity2
	     */
	    if (nitems < 7) {
		XFree (property_return);
		return (BadMatch);
	    }
	    count = 1;
	    break;

	case 32:
	    /*
	     * Must have at least:
	     *		VisualID0
	     *		type
	     *		count
	     *		length
	     *		intensity1
	     *		intensity2
	     */
	    if (nitems < 6) {
		XFree (property_return);
		return (BadMatch);
	    }
	    count = 0;
	    break;
	    
	default:
	    XFree (property_return);
	    return (BadMatch);
	}
	
	/* Get VisualID */
	visualID = _XcmsGetElement(format, &pChar, &nitems);
	while (count--) {
	    visualID = visualID << format;
	    visualID |= _XcmsGetElement(format, &pChar, &nitems);
	}
	if (visual->visualid != visualID) {
	    if (!skipVisual(format, &pChar, &nitems)) {
		XFree (property_return);
		return (BadMatch);
	    }
	    continue;
	}

	/* Found the visual */
	maxcolor = 0xffff;

	/* Get table type and number of channels */
	cType = (int)_XcmsGetElement(format, &pChar, &nitems);
	nTables = (int)_XcmsGetElement(format, &pChar, &nitems);

	/* 
	** Note: it is assumed that the per-channel maps in the table all have
	** the same length.  This is a safe bet for most hardware, as it depends
	** not on the visual masks but on the visual bits_per_rgb.  Thus, we
	** extract the information from the red channel map and ignore the rest.
	*/

	switch (cType) {

	case 0:
	    if (!QueryTableType0(maxcolor, format, &pChar, &nitems, &nelem, &ph, &pf)) {
		XFree (property_return);
		return (BadMatch);
	    }
            if (nelem == 2 && 
		*ph == 0 && *pf == 0 &&
		*(ph+1) == maxcolor && *(pf+1) == maxcolor) {
		/* exactly linear */
		*gamma = 1.0;
	    } else {
		*gamma = exponentOfPowerFunc(maxcolor, maxcolor, (int)nelem, ph, pf);
	    }
	    break;

	case 1:
	    if (!QueryTableType1(maxcolor, format, &pChar, &nitems, &nelem, &ph, &pf)) {
		XFree (property_return);
		return (BadMatch);
	    }
	    *gamma = exponentOfPowerFunc((1<<visual->bits_per_rgb)-1, maxcolor, (int)nelem, ph, pf);
	    break;

	default:
	    XFree (property_return);
	    return (BadMatch);
	}

	XFree (property_return);

	/* These were allocated in the QueryTableType<n> routines and 
	   must be freed now */
	if (ph) {
	    free(ph);
	}
	if (pf) {
	    free(pf);
	}

	return (Success);
    }    
    /* bug fix for 4248958: OPENGL program shows mem leak in libdga*/
    if(property_return) XFree (property_return);

    *gamma = XSOLARIS_STD_GAMMA;
    return (Success);
}