/*
 * Implementation of class ColorBuffer
 *
 * $Date$
 * $Revision$
 *
 */

#include "WImage/ColorBuffer.hh"
#include "WImage/tools.h"
#include "WImage/debug.h"
#include "WImage/miscdefs.h"
#ifdef _WIN32_WCE
#undef DELETE
#endif
#include <stdexcept>
#include <cassert>
#ifndef _WIN32_WCE
#include <iostream>
#include <cstdio>
#else
#include "WImage/miscdefs.h"
#endif

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif


ColorBuffer::ColorBuffer (int xysize, int colors, clrmodel cmodel)
{
  this->m_xysize = xysize;
  m_colors = 0;
  m_images = NULL;
  m_isReference = false;
  ColorBuffer::init (colors, cmodel);
}

ColorBuffer::~ColorBuffer (void)
{
  destroy ();
}
void
ColorBuffer::init (int colors, clrmodel cmodel)
{
  this->m_colors = colors;
  this->m_cmodel = cmodel;

  if (m_colors > 0)
    {
      m_images = NEW (Image* [m_colors]);

      for (int i = 0; i < m_colors; i++)
        {
          m_images[i] = NULL;
        }
    }
  else
    {
      m_images = NULL;
    }
}

#ifndef _WIN32_WCE
int
ColorBuffer::fileSize (char const *fname) const
{
  FILE *fp = fopen (fname, "rb");
  rewind (fp);
  long start = ftell (fp);
  fseek (fp, 0, SEEK_END);
  long end = ftell (fp);
  fclose (fp);
  return end - start;
}
#endif

void
ColorBuffer::destroy (void)
{
  if (m_images != NULL)
    {
      for (int i = 0; i < m_colors; i++)
        {
          if (!m_isReference)
            {
              DELETENOTNULL (m_images[i]);
            }
        }
      DELETEAR (m_images);
    }
}

void
ColorBuffer::swapColors (int ch1, int ch2)
{
  if (ch1 < 0 || ch2 < 0 || ch1 >= m_colors || ch2 >= m_colors)
    {
      throw invalid_argument ("swapChannels: invalid channel spec");
    }
  Image *tmp = m_images[ch1];
  m_images[ch1] = m_images[ch2];
  m_images[ch2] = tmp;
}

coeff
ColorBuffer::averageColor (int fromY, int fromX, int toY, int toX)
{
  coeff ret = 0;
  for (int i = 0; i < m_colors; i++)
    {
      ret += m_images[i]->saverage (fromY, fromX, toY, toX);
    }
  return ret / m_colors;
}

void
ColorBuffer::colormodel (clrmodel model)
{
  if (m_colors != 3)
    {
      throw invalid_argument ("colormodel: number of colors is not 3.");
    }

  if (m_cmodel == model)
    {
      return;
    }

  /* This is from
   * http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html
   */
  coeff rgb2yuv[] = {
    +0.299, +0.587, +0.114,
    -0.147, -0.289, +0.436,
    +0.615, -0.515, -0.100
  };
  coeff yuv2rgb[] = {
    +1.000, +0.000, +1.140,
    +1.000, -0.396, -0.581,
    +1.000, +2.029, +0.000
  };

  coeff *matrix = NULL;

  switch (model)
    {
    case cm_yuv:
      switch (m_cmodel)
        {
        case cm_rgb:
          matrix = rgb2yuv;
          break;
        default:
          throw invalid_argument ("colormodel: unsupported conversion.");
        }
      break;
    case cm_rgb:
      switch (m_cmodel)
        {
        case cm_yuv:
          matrix = yuv2rgb;
          break;
        default:
          throw invalid_argument ("colormodel: unsupported conversion.");
        }
      break;
    default:
      throw invalid_argument ("colormodel: unsupported format.");
    }

  coeff src[3];
  coeff dest;
  for (int i = 0; i < m_xysize; i++)
    {
      for (int j = 0; j < m_colors; j++)
        {
          src[j] = m_images[j]->at (i);
        }
      for (int j = 0; j < m_colors; j++)
        {
          dest = 0.0;
          for (int k = 0; k < m_colors; k++)
            {
              dest += matrix[j * m_colors + k] * src[k];
            }
          m_images[j]->to (i, dest);
        }
    }
  m_cmodel = model;
}

