/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program 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.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "swindow/sx11/SX11Color.h"

#include "stoolkit/SBinHashtable.h"
#include "stoolkit/SExcept.h"

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 */

/**
 * This is out color class
 */
SX11Color::SX11Color (SX11Impl* _impl, SS_WORD32 value) : SColor(value)
{
 pixel = allocate(_impl);
 impl = _impl;
}

SX11Color::SX11Color (SX11Impl* _impl, unsigned char _red, unsigned char _green, unsigned char _blue, unsigned char _alpha)
 : SColor (_red, _green, _blue, _alpha)
{
 pixel = allocate(_impl);
 impl = _impl;
}

SX11Color::SX11Color (SX11Impl* _impl, double _red, double _green, double _blue, double _alpha)
 : SColor (_red, _green, _blue, _alpha)
{
 pixel = allocate(_impl);
 impl = _impl;
}

SX11Color::SX11Color (const SX11Color& color)
  : SColor (color.red, color.green, color.blue, color.alpha)
{
 pixel = color.pixel;
 impl = color.impl;
}

SX11Color::SX11Color (SX11Impl* _impl,const SColor& color)
  : SColor (color)
{
 pixel = allocate(_impl);
 impl = _impl;
}

SX11Color
SX11Color::operator= (const SX11Color& color)
{
  if (&color == this) return *this;
  red = color.red;
  green = color.green;
  blue = color.blue;
  alpha = color.alpha;
  pixel = color.pixel;
  impl = color.impl;
  return *this;
}

SX11Color::~SX11Color()
{
}

SS_WORD32
SX11Color::getValue() const
{
  return ((SS_WORD32) blue)
        + (((SS_WORD32) green) << 8) 
        + (((SS_WORD32) red) << 16) 
        + (((SS_WORD32) alpha) << 24); 
}

SPixel
SX11Color::getPixelValue() const
{
  return pixel;
}

SBinHashtable<SS_WORD32> colorCache;

/**
 * Try to get the color from cache or allocate it.
 * This one does not take the alpha!
 */
SS_WORD32
SX11Color::allocate(SX11Impl* _impl)
{
  XColor   xcolor;
  xcolor.red = (((unsigned short)red)<<8)+(unsigned short)red;
  xcolor.green = (((unsigned short)green)<<8)+(unsigned short)green;
  xcolor.blue = (((unsigned short)blue)<<8)+(unsigned short)blue;
  xcolor.flags = 0;

  SString mvle ((long)getValue() & 0x00ffffff);
  SS_WORD32 mpixel = colorCache[mvle];
  if (mpixel!=0) return mpixel & 0x00ffffff;

  if (_impl->visual->c_class != PseudoColor)
  {
    if (XAllocColor (_impl->display, _impl->colormap, &xcolor)==0)
    {
      colorCache.put (mvle, 0x01000000);
      return 0;
    }
    mpixel = xcolor.pixel | 0x01000000;
    colorCache.put (mvle, mpixel);
    return mpixel;
  }

  XColor   copyXcolor = xcolor;
  XColor*  colors;
  char*    flags;

  unsigned long r,g,b;
  int           bestMatch;
  unsigned long bestDistance;
  unsigned long d;
  int    i;
  
  if (XAllocColor (_impl->display, _impl->colormap, &copyXcolor)!=0)
  {
    mpixel = copyXcolor.pixel | 0x01000000;
    colorCache.put (mvle, mpixel);
    return copyXcolor.pixel;
  }

  // Fuzzy logic to get "some" good color. 
  if (_impl->cellCount<=0) return 0;

  colors = new XColor [_impl->cellCount];
  CHECK_NEW (colors);
  flags = new char [_impl->cellCount];
  CHECK_NEW (flags);
  memset (flags, 0, _impl->cellCount);

  // Find best match
  for (i=0; i<_impl->cellCount; i++)
  {
    colors[i].pixel = i;
  }
  while (1)
  {
    XQueryColors (_impl->display, _impl->colormap,
      colors, _impl->cellCount);
    
    bestMatch = -1;
    bestDistance =  0;

    for (i=0; i<_impl->cellCount; i++)
    {
      if (flags[i]) continue;

      r = (colors[i].red>>2)-(xcolor.red>>2);
      g = (colors[i].green>>2)-(xcolor.green>>2);
      b = (colors[i].blue>>2)-(xcolor.blue>>2);
      d = r*r+g*g+b*b;
      if (bestMatch<0 || d< bestDistance)
      {
        bestDistance = d;
        bestMatch = i;
      }
    }
    /* we tried everything. can not allocate color. */
    if (bestMatch<0)
    {
       break;
    }
    copyXcolor = colors[bestMatch];
    if (XAllocColor (_impl->display, _impl->colormap, &copyXcolor)!=0)
    {
      break;
    }
    /* can not use this cell, continue our search */
    flags[bestMatch] = 1;
  }

  delete colors;
  delete flags;

  if (bestMatch<0)
  {
    // No way to get any colors. All cells are private
    colorCache.put (mvle, 0x01000000);
    return 0;
  }
  mpixel = copyXcolor.pixel | 0x01000000;
  colorCache.put (mvle, mpixel);
  return copyXcolor.pixel;
}

/**
 * Blend the other color into this color, using alpha values
 * This function is virtual because you may need to reimplement this.
 */
void
SX11Color::blend (const SColor& color)
{
 red = (unsigned char) (((int) red  *  (255 - (int)color.alpha) 
  + (int) color.red * (int) color.alpha) / 255);
 green = (unsigned char) (((int) green  *  (255 - (int)color.alpha) 
  + (int) color.green * (int) color.alpha) / 255);
 blue = (unsigned char) (((int) blue  *  (255 - (int)color.alpha) 
  + (int) color.blue * (int) color.alpha) / 255);
 pixel = allocate(impl);
}
