/*
 * Copyright © 2004 David Reveman
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * David Reveman not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * David Reveman makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * DAVID REVEMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL DAVID REVEMAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include "xgl.h"
#include "colormapst.h"
#include "micmap.h"
#include "fb.h"

static xglPixelFormatRec xglPixelFormats[] = {
    {
	8, 8,
	{
	    8,
	    0x000000ff,
	    0x00000000,
	    0x00000000,
	    0x00000000
	}
    }, {
	15, 5,
	{
	    16,
	    0x00000000,
	    0x00007c00,
	    0x000003e0,
	    0x0000001f
	}
    }, {
	16, 6,
	{
	    16,
	    0x00000000,
	    0x0000f800,
	    0x000007e0,
	    0x0000001f
	}
    }, {
	24, 8,
	{
	    32,
	    0x00000000,
	    0x00ff0000,
	    0x0000ff00,
	    0x000000ff
	}
    }, {
	32, 8,
	{
	    32,
	    0xff000000,
	    0x00ff0000,
	    0x0000ff00,
	    0x000000ff
	}
    }
};

#define NUM_XGL_PIXEL_FORMATS				     \
    (sizeof (xglPixelFormats) / sizeof (xglPixelFormats[0]))

xglVisualPtr xglVisuals = NULL;

void
xglSetVisualTypes (int depth,
		   int visuals,
		   int redSize,
		   int greenSize,
		   int blueSize)
{
    xglPixelFormatPtr pBestFormat = 0;
    int		      i, rs, gs, bs, diff, bestDiff = 0;

    for (i = 0; i < NUM_XGL_PIXEL_FORMATS; i++)
    {
	if (xglPixelFormats[i].depth == depth)
	{
	    if (visuals)
	    {
		rs = Ones (xglPixelFormats[i].masks.red_mask);
		gs = Ones (xglPixelFormats[i].masks.green_mask);
		bs = Ones (xglPixelFormats[i].masks.blue_mask);

		if (redSize   >= rs &&
		    greenSize >= gs &&
		    blueSize  >= bs)
		{
		    diff = (redSize - rs) + (greenSize - gs) + (blueSize - bs);
		    if (pBestFormat)
		    {
			if (diff < bestDiff)
			{
			    pBestFormat = &xglPixelFormats[i];
			    bestDiff = diff;
			}
		    }
		    else
		    {
			pBestFormat = &xglPixelFormats[i];
			bestDiff = diff;
		    }
		}
	    }
	    else
	    {
		pBestFormat = &xglPixelFormats[i];
		break;
	    }
	}
    }

    if (pBestFormat)
    {
	xglVisualPtr new, *prev, v;

	new = xalloc (sizeof (xglVisualRec));
	if (!new)
	    return;

	new->next = 0;

	new->format.surface  = 0;
	new->format.drawable = 0;
	new->pPixel	     = pBestFormat;
	new->vid	     = 0;

	for (prev = &xglVisuals; (v = *prev); prev = &v->next);
	*prev = new;
    }
}

Bool
xglHasVisualTypes (xglVisualPtr pVisual,
		   int		depth)
{
    xglVisualPtr v;

    for (v = pVisual; v; v = v->next)
	if (v->pPixel->depth == depth)
	    return TRUE;

    return FALSE;
}

glitz_format_t *
xglFindBestSurfaceFormat (ScreenPtr         pScreen,
			  xglPixelFormatPtr pPixel)
{
    glitz_format_t templ, *format, *best = 0;
    unsigned int   mask;
    unsigned short rs, gs, bs, as;
    int	           i = 0;

    XGL_SCREEN_PRIV (pScreen);

    rs = Ones (pPixel->masks.red_mask);
    gs = Ones (pPixel->masks.green_mask);
    bs = Ones (pPixel->masks.blue_mask);
    as = Ones (pPixel->masks.alpha_mask);

    templ.color.fourcc = GLITZ_FOURCC_RGB;
    mask = GLITZ_FORMAT_FOURCC_MASK;

    do {
	format = glitz_find_format (pScreenPriv->drawable, mask, &templ, i++);
	if (format)
	{
	    if (format->color.red_size   >= rs &&
		format->color.green_size >= gs &&
		format->color.blue_size  >= bs &&
		format->color.alpha_size >= as)
	    {
		if (best)
		{
		    if (((format->color.red_size   - rs) +
			 (format->color.green_size - gs) +
			 (format->color.blue_size  - bs)) <
			((best->color.red_size   - rs) +
			 (best->color.green_size - gs) +
			 (best->color.blue_size  - bs)))
			best = format;
		}
		else
		{
		    best = format;
		}
	    }
	}
    } while (format);

    return best;
}

static Bool
xglInitVisual (ScreenPtr	 pScreen,
	       xglVisualPtr	 pVisual,
	       xglPixelFormatPtr pPixel,
	       VisualID		 vid)
{
    glitz_format_t *format;

    XGL_SCREEN_PRIV (pScreen);

    format = xglFindBestSurfaceFormat (pScreen, pPixel);
    if (format)
    {
	glitz_drawable_format_t templ;
	unsigned long	        mask;

	templ.color        = format->color;
	templ.depth_size   = 0;
	templ.stencil_size = 0;
	templ.doublebuffer = 0;
	templ.samples      = 1;

	mask =
	    GLITZ_FORMAT_FOURCC_MASK       |
	    GLITZ_FORMAT_RED_SIZE_MASK     |
	    GLITZ_FORMAT_GREEN_SIZE_MASK   |
	    GLITZ_FORMAT_BLUE_SIZE_MASK    |
	    GLITZ_FORMAT_ALPHA_SIZE_MASK   |
	    GLITZ_FORMAT_DEPTH_SIZE_MASK   |
	    GLITZ_FORMAT_STENCIL_SIZE_MASK |
	    GLITZ_FORMAT_DOUBLEBUFFER_MASK |
	    GLITZ_FORMAT_SAMPLES_MASK;

	pVisual->next	 = 0;
	pVisual->vid	 = vid;
	pVisual->pPixel	 = pPixel;
	pVisual->pbuffer = FALSE;

	pVisual->format.surface  = format;
	pVisual->format.drawable =
	    glitz_find_drawable_format (pScreenPriv->drawable,
					mask, &templ, 0);

	return TRUE;
    }

    return FALSE;
}

static Bool
xglInitPbufferVisual (ScreenPtr	        pScreen,
		      xglVisualPtr	pVisual,
		      xglPixelFormatPtr pPixel,
		      VisualID		vid)
{
    glitz_format_t *format;

    XGL_SCREEN_PRIV (pScreen);

    format = xglFindBestSurfaceFormat (pScreen, pPixel);
    if (format)
    {
	glitz_drawable_format_t templ, *screenFormat;
	unsigned long	        mask;

	/* use same drawable format as screen for pbuffers */
	screenFormat = glitz_drawable_get_format (pScreenPriv->drawable);
	templ.id = screenFormat->id;

	templ.color   = format->color;
	templ.samples = 1;

	mask =
	    GLITZ_FORMAT_ID_MASK	 |
	    GLITZ_FORMAT_FOURCC_MASK     |
	    GLITZ_FORMAT_RED_SIZE_MASK   |
	    GLITZ_FORMAT_GREEN_SIZE_MASK |
	    GLITZ_FORMAT_BLUE_SIZE_MASK  |
	    GLITZ_FORMAT_SAMPLES_MASK;

	pVisual->next	 = 0;
	pVisual->vid	 = vid;
	pVisual->pPixel	 = pPixel;
	pVisual->pbuffer = TRUE;

	pVisual->format.surface  = format;
	pVisual->format.drawable =
	    glitz_find_pbuffer_format (pScreenPriv->drawable,
				       mask, &templ, 0);

	if (pVisual->format.drawable)
	    return TRUE;
    }

    return FALSE;
}