coeff
ColorBuffer::smin (void) const
{
  if (m_colors < 1)
    {
      throw invalid_argument ("smin: empty image.");
    }

  coeff result = m_images[0]->smin ();
  for (int i = 1; i < m_colors; i++)
    {
      coeff tmp = m_images[i]->smin ();
      if (tmp < result)
        {
          result = tmp;
        }
    }
  return result;
}

coeff
ColorBuffer::smax (void) const
{
  if (m_colors < 1)
    {
      throw invalid_argument ("smax: empty image.");
    }

  coeff result = m_images[0]->smax ();
  for (int i = 1; i < m_colors; i++)
    {
      coeff tmp = m_images[i]->smax ();
      if (tmp > result)
        {
          result = tmp;
        }
    }
  return result;
}

void
ColorBuffer::valadjust (void)
{
  coeff cmin = smin ();
  coeff cmax = smax ();
  coeff norm = 1.0;

  DPRINTF (("old max: %.2f, min: %.2f\n", cmax, cmin));

  if (cmax - cmin > 255.0)
    {
      norm = 255.0 / (cmax - cmin);
    }
  if (cmin < 0.0)
    {
      cmin *= -1;
      cmax = 0.0;
    }
  else if (cmax > 255.0)
    {
      cmax -= 255.0 / norm;
      cmin = 0.0;
    }
  else
    {
      cmax = cmin = 0;
    }

  DPRINTF (("new max: %.2f, min: %.2f\n", cmax, cmin));
  for (int i = 0; i < m_images[0]->size (); i++)
    {
      for (int j = 0; j < m_colors; j++)
        {
          coeff d = norm * (cmin + m_images[j]->at (i) - cmax);
          m_images[j]->to (i, d < 0? 0: d);
        }
    }
  assert (smin () >= 0.0);
  assert (smax () < 256);
  DPRINTF (("now max: %.2f, min: %.2f\n\n", smax (), smin ()));
}


void
ColorBuffer::truncate (coeff min, coeff max)
{
  for (int i = 0; i < m_xysize; i++)
    {
      for (int j = 0; j < m_colors; j++)
        {
          if (m_images[j]->at (i) < min)
            {
              m_images[j]->to (i, min);
            }
          else if (m_images[j]->at (i) > max)
            {
              m_images[j]->to (i, max);
            }
        }
    }
  assert (smin () >= 0.0);
  assert (smax () < 256);
}

bool
ColorBuffer::equals (ColorBuffer &buf) const
{
  return this->epsilons (buf, COEFF_EPSILON);
}


bool
ColorBuffer::epsilons (ColorBuffer &buf, coeff epsilon) const
{
  for (int i = 0; i < m_colors; i++)
    {
      if (!m_images[i]->epsilons (*buf.m_images[i], epsilon))
        {
          return false;
        }
    }
  return true;
}

unsigned char*
ColorBuffer::toPixmap (clrmodel cmodel)
{
  clrmodel old = m_cmodel;
  if (old !=  cmodel)
    {
      colormodel (cmodel);
    }


  unsigned char *allpixels = NEW (unsigned char [m_colors * m_xysize]);

  /* import */
  for (int i = 0; i < m_xysize; i++)
    {
      for (int j = 0; j < m_colors; j++)
        {
          allpixels[m_colors * i + j]
            = (unsigned char)tools_coeff2pixel (m_images[j]->at (i));
        }
    }

  if (old !=  cmodel)
    {
      colormodel (old);
    }

  return allpixels;
}


