/* Copyright (c) Mark J. Kilgard, 1994, 1996. */

/* This program is freely distributable without licensing fees 
   and is provided without guarantee or warrantee expressed or 
   implied. This program is -not- in the public domain. */

#include <stdlib.h>
#include <GL/glut.h>
#include "glutint.h"
#include "layerutil.h"

GLUTcolormap *__glutColormapList = NULL;

static GLUTcolormap *
associateNewColormap(XVisualInfo * vis)
{
  GLUTcolormap *cmap;
  int transparentPixel, i;
  unsigned long pixels[255];

  cmap = (GLUTcolormap *) malloc(sizeof(GLUTcolormap));
  if (!cmap)
    __glutFatalError("out of memory.");
#if defined(WIN32)
  pixels[0] = 0;		  /* avoid compilation warnings on win32 */
  cmap->visual = 0;
  cmap->size = 256;			/* always assume 256 on Win32 */
#else
  cmap->visual = vis->visual;
  cmap->size = vis->visual->map_entries;
#endif
  cmap->refcnt = 1;
  cmap->cells = (GLUTcolorcell *)
    malloc(sizeof(GLUTcolorcell) * cmap->size);
  if (!cmap->cells)
    __glutFatalError("out of memory.");
  /* make all color cell entries be invalid */
  for (i = cmap->size - 1; i >= 0; i--) {
    cmap->cells[i].component[GLUT_RED] = -1.0;
    cmap->cells[i].component[GLUT_GREEN] = -1.0;
    cmap->cells[i].component[GLUT_BLUE] = -1.0;
  }
  transparentPixel = __glutGetTransparentPixel(__glutDisplay, vis);
  if (transparentPixel == -1 || transparentPixel >= cmap->size) {

    /* If there is no transparent pixel or if the transparent
       pixel is outside the range of valid colormap cells (HP
       can implement their overlays this smart way since their
       transparent pixel is 255), we can AllocAll the colormap.
       See note below.  */

    cmap->cmap = XCreateColormap(__glutDisplay,
      __glutRoot, cmap->visual, AllocAll);
  } else {

    /* On machines where zero (or some other value in the range
       of 0 through map_entries-1), BadAlloc may be generated
       when an AllocAll overlay colormap is allocated since the
       transparent pixel precludes all the cells in the colormap
       being allocated (the transparent pixel is pre-allocated).
       So in this case, use XAllocColorCells to allocate
       map_entries-1 pixels (that is, all but the transparent
       pixel.  */

#if defined(WIN32)
    cmap->cmap = XCreateColormap(__glutDisplay,
      __glutRoot, 0, AllocNone);
#else
    cmap->cmap = XCreateColormap(__glutDisplay,
      __glutRoot, vis->visual, AllocNone);
#endif
    XAllocColorCells(__glutDisplay, cmap->cmap, False, 0, 0,
      pixels, cmap->size - 1);
  }
  cmap->next = __glutColormapList;
  __glutColormapList = cmap;
  return cmap;
}

GLUTcolormap *
__glutAssociateColormap(XVisualInfo * vis)
{
  GLUTcolormap *cmap = __glutColormapList;

  while (cmap != NULL) {
#if !defined(WIN32)
    /* Play safe: compare visual IDs, not Visual*'s. */
    if (cmap->visual->visualid == vis->visual->visualid) {
      /* Already have created colormap for the visual. */
      cmap->refcnt++;
      return cmap;
    }
#endif
    cmap = cmap->next;
  }
  return associateNewColormap(vis);
}

#define CLAMP(i) ((i) > 1.0 ? 1.0 : ((i) < 0.0 ? 0.0 : (i)))

