/*
 * Copyright (c) 1998-2001 by The XFree86 Project, Inc.
 *
 * 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
 *
 * Except as contained in this notice, the name of the copyright holder(s)
 * and author(s) shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from the copyright holder(s) and author(s).
 */

#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif

#include <math.h>
#include <X11/X.h>
#include <X11/Xproto.h>

#include "misc.h"

#include "dix/colormap_priv.h"
#include "dix/screen_hooks_priv.h"
#include "mi/mi_priv.h"

#include "misc.h"
#include "scrnintstr.h"
#include "resource.h"

#include "xf86.h"
#include "xf86_OSproc.h"
#include "xf86str.h"
#include "micmap.h"
#include "xf86RandR12_priv.h"
#include "xf86Crtc.h"

#ifdef XFreeXDGA
#include <X11/extensions/xf86dgaproto.h>
#include "dgaproc.h"
#include "dgaproc_priv.h"
#endif

#include "xf86cmap.h"

#define SCREEN_PROLOGUE(pScreen, field) ((pScreen)->field = \
    ((CMapScreenPtr)dixLookupPrivate(&(pScreen)->devPrivates, CMapScreenKey))->field)
#define SCREEN_EPILOGUE(pScreen, field, wrapper)\
    ((pScreen)->field = wrapper)

#define LOAD_PALETTE(pmap) \
    ((pmap == GetInstalledmiColormap(pmap->pScreen)) && \
     ((pScreenPriv->flags & CMAP_LOAD_EVEN_IF_OFFSCREEN) || \
      xf86ScreenToScrn(pmap->pScreen)->vtSema || pScreenPriv->isDGAmode))

typedef struct _CMapLink {
    ColormapPtr cmap;
    struct _CMapLink *next;
} CMapLink, *CMapLinkPtr;

typedef struct {
    CreateColormapProcPtr CreateColormap;
    DestroyColormapProcPtr DestroyColormap;
    InstallColormapProcPtr InstallColormap;
    StoreColorsProcPtr StoreColors;
    Bool (*EnterVT) (ScrnInfoPtr);
    Bool (*SwitchMode) (ScrnInfoPtr, DisplayModePtr);
    int (*SetDGAMode) (ScrnInfoPtr, int, DGADevicePtr);
    xf86ChangeGammaProc *ChangeGamma;
    int maxColors;
    int sigRGBbits;
    int gammaElements;
    LOCO *gamma;
    int *PreAllocIndices;
    CMapLinkPtr maps;
    unsigned int flags;
    Bool isDGAmode;
} CMapScreenRec, *CMapScreenPtr;

typedef struct {
    int numColors;
    LOCO *colors;
    Bool recalculate;
    int overscan;
} CMapColormapRec, *CMapColormapPtr;

static DevPrivateKeyRec CMapScreenKeyRec;

#define CMapScreenKeyRegistered dixPrivateKeyRegistered(&CMapScreenKeyRec)
#define CMapScreenKey (&CMapScreenKeyRec)
static DevPrivateKeyRec CMapColormapKeyRec;

#define CMapColormapKey (&CMapColormapKeyRec)

static void CMapInstallColormap(ColormapPtr);
static void CMapStoreColors(ColormapPtr, int, xColorItem *);
static void CMapCloseScreen(CallbackListPtr*, ScreenPtr, void*);
static Bool CMapCreateColormap(ColormapPtr);
static void CMapDestroyColormap(ColormapPtr);

static Bool CMapEnterVT(ScrnInfoPtr);
static Bool CMapSwitchMode(ScrnInfoPtr, DisplayModePtr);

#ifdef XFreeXDGA
static int CMapSetDGAMode(ScrnInfoPtr, int, DGADevicePtr);
#endif
static int CMapChangeGamma(ScrnInfoPtr, Gamma);

static void ComputeGamma(ScrnInfoPtr, CMapScreenPtr);
static Bool CMapAllocateColormapPrivate(ColormapPtr);
static void CMapRefreshColors(ColormapPtr, int, int *);
static void CMapSetOverscan(ColormapPtr, int, int *);
static void CMapReinstallMap(ColormapPtr);

Bool
xf86ColormapAllocatePrivates(ScrnInfoPtr pScrn)
{
    if (!dixRegisterPrivateKey(&CMapScreenKeyRec, PRIVATE_SCREEN, 0))
        return FALSE;

    if (!dixRegisterPrivateKey(&CMapColormapKeyRec, PRIVATE_COLORMAP, 0))
        return FALSE;
    return TRUE;
}