void
xglInitVisuals (ScreenPtr pScreen)
{
    xglVisualPtr pVisual, v, new, *prev;
    int		 i;

    XGL_SCREEN_PRIV (pScreen);

    for (i = 0; i < pScreen->numVisuals; i++)
    {
	for (pVisual = xglVisuals; pVisual; pVisual = pVisual->next)
	    if (pVisual->pPixel->depth == pScreen->visuals[i].nplanes)
		break;

	if (pVisual)
	{
	    new = xalloc (sizeof (xglVisualRec));
	    if (new)
	    {
		if (xglInitVisual (pScreen, new, pVisual->pPixel,
				   pScreen->visuals[i].vid))
		{
		    new->next = 0;

		    prev = &pScreenPriv->pVisual;
		    while ((v = *prev))
			prev = &v->next;

		    *prev = new;
		}
		else
		{
		    xfree (new);
		}
	    }

	    new = xalloc (sizeof (xglVisualRec));
	    if (new)
	    {
		if (xglInitPbufferVisual (pScreen, new, pVisual->pPixel,
					  pScreen->visuals[i].vid))
		{
		    new->next = 0;

		    prev = &pScreenPriv->pVisual;
		    while ((v = *prev))
			prev = &v->next;

		    *prev = new;
		}
		else
		{
		    xfree (new);
		}
	    }
	}
    }

    /* Add additional Xgl visuals for pixmap formats */
    for (i = 0; i < screenInfo.numPixmapFormats; i++)
    {
	if (!xglHasVisualTypes (pScreenPriv->pVisual,
				screenInfo.formats[i].depth))
	{
	    for (v = xglVisuals; v; v = v->next)
		if (v->pPixel->depth == screenInfo.formats[i].depth)
		    break;

	    if (v)
	    {
		new = xalloc (sizeof (xglVisualRec));
		if (new)
		{
		    if (xglInitVisual (pScreen, new, v->pPixel, 0))
		    {
			new->next = 0;

			prev = &pScreenPriv->pVisual;
			while ((v = *prev))
			    prev = &v->next;

			*prev = new;
		    }
		    else
		    {
			xfree (new);
		    }
		}
	    }
	}
    }
}

xglVisualPtr
xglFindVisualWithDepth (ScreenPtr pScreen,
			int       depth)
{
    xglVisualPtr v;

    XGL_SCREEN_PRIV (pScreen);

    for (v = pScreenPriv->pVisual; v; v = v->next)
    {
	if (v->pPixel->depth == depth)
	    return v;
    }

    return 0;
}

xglVisualPtr
xglFindVisualWithId (ScreenPtr pScreen,
		     int       vid)
{
    xglVisualPtr v;

    XGL_SCREEN_PRIV (pScreen);

    for (v = pScreenPriv->pVisual; v; v = v->next)
    {
	if (v->vid == vid)
	    return v;
    }

    return 0;
}

void
xglClearVisualTypes (void)
{
    xglVisualPtr v;

    while (xglVisuals)
    {
	v = xglVisuals;
	xglVisuals = v->next;
	xfree (v);
    }

    miClearVisualTypes ();
}