/* CENTRY */
void APIENTRY 
glutSetColor(int ndx, GLfloat red, GLfloat green, GLfloat blue)
{
  GLUTcolormap *cmap, *newcmap;
  XVisualInfo *vis;
  XColor color;
  int i;

  if (__glutCurrentWindow->renderWin == __glutCurrentWindow->win) {
    cmap = __glutCurrentWindow->colormap;
    vis = __glutCurrentWindow->vis;
  } else {
    cmap = __glutCurrentWindow->overlay->colormap;
    vis = __glutCurrentWindow->overlay->vis;
    if (ndx == __glutCurrentWindow->overlay->transparentPixel) {
      __glutWarning(
        "glutSetColor: cannot set color of overlay transparent index %d\n",
        ndx);
      return;
    }
  }

  if (!cmap) {
    __glutWarning("glutSetColor: current window is RGBA");
    return;
  }
#if defined(WIN32)
  if (ndx >= 256 ||			/* always assume 256 colors on Win32 */
#else
  if (ndx >= vis->visual->map_entries ||
#endif
    ndx < 0) {
    __glutWarning("glutSetColor: index %d out of range", ndx);
    return;
  }
  if (cmap->refcnt > 1) {
    GLUTwindow *toplevel;

    newcmap = associateNewColormap(vis);
    cmap->refcnt--;
    /* Wouldn't it be nice if XCopyColormapAndFree could be
       told not to free the old colormap's entries! */
    for (i = cmap->size - 1; i >= 0; i--) {
      if (i == ndx) {
        /* We are going to set this cell shortly! */
        continue;
      }
      if (cmap->cells[i].component[GLUT_RED] >= 0.0) {
        color.pixel = i;
        newcmap->cells[i].component[GLUT_RED] =
          cmap->cells[i].component[GLUT_RED];
        color.red = (GLfloat) 0xffff *
          cmap->cells[i].component[GLUT_RED];
        newcmap->cells[i].component[GLUT_GREEN] =
          cmap->cells[i].component[GLUT_GREEN];
        color.green = (GLfloat) 0xffff *
          cmap->cells[i].component[GLUT_GREEN];
        newcmap->cells[i].component[GLUT_BLUE] =
          cmap->cells[i].component[GLUT_BLUE];
        color.blue = (GLfloat) 0xffff *
          cmap->cells[i].component[GLUT_BLUE];
        color.flags = DoRed | DoGreen | DoBlue;
#if defined(WIN32)
	if (IsWindowVisible(__glutCurrentWindow->win)) {
	  XHDC = __glutCurrentWindow->hdc;
	} else {
	  XHDC = 0;
	}
#endif
        XStoreColor(__glutDisplay, newcmap->cmap, &color);
      } else {
        /* Leave unallocated entries unallocated. */
      }
    }
    cmap = newcmap;
    if (__glutCurrentWindow->renderWin == __glutCurrentWindow->win) {
      __glutCurrentWindow->colormap = cmap;
      __glutCurrentWindow->cmap = cmap->cmap;
    } else {
      __glutCurrentWindow->overlay->colormap = cmap;
      __glutCurrentWindow->overlay->cmap = cmap->cmap;
    }
    XSetWindowColormap(__glutDisplay,
      __glutCurrentWindow->renderWin, cmap->cmap);

    toplevel = __glutToplevelOf(__glutCurrentWindow);
    if (toplevel->cmap != cmap->cmap) {
      __glutPutOnWorkList(toplevel, GLUT_COLORMAP_WORK);
    }
  }
  color.pixel = ndx;
  red = CLAMP(red);
  cmap->cells[ndx].component[GLUT_RED] = red;
  color.red = (GLfloat) 0xffff *red;
  green = CLAMP(green);
  cmap->cells[ndx].component[GLUT_GREEN] = green;
  color.green = (GLfloat) 0xffff *green;
  blue = CLAMP(blue);
  cmap->cells[ndx].component[GLUT_BLUE] = blue;
  color.blue = (GLfloat) 0xffff *blue;
  color.flags = DoRed | DoGreen | DoBlue;
#if defined(WIN32)
  if (IsWindowVisible(__glutCurrentWindow->win)) {
    XHDC = __glutCurrentWindow->hdc;
  } else {
    XHDC = 0;
  }
#endif
  XStoreColor(__glutDisplay, cmap->cmap, &color);
}

GLfloat APIENTRY
glutGetColor(int ndx, int comp)
{
  GLUTcolormap *colormap;
  XVisualInfo *vis;

  if (__glutCurrentWindow->renderWin == __glutCurrentWindow->win) {
    colormap = __glutCurrentWindow->colormap;
    vis = __glutCurrentWindow->vis;
  } else {
    colormap = __glutCurrentWindow->overlay->colormap;
    vis = __glutCurrentWindow->overlay->vis;
    if (ndx == __glutCurrentWindow->overlay->transparentPixel) {
      __glutWarning("glutGetColor: requesting overlay transparent index %d\n",
        ndx);
      return -1.0;
    }
  }

  if (!colormap) {
    __glutWarning("glutGetColor: current window is RGBA");
    return -1.0;
  }
#if defined(WIN32)
#define OUT_OF_RANGE_NDX(ndx) (ndx >= 256 || ndx < 0)
#else
#define OUT_OF_RANGE_NDX(ndx) (ndx >= vis->visual->map_entries || ndx < 0)
#endif
  if (OUT_OF_RANGE_NDX(ndx)) {
    __glutWarning("glutGetColor: index %d out of range", ndx);
    return -1.0;
  }
  return colormap->cells[ndx].component[comp];
}
/* ENDCENTRY */

void
__glutFreeColormap(GLUTcolormap * cmap)
{
  GLUTcolormap *cur, **prev;

  cmap->refcnt--;
  if (cmap->refcnt == 0) {
    /* remove from colormap list */
    cur = __glutColormapList;
    prev = &__glutColormapList;
    while (cur) {
      if (cur == cmap) {
        *prev = cmap->next;
        break;
      }
      prev = &(cur->next);
      cur = cur->next;
    }
    /* actually free colormap */
    XFreeColormap(__glutDisplay, cmap->cmap);
    free(cmap->cells);
    free(cmap);
  }
}

/* CENTRY */
void APIENTRY 
glutCopyColormap(int winnum)
{
  GLUTwindow *window = __glutWindowList[winnum - 1];
  GLUTcolormap *oldcmap, *newcmap, *copycmap;
  XVisualInfo *dstvis;
  XColor color;
  int i, last;

  if (__glutCurrentWindow->renderWin == __glutCurrentWindow->win) {
    oldcmap = __glutCurrentWindow->colormap;
    dstvis = __glutCurrentWindow->vis;
    newcmap = window->colormap;
  } else {
    oldcmap = __glutCurrentWindow->overlay->colormap;
    dstvis = __glutCurrentWindow->overlay->vis;
    if (!window->overlay) {
      __glutWarning("glutCopyColormap: window %d has no overlay", winnum);
      return;
    }
    newcmap = window->overlay->colormap;
  }

  if (!oldcmap) {
    __glutWarning("glutCopyColormap: destination colormap must be color index");
    return;
  }
  if (!newcmap) {
    __glutWarning(
      "glutCopyColormap: source colormap of window %d must be color index",
      winnum);
    return;
  }
  if (newcmap == oldcmap) {
    /* Source and destination are the same; now copy needed. */
    return;
  }
#if !defined(WIN32)
  /* Play safe: compare visual IDs, not Visual*'s. */
  if (newcmap->visual->visualid == oldcmap->visual->visualid) {
#endif
    /* Visuals match!  "Copy" by reference...  */
    __glutFreeColormap(oldcmap);
    newcmap->refcnt++;
    if (__glutCurrentWindow->renderWin == __glutCurrentWindow->win) {
      __glutCurrentWindow->colormap = newcmap;
      __glutCurrentWindow->cmap = newcmap->cmap;
    } else {
      __glutCurrentWindow->overlay->colormap = newcmap;
      __glutCurrentWindow->overlay->cmap = newcmap->cmap;
    }
    XSetWindowColormap(__glutDisplay, __glutCurrentWindow->renderWin,
      newcmap->cmap);
    __glutPutOnWorkList(__glutToplevelOf(window), GLUT_COLORMAP_WORK);
#if !defined(WIN32)
  } else {
    /* Visuals different - need a distinct X colormap! */
    copycmap = associateNewColormap(dstvis);
    /* Wouldn't it be nice if XCopyColormapAndFree could be
       told not to free the old colormap's entries! */
    last = newcmap->size;
    if (last > copycmap->size) {
      last = copycmap->size;
    }
    for (i = last - 1; i >= 0; i--) {
      if (newcmap->cells[i].component[GLUT_RED] >= 0.0) {
        color.pixel = i;
        copycmap->cells[i].component[GLUT_RED] =
          newcmap->cells[i].component[GLUT_RED];
        color.red = (GLfloat) 0xffff *
          newcmap->cells[i].component[GLUT_RED];
        copycmap->cells[i].component[GLUT_GREEN] =
          newcmap->cells[i].component[GLUT_GREEN];
        color.green = (GLfloat) 0xffff *
          newcmap->cells[i].component[GLUT_GREEN];
        copycmap->cells[i].component[GLUT_BLUE] =
          newcmap->cells[i].component[GLUT_BLUE];
        color.blue = (GLfloat) 0xffff *
          newcmap->cells[i].component[GLUT_BLUE];
        color.flags = DoRed | DoGreen | DoBlue;
        XStoreColor(__glutDisplay, copycmap->cmap, &color);
      }
    }
  }
#endif
}
/* ENDCENTRY */