Bool
xf86HandleColormaps(ScreenPtr pScreen,
                    int maxColors,
                    int sigRGBbits,
                    xf86LoadPaletteProc * loadPalette,
                    xf86SetOverscanProc * setOverscan, unsigned int flags)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    ColormapPtr pDefMap = NULL;
    CMapScreenPtr pScreenPriv;
    LOCO *gamma;
    int *indices;
    int elements;

    if (!maxColors || !sigRGBbits ||
        (!loadPalette && !xf86_crtc_supports_gamma(pScrn)))
        return FALSE;

    elements = 1 << sigRGBbits;

    if (!(gamma = calloc(elements, sizeof(LOCO))))
        return FALSE;

    if (!(indices = calloc(maxColors, sizeof(int)))) {
        free(gamma);
        return FALSE;
    }

    if (!(pScreenPriv = calloc(1, sizeof(CMapScreenRec)))) {
        free(gamma);
        free(indices);
        return FALSE;
    }

    dixSetPrivate(&pScreen->devPrivates, &CMapScreenKeyRec, pScreenPriv);
    dixScreenHookClose(pScreen, CMapCloseScreen);

    pScreenPriv->CreateColormap = pScreen->CreateColormap;
    pScreenPriv->DestroyColormap = pScreen->DestroyColormap;
    pScreenPriv->InstallColormap = pScreen->InstallColormap;
    pScreenPriv->StoreColors = pScreen->StoreColors;
    pScreen->CreateColormap = CMapCreateColormap;
    pScreen->DestroyColormap = CMapDestroyColormap;
    pScreen->InstallColormap = CMapInstallColormap;
    pScreen->StoreColors = CMapStoreColors;

    pScrn->LoadPalette = loadPalette;
    pScrn->SetOverscan = setOverscan;
    pScreenPriv->maxColors = maxColors;
    pScreenPriv->sigRGBbits = sigRGBbits;
    pScreenPriv->gammaElements = elements;
    pScreenPriv->gamma = gamma;
    pScreenPriv->PreAllocIndices = indices;
    pScreenPriv->maps = NULL;
    pScreenPriv->flags = flags;
    pScreenPriv->isDGAmode = FALSE;

    pScreenPriv->EnterVT = pScrn->EnterVT;
    pScreenPriv->SwitchMode = pScrn->SwitchMode;
    pScreenPriv->SetDGAMode = pScrn->SetDGAMode;
    pScreenPriv->ChangeGamma = pScrn->ChangeGamma;

    if (!(flags & CMAP_LOAD_EVEN_IF_OFFSCREEN)) {
        pScrn->EnterVT = CMapEnterVT;
        if ((flags & CMAP_RELOAD_ON_MODE_SWITCH) && pScrn->SwitchMode)
            pScrn->SwitchMode = CMapSwitchMode;
    }
#ifdef XFreeXDGA
    pScrn->SetDGAMode = CMapSetDGAMode;
#endif
    pScrn->ChangeGamma = CMapChangeGamma;

    ComputeGamma(pScrn, pScreenPriv);

    /* get the default map */
    dixLookupResourceByType((void **) &pDefMap, pScreen->defColormap,
                            X11_RESTYPE_COLORMAP, serverClient, DixInstallAccess);

    if (!CMapAllocateColormapPrivate(pDefMap)) {
        CMapCloseScreen(NULL, pScreen, NULL);
        return FALSE;
    }

    if (xf86_crtc_supports_gamma(pScrn)) {
        pScrn->LoadPalette = xf86RandR12LoadPalette;

        if (!xf86RandR12InitGamma(pScrn, elements)) {
            CMapCloseScreen(NULL, pScreen, NULL);
            return FALSE;
        }
    }

    /* Force the initial map to be loaded */
    SetInstalledmiColormap(pScreen, NULL);
    CMapInstallColormap(pDefMap);
    return TRUE;
}

/**** Screen functions ****/

static Bool
CMapColormapUseMax(VisualPtr pVisual, CMapScreenPtr pScreenPriv)
{
    if (pVisual->nplanes > 16)
        return TRUE;
    return ((1 << pVisual->nplanes) > pScreenPriv->maxColors);
}

static Bool
CMapAllocateColormapPrivate(ColormapPtr pmap)
{
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pmap->pScreen->devPrivates,
                                         CMapScreenKey);
    CMapColormapPtr pColPriv;
    int numColors;
    LOCO *colors;

    if (CMapColormapUseMax(pmap->pVisual, pScreenPriv))
        numColors = pmap->pVisual->ColormapEntries;
    else
        numColors = 1 << pmap->pVisual->nplanes;

    if (!(colors = calloc(numColors, sizeof(LOCO))))
        return FALSE;

    if (!(pColPriv = calloc(1, sizeof(CMapColormapRec)))) {
        free(colors);
        return FALSE;
    }

    dixSetPrivate(&pmap->devPrivates, CMapColormapKey, pColPriv);

    pColPriv->numColors = numColors;
    pColPriv->colors = colors;
    pColPriv->recalculate = TRUE;
    pColPriv->overscan = -1;

    /* add map to list */
    CMapLinkPtr pLink = calloc(1, sizeof(CMapLink));
    if (pLink) {
        pLink->cmap = pmap;
        pLink->next = pScreenPriv->maps;
        pScreenPriv->maps = pLink;
    }

    return TRUE;
}

static Bool
CMapCreateColormap(ColormapPtr pmap)
{
    ScreenPtr pScreen = pmap->pScreen;
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);
    Bool ret = FALSE;

    pScreen->CreateColormap = pScreenPriv->CreateColormap;
    if ((*pScreen->CreateColormap) (pmap)) {
        if (CMapAllocateColormapPrivate(pmap))
            ret = TRUE;
    }
    pScreen->CreateColormap = CMapCreateColormap;

    return ret;
}

static void
CMapDestroyColormap(ColormapPtr cmap)
{
    ScreenPtr pScreen = cmap->pScreen;
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);
    CMapColormapPtr pColPriv =
        (CMapColormapPtr) dixLookupPrivate(&cmap->devPrivates, CMapColormapKey);
    CMapLinkPtr prevLink = NULL, pLink = pScreenPriv->maps;

    if (pColPriv) {
        free(pColPriv->colors);
        free(pColPriv);
    }

    /* remove map from list */
    while (pLink) {
        if (pLink->cmap == cmap) {
            if (prevLink)
                prevLink->next = pLink->next;
            else
                pScreenPriv->maps = pLink->next;
            free(pLink);
            break;
        }
        prevLink = pLink;
        pLink = pLink->next;
    }

    if (pScreenPriv->DestroyColormap) {
        pScreen->DestroyColormap = pScreenPriv->DestroyColormap;
        (*pScreen->DestroyColormap) (cmap);
        pScreen->DestroyColormap = CMapDestroyColormap;
    }
}

static void
CMapStoreColors(ColormapPtr pmap, int ndef, xColorItem * pdefs)
{
    ScreenPtr pScreen = pmap->pScreen;
    VisualPtr pVisual = pmap->pVisual;
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);
    int *indices = pScreenPriv->PreAllocIndices;
    int num = ndef;

    /* At the moment this isn't necessary since there's nobody below us */
    pScreen->StoreColors = pScreenPriv->StoreColors;
    (*pScreen->StoreColors) (pmap, ndef, pdefs);
    pScreen->StoreColors = CMapStoreColors;

    /* should never get here for these */
    if ((pVisual->class == TrueColor) ||
        (pVisual->class == StaticColor) || (pVisual->class == StaticGray))
        return;

    if (pVisual->class == DirectColor) {
        CMapColormapPtr pColPriv =
            (CMapColormapPtr) dixLookupPrivate(&pmap->devPrivates,
                                               CMapColormapKey);
        int i;

        if (CMapColormapUseMax(pVisual, pScreenPriv)) {
            int index;

            num = 0;
            while (ndef--) {
                if (pdefs[ndef].flags & DoRed) {
                    index = (pdefs[ndef].pixel & pVisual->redMask) >>
                        pVisual->offsetRed;
                    i = num;
                    while (i--)
                        if (indices[i] == index)
                            break;
                    if (i == -1)
                        indices[num++] = index;
                }
                if (pdefs[ndef].flags & DoGreen) {
                    index = (pdefs[ndef].pixel & pVisual->greenMask) >>
                        pVisual->offsetGreen;
                    i = num;
                    while (i--)
                        if (indices[i] == index)
                            break;
                    if (i == -1)
                        indices[num++] = index;
                }
                if (pdefs[ndef].flags & DoBlue) {
                    index = (pdefs[ndef].pixel & pVisual->blueMask) >>
                        pVisual->offsetBlue;
                    i = num;
                    while (i--)
                        if (indices[i] == index)
                            break;
                    if (i == -1)
                        indices[num++] = index;
                }
            }

        }
        else {
            /* not really as overkill as it seems */
            num = pColPriv->numColors;
            for (i = 0; i < pColPriv->numColors; i++)
                indices[i] = i;
        }
    }
    else {
        while (ndef--)
            indices[ndef] = pdefs[ndef].pixel;
    }

    CMapRefreshColors(pmap, num, indices);
}

static void
CMapInstallColormap(ColormapPtr pmap)
{
    ScreenPtr pScreen = pmap->pScreen;
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);

    if (pmap == GetInstalledmiColormap(pmap->pScreen))
        return;

    pScreen->InstallColormap = pScreenPriv->InstallColormap;
    (*pScreen->InstallColormap) (pmap);
    pScreen->InstallColormap = CMapInstallColormap;

    /* Important. We let the lower layers, namely DGA,
       overwrite the choice of Colormap to install */
    if (GetInstalledmiColormap(pmap->pScreen))
        pmap = GetInstalledmiColormap(pmap->pScreen);

    if (!(pScreenPriv->flags & CMAP_PALETTED_TRUECOLOR) &&
        (pmap->pVisual->class == TrueColor) &&
        CMapColormapUseMax(pmap->pVisual, pScreenPriv))
        return;

    if (LOAD_PALETTE(pmap))
        CMapReinstallMap(pmap);
}

/**** ScrnInfoRec functions ****/

static Bool
CMapEnterVT(ScrnInfoPtr pScrn)
{
    ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
    Bool ret;
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);

    pScrn->EnterVT = pScreenPriv->EnterVT;
    ret = (*pScreenPriv->EnterVT) (pScrn);
    pScreenPriv->EnterVT = pScrn->EnterVT;
    pScrn->EnterVT = CMapEnterVT;
    if (ret) {
        if (GetInstalledmiColormap(pScreen))
            CMapReinstallMap(GetInstalledmiColormap(pScreen));
        return TRUE;
    }
    return FALSE;
}

static Bool
CMapSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);

    if ((*pScreenPriv->SwitchMode) (pScrn, mode)) {
        if (GetInstalledmiColormap(pScreen))
            CMapReinstallMap(GetInstalledmiColormap(pScreen));
        return TRUE;
    }
    return FALSE;
}

#ifdef XFreeXDGA
static int
CMapSetDGAMode(ScrnInfoPtr pScrn, int num, DGADevicePtr dev)
{
    ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);
    int ret;

    ret = (*pScreenPriv->SetDGAMode) (pScrn, num, dev);

    pScreenPriv->isDGAmode = DGAActive(pScrn->scrnIndex);

    if (!pScreenPriv->isDGAmode && GetInstalledmiColormap(pScreen)
        && xf86ScreenToScrn(pScreen)->vtSema)
        CMapReinstallMap(GetInstalledmiColormap(pScreen));

    return ret;
}
#endif

/**** Utilities ****/

static void
CMapReinstallMap(ColormapPtr pmap)
{
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pmap->pScreen->devPrivates,
                                         CMapScreenKey);
    CMapColormapPtr cmapPriv =
        (CMapColormapPtr) dixLookupPrivate(&pmap->devPrivates, CMapColormapKey);
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pmap->pScreen);
    int i = cmapPriv->numColors;
    int *indices = pScreenPriv->PreAllocIndices;

    while (i--)
        indices[i] = i;

    if (cmapPriv->recalculate)
        CMapRefreshColors(pmap, cmapPriv->numColors, indices);
    else {
        (*pScrn->LoadPalette) (pScrn, cmapPriv->numColors,
                               indices, cmapPriv->colors, pmap->pVisual);
        if (pScrn->SetOverscan) {
#ifdef DEBUGOVERSCAN
            ErrorF("SetOverscan() called from CMapReinstallMap\n");
#endif
            pScrn->SetOverscan(pScrn, cmapPriv->overscan);
        }
    }

    cmapPriv->recalculate = FALSE;
}

static void
CMapRefreshColors(ColormapPtr pmap, int defs, int *indices)
{
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pmap->pScreen->devPrivates,
                                         CMapScreenKey);
    CMapColormapPtr pColPriv =
        (CMapColormapPtr) dixLookupPrivate(&pmap->devPrivates, CMapColormapKey);
    VisualPtr pVisual = pmap->pVisual;
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pmap->pScreen);
    int numColors, i;
    LOCO *gamma, *colors;
    EntryPtr entry;
    int reds, greens, blues, maxValue, index, shift;

    numColors = pColPriv->numColors;
    shift = 16 - pScreenPriv->sigRGBbits;
    maxValue = (1 << pScreenPriv->sigRGBbits) - 1;
    gamma = pScreenPriv->gamma;
    colors = pColPriv->colors;

    reds = pVisual->redMask >> pVisual->offsetRed;
    greens = pVisual->greenMask >> pVisual->offsetGreen;
    blues = pVisual->blueMask >> pVisual->offsetBlue;

    switch (pVisual->class) {
    case StaticGray:
        for (i = 0; i < numColors; i++) {
            index = (i + 1) * maxValue / numColors;
            colors[i].red = gamma[index].red;
            colors[i].green = gamma[index].green;
            colors[i].blue = gamma[index].blue;
        }
        break;
    case TrueColor:
        if (CMapColormapUseMax(pVisual, pScreenPriv)) {
            for (i = 0; i <= reds; i++)
                colors[i].red = gamma[i * maxValue / reds].red;
            for (i = 0; i <= greens; i++)
                colors[i].green = gamma[i * maxValue / greens].green;
            for (i = 0; i <= blues; i++)
                colors[i].blue = gamma[i * maxValue / blues].blue;
            break;
        }
        for (i = 0; i < numColors; i++) {
            colors[i].red = gamma[((i >> pVisual->offsetRed) & reds) *
                                  maxValue / reds].red;
            colors[i].green = gamma[((i >> pVisual->offsetGreen) & greens) *
                                    maxValue / greens].green;
            colors[i].blue = gamma[((i >> pVisual->offsetBlue) & blues) *
                                   maxValue / blues].blue;
        }
        break;
    case StaticColor:
    case PseudoColor:
    case GrayScale:
        for (i = 0; i < defs; i++) {
            index = indices[i];
            entry = (EntryPtr) &pmap->red[index];

            if (entry->fShared) {
                colors[index].red =
                    gamma[entry->co.shco.red->color >> shift].red;
                colors[index].green =
                    gamma[entry->co.shco.green->color >> shift].green;
                colors[index].blue =
                    gamma[entry->co.shco.blue->color >> shift].blue;
            }
            else {
                colors[index].red = gamma[entry->co.local.red >> shift].red;
                colors[index].green =
                    gamma[entry->co.local.green >> shift].green;
                colors[index].blue = gamma[entry->co.local.blue >> shift].blue;
            }
        }
        break;
    case DirectColor:
        if (CMapColormapUseMax(pVisual, pScreenPriv)) {
            for (i = 0; i < defs; i++) {
                index = indices[i];
                if (index <= reds)
                    colors[index].red =
                        gamma[pmap->red[index].co.local.red >> shift].red;
                if (index <= greens)
                    colors[index].green =
                        gamma[pmap->green[index].co.local.green >> shift].green;
                if (index <= blues)
                    colors[index].blue =
                        gamma[pmap->blue[index].co.local.blue >> shift].blue;

            }
            break;
        }
        for (i = 0; i < defs; i++) {
            index = indices[i];

            colors[index].red = gamma[pmap->red[(index >> pVisual->
                                                 offsetRed) & reds].co.local.
                                      red >> shift].red;
            colors[index].green =
                gamma[pmap->green[(index >> pVisual->offsetGreen) & greens].co.
                      local.green >> shift].green;
            colors[index].blue =
                gamma[pmap->blue[(index >> pVisual->offsetBlue) & blues].co.
                      local.blue >> shift].blue;
        }
        break;
    }

    if (LOAD_PALETTE(pmap))
        (*pScrn->LoadPalette) (pScrn, defs, indices, colors, pmap->pVisual);

    if (pScrn->SetOverscan)
        CMapSetOverscan(pmap, defs, indices);

}

static Bool
CMapCompareColors(LOCO * color1, LOCO * color2)
{
    /* return TRUE if the color1 is "closer" to black than color2 */
#ifdef DEBUGOVERSCAN
    ErrorF("#%02x%02x%02x vs #%02x%02x%02x (%d vs %d)\n",
           color1->red, color1->green, color1->blue,
           color2->red, color2->green, color2->blue,
           color1->red + color1->green + color1->blue,
           color2->red + color2->green + color2->blue);
#endif
    return (color1->red + color1->green + color1->blue <
            color2->red + color2->green + color2->blue);
}

static void
CMapSetOverscan(ColormapPtr pmap, int defs, int *indices)
{
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pmap->pScreen->devPrivates,
                                         CMapScreenKey);
    CMapColormapPtr pColPriv =
        (CMapColormapPtr) dixLookupPrivate(&pmap->devPrivates, CMapColormapKey);
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pmap->pScreen);
    VisualPtr pVisual = pmap->pVisual;
    int i;
    LOCO *colors;
    int index;
    Bool newOverscan = FALSE;
    int overscan, tmpOverscan;

    colors = pColPriv->colors;
    overscan = pColPriv->overscan;

    /*
     * Search for a new overscan index in the following cases:
     *
     *   - The index hasn't yet been initialised.  In this case search
     *     for an index that is black or a close match to black.
     *
     *   - The colour of the old index is changed.  In this case search
     *     all indices for a black or close match to black.
     *
     *   - The colour of the old index wasn't black.  In this case only
     *     search the indices that were changed for a better match to black.
     */

    switch (pVisual->class) {
    case StaticGray:
    case TrueColor:
        /* Should only come here once.  Initialise the overscan index to 0 */
        overscan = 0;
        newOverscan = TRUE;
        break;
    case StaticColor:
        /*
         * Only come here once, but search for the overscan in the same way
         * as for the other cases.
         */
    case DirectColor:
    case PseudoColor:
    case GrayScale:
        if (overscan < 0 || overscan > pScreenPriv->maxColors - 1) {
            /* Uninitialised */
            newOverscan = TRUE;
        }
        else {
            /* Check if the overscan was changed */
            for (i = 0; i < defs; i++) {
                index = indices[i];
                if (index == overscan) {
                    newOverscan = TRUE;
                    break;
                }
            }
        }
        if (newOverscan) {
            /* The overscan is either uninitialised or it has been changed */

            if (overscan < 0 || overscan > pScreenPriv->maxColors - 1)
                tmpOverscan = pScreenPriv->maxColors - 1;
            else
                tmpOverscan = overscan;

            /* search all entries for a close match to black */
            for (i = pScreenPriv->maxColors - 1; i >= 0; i--) {
                if (colors[i].red == 0 && colors[i].green == 0 &&
                    colors[i].blue == 0) {
                    overscan = i;
#ifdef DEBUGOVERSCAN
                    ErrorF("Black found at index 0x%02x\n", i);
#endif
                    break;
                }
                else {
#ifdef DEBUGOVERSCAN
                    ErrorF("0x%02x: ", i);
#endif
                    if (CMapCompareColors(&colors[i], &colors[tmpOverscan])) {
                        tmpOverscan = i;
#ifdef DEBUGOVERSCAN
                        ErrorF("possible \"Black\" at index 0x%02x\n", i);
#endif
                    }
                }
            }
            if (i < 0)
                overscan = tmpOverscan;
        }
        else {
            /* Check of the old overscan wasn't black */
            if (colors[overscan].red != 0 || colors[overscan].green != 0 ||
                colors[overscan].blue != 0) {
                int oldOverscan = tmpOverscan = overscan;

                /* See of there is now a better match */
                for (i = 0; i < defs; i++) {
                    index = indices[i];
                    if (colors[index].red == 0 && colors[index].green == 0 &&
                        colors[index].blue == 0) {
                        overscan = index;
#ifdef DEBUGOVERSCAN
                        ErrorF("Black found at index 0x%02x\n", index);
#endif
                        break;
                    }
                    else {
#ifdef DEBUGOVERSCAN
                        ErrorF("0x%02x: ", index);
#endif
                        if (CMapCompareColors(&colors[index],
                                              &colors[tmpOverscan])) {
                            tmpOverscan = index;
#ifdef DEBUGOVERSCAN
                            ErrorF("possible \"Black\" at index 0x%02x\n",
                                   index);
#endif
                        }
                    }
                }
                if (i == defs)
                    overscan = tmpOverscan;
                if (overscan != oldOverscan)
                    newOverscan = TRUE;
            }
        }
        break;
    }
    if (newOverscan) {
        pColPriv->overscan = overscan;
        if (LOAD_PALETTE(pmap)) {
#ifdef DEBUGOVERSCAN
            ErrorF("SetOverscan() called from CmapSetOverscan\n");
#endif
            pScrn->SetOverscan(pScrn, overscan);
        }
    }
}

static void CMapCloseScreen(CallbackListPtr *pcbl, ScreenPtr pScreen, void *unused)
{
    CMapScreenPtr pScreenPriv =
        (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates, CMapScreenKey);
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);

    if (!pScrn)
        return;

    dixScreenUnhookClose(pScreen, CMapCloseScreen);

    pScreen->CreateColormap = pScreenPriv->CreateColormap;
    pScreen->DestroyColormap = pScreenPriv->DestroyColormap;
    pScreen->InstallColormap = pScreenPriv->InstallColormap;
    pScreen->StoreColors = pScreenPriv->StoreColors;

    pScrn->EnterVT = pScreenPriv->EnterVT;
    pScrn->SwitchMode = pScreenPriv->SwitchMode;
    pScrn->SetDGAMode = pScreenPriv->SetDGAMode;
    pScrn->ChangeGamma = pScreenPriv->ChangeGamma;

    free(pScreenPriv->gamma);
    free(pScreenPriv->PreAllocIndices);
    free(pScreenPriv);
    dixSetPrivate(&pScreen->devPrivates, &CMapScreenKeyRec, NULL);
}

static void
ComputeGamma(ScrnInfoPtr pScrn, CMapScreenPtr priv)
{
    int elements = priv->gammaElements - 1;
    double RedGamma, GreenGamma, BlueGamma;
    int i;

#ifndef DONT_CHECK_GAMMA
    /* This check is to catch drivers that are not initialising pScrn->gamma */
    if (pScrn->gamma.red < GAMMA_MIN || pScrn->gamma.red > GAMMA_MAX ||
        pScrn->gamma.green < GAMMA_MIN || pScrn->gamma.green > GAMMA_MAX ||
        pScrn->gamma.blue < GAMMA_MIN || pScrn->gamma.blue > GAMMA_MAX) {

        xf86DrvMsgVerb(pScrn->scrnIndex, X_WARNING, 0,
                       "The %s driver didn't call xf86SetGamma() to initialise\n"
                       "\tthe gamma values.\n", pScrn->driverName);
        xf86DrvMsgVerb(pScrn->scrnIndex, X_WARNING, 0,
                       "PLEASE FIX THE `%s' DRIVER!\n",
                       pScrn->driverName);
        pScrn->gamma.red = 1.0;
        pScrn->gamma.green = 1.0;
        pScrn->gamma.blue = 1.0;
    }
#endif

    RedGamma = 1.0 / (double) pScrn->gamma.red;
    GreenGamma = 1.0 / (double) pScrn->gamma.green;
    BlueGamma = 1.0 / (double) pScrn->gamma.blue;

    for (i = 0; i <= elements; i++) {
        if (RedGamma == 1.0)
            priv->gamma[i].red = i;
        else
            priv->gamma[i].red = (CARD16) (pow((double) i / (double) elements,
                                               RedGamma) * (double) elements +
                                           0.5);

        if (GreenGamma == 1.0)
            priv->gamma[i].green = i;
        else
            priv->gamma[i].green = (CARD16) (pow((double) i / (double) elements,
                                                 GreenGamma) *
                                             (double) elements + 0.5);

        if (BlueGamma == 1.0)
            priv->gamma[i].blue = i;
        else
            priv->gamma[i].blue = (CARD16) (pow((double) i / (double) elements,
                                                BlueGamma) * (double) elements +
                                            0.5);
    }
}

int
CMapChangeGamma(ScrnInfoPtr pScrn, Gamma gamma)
{
    int ret = Success;
    ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
    CMapColormapPtr pColPriv;
    CMapScreenPtr pScreenPriv;
    CMapLinkPtr pLink;

    /* Is this sufficient checking ? */
    if (!CMapScreenKeyRegistered)
        return BadImplementation;

    pScreenPriv = (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                                   CMapScreenKey);
    if (!pScreenPriv)
        return BadImplementation;

    if (gamma.red < GAMMA_MIN || gamma.red > GAMMA_MAX ||
        gamma.green < GAMMA_MIN || gamma.green > GAMMA_MAX ||
        gamma.blue < GAMMA_MIN || gamma.blue > GAMMA_MAX)
        return BadValue;

    pScrn->gamma.red = gamma.red;
    pScrn->gamma.green = gamma.green;
    pScrn->gamma.blue = gamma.blue;

    ComputeGamma(pScrn, pScreenPriv);

    /* mark all colormaps on this screen */
    pLink = pScreenPriv->maps;
    while (pLink) {
        pColPriv = (CMapColormapPtr) dixLookupPrivate(&pLink->cmap->devPrivates,
                                                      CMapColormapKey);
        pColPriv->recalculate = TRUE;
        pLink = pLink->next;
    }

    if (GetInstalledmiColormap(pScreen) &&
        ((pScreenPriv->flags & CMAP_LOAD_EVEN_IF_OFFSCREEN) ||
         pScrn->vtSema || pScreenPriv->isDGAmode)) {
        ColormapPtr pMap = GetInstalledmiColormap(pScreen);

        if (!(pScreenPriv->flags & CMAP_PALETTED_TRUECOLOR) &&
            (pMap->pVisual->class == TrueColor) &&
            CMapColormapUseMax(pMap->pVisual, pScreenPriv)) {

            /* if the current map doesn't have a palette look
               for another map to change the gamma on. */

            pLink = pScreenPriv->maps;
            while (pLink) {
                if (pLink->cmap->pVisual->class == PseudoColor)
                    break;
                pLink = pLink->next;
            }

            if (pLink) {
                /* need to trick CMapRefreshColors() into thinking
                   this is the currently installed map */
                SetInstalledmiColormap(pScreen, pLink->cmap);
                CMapReinstallMap(pLink->cmap);
                SetInstalledmiColormap(pScreen, pMap);
            }
        }
        else
            CMapReinstallMap(pMap);
    }

    pScrn->ChangeGamma = pScreenPriv->ChangeGamma;
    if (pScrn->ChangeGamma)
        ret = pScrn->ChangeGamma(pScrn, gamma);
    pScrn->ChangeGamma = CMapChangeGamma;

    return ret;
}

static void
ComputeGammaRamp(CMapScreenPtr priv,
                 unsigned short *red,
                 unsigned short *green, unsigned short *blue)
{
    int elements = priv->gammaElements;
    LOCO *entry = priv->gamma;
    int shift = 16 - priv->sigRGBbits;

    while (elements--) {
        entry->red = *(red++) >> shift;
        entry->green = *(green++) >> shift;
        entry->blue = *(blue++) >> shift;
        entry++;
    }
}

int
xf86ChangeGammaRamp(ScreenPtr pScreen,
                    int size,
                    unsigned short *red,
                    unsigned short *green, unsigned short *blue)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    CMapColormapPtr pColPriv;
    CMapScreenPtr pScreenPriv;
    CMapLinkPtr pLink;

    if (!CMapScreenKeyRegistered)
        return BadImplementation;

    pScreenPriv = (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                                   CMapScreenKey);
    if (!pScreenPriv)
        return BadImplementation;

    if (pScreenPriv->gammaElements != size)
        return BadValue;

    ComputeGammaRamp(pScreenPriv, red, green, blue);

    /* mark all colormaps on this screen */
    pLink = pScreenPriv->maps;
    while (pLink) {
        pColPriv = (CMapColormapPtr) dixLookupPrivate(&pLink->cmap->devPrivates,
                                                      CMapColormapKey);
        pColPriv->recalculate = TRUE;
        pLink = pLink->next;
    }

    if (GetInstalledmiColormap(pScreen) &&
        ((pScreenPriv->flags & CMAP_LOAD_EVEN_IF_OFFSCREEN) ||
         pScrn->vtSema || pScreenPriv->isDGAmode)) {
        ColormapPtr pMap = GetInstalledmiColormap(pScreen);

        if (!(pScreenPriv->flags & CMAP_PALETTED_TRUECOLOR) &&
            (pMap->pVisual->class == TrueColor) &&
            CMapColormapUseMax(pMap->pVisual, pScreenPriv)) {

            /* if the current map doesn't have a palette look
               for another map to change the gamma on. */

            pLink = pScreenPriv->maps;
            while (pLink) {
                if (pLink->cmap->pVisual->class == PseudoColor)
                    break;
                pLink = pLink->next;
            }

            if (pLink) {
                /* need to trick CMapRefreshColors() into thinking
                   this is the currently installed map */
                SetInstalledmiColormap(pScreen, pLink->cmap);
                CMapReinstallMap(pLink->cmap);
                SetInstalledmiColormap(pScreen, pMap);
            }
        }
        else
            CMapReinstallMap(pMap);
    }

    return Success;
}

int
xf86GetGammaRampSize(ScreenPtr pScreen)
{
    CMapScreenPtr pScreenPriv;

    if (!CMapScreenKeyRegistered)
        return 0;

    pScreenPriv = (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                                   CMapScreenKey);
    if (!pScreenPriv)
        return 0;

    return pScreenPriv->gammaElements;
}

int
xf86GetGammaRamp(ScreenPtr pScreen,
                 int size,
                 unsigned short *red,
                 unsigned short *green, unsigned short *blue)
{
    CMapScreenPtr pScreenPriv;
    LOCO *entry;
    int shift, sigbits;

    if (!CMapScreenKeyRegistered)
        return BadImplementation;

    pScreenPriv = (CMapScreenPtr) dixLookupPrivate(&pScreen->devPrivates,
                                                   CMapScreenKey);
    if (!pScreenPriv)
        return BadImplementation;

    if (size > pScreenPriv->gammaElements)
        return BadValue;

    entry = pScreenPriv->gamma;
    sigbits = pScreenPriv->sigRGBbits;

    while (size--) {
        *red = entry->red << (16 - sigbits);
        *green = entry->green << (16 - sigbits);
        *blue = entry->blue << (16 - sigbits);
        shift = sigbits;
        while (shift < 16) {
            *red |= *red >> shift;
            *green |= *green >> shift;
            *blue |= *blue >> shift;
            shift += sigbits;
        }
        red++;
        green++;
        blue++;
        entry++;
    }

    return Success;
}

int
xf86ChangeGamma(ScreenPtr pScreen, Gamma gamma)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);

    if (pScrn->ChangeGamma)
        return (*pScrn->ChangeGamma) (pScrn, gamma);

    return BadImplementation;
}
