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

#include "Wave/PyramidTransform.hh"
#include "Wave/StandardTransform.hh"
#include "WTools/ImageComparison.hh"
#ifdef _WIN32_WCE
#undef DELETE
#endif
#include <cmath>
#include <cassert>
#include <iomanip>
#include <iostream>
#include "WImage/miscdefs.h"

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

const float ImageComparison::m_scWeights[][N_WEIGHTS] = {
  {  5.00, 0.83, 1.01, 0.52, 0.47, 0.30 },
  { 19.21, 1.26, 0.44, 0.53, 0.28, 0.14 },
  { 34.47, 0.36, 0.45, 0.14, 0.18, 0.27 }
};

const float ImageComparison::m_drWeights[][N_WEIGHTS] = {
  {  4.04, 0.79, 0.45, 0.42, 0.41, 0.32 },
  { 15.25, 0.92, 0.53, 0.26, 0.14, 0.07 },
  { 22.62, 0.40, 0.73, 0.25, 0.15, 0.38 }
};

ImageComparison::ImageComparison (const Image &img1, const Image &img2)
{
  m_images[0] = img1.clone ();
  m_images[1] = img2.clone ();
  m_ysize = MIN (m_images[0]->rows (), m_images[1]->rows ());
  m_xsize = MIN (m_images[0]->cols (), m_images[1]->cols ());
  m_xysize = m_xsize * m_ysize;
  
  for (int i = 0; i < 2; i++)
    {
      if (m_images[i]->rows () != m_ysize || m_images[i]->cols () != m_xsize)
        {
          m_images[i]->resize (m_ysize, m_xsize);
        }
    }
  
  m_lqcachesize = MAX (m_ysize, m_xsize);
  m_lqcache = NEW (logvals [m_lqcachesize]);

  for (int i = 0; i < m_lqcachesize; i++)
    {
      m_lqcache[i].init = false;
    }

  for (int col = 0; col < N_WEIGHTS; col++)
    {
      m_lqDWeights[0][col] = m_drWeights[0][col];
      std::cout << "draw: " << m_lqDWeights[0][col] << std::endl;
    }
  for (int col = 0; col < N_WEIGHTS; col++)
    {
      m_lqDWeights[1][col] = m_scWeights[0][col];
      std::cout << "scan: " << m_lqDWeights[0][col] << std::endl;
    }
}

ImageComparison::~ImageComparison (void)
{
  DELETE (m_images[0]);
  DELETE (m_images[1]);
  DELETEAR (m_lqcache);
}

Image*
ImageComparison::difference (coeff magnify, bool reverse)
{
  Image *ret = m_images[0]->clone ();
  coeff tmp;
  
  for (int row = 0; row < m_ysize; row++)
    {
      for (int col = 0; col < m_xsize; col++)
        {
          tmp = (reverse? -1: 1) * ret->at (row, col) 
            + (reverse? 1: -1) * m_images[1]->at (row, col);
          ret->to (row, col, magnify*tmp);
        }
    }
  return ret;
}


double 
ImageComparison::cmpsnr (void)
{
  double total_error = 0.0;
  
  for (int i = 0; i < m_xysize; i++)
    {
      total_error += SQUARE (m_images[0]->at (i) - m_images[1]->at (i));
    }

  double rms = sqrt(total_error/(double)(m_xysize));
  double psnr = 20.0 * log(255.0/rms)/log(10.0);

  return psnr;
}

double 
ImageComparison::cmpmse (void)
{
  double total_error = 0.0;
  
  for (int i = 0; i < m_xysize; i++)
    {
      total_error += SQUARE (m_images[0]->at (i) - m_images[1]->at (i));
    }

  double mse = total_error/(double)(m_xysize);
  
  return mse;
}

lq
ImageComparison::distlq (double percent, FilterSet &flt, imgtype type)
{
  lq score = { 0.0, 0.0 };

  Image *origs[2];

  for (int i = 0; i < 2; i++)
    {
      origs[i] = m_images[i];
      m_images[i] = truncateForLq (*origs[i], percent, flt);
    }
  score.colors = m_lqDWeights[type][0] 
    * fabs (m_images[0]->at (0, 0) - m_images[1]->at (0, 0));


  /* create a normalized details measure */
  score.details = lq_sum (type, false, false);

  for (int i = 0; i < 2; i++)
    {
      DELETE (m_images[i]);
      m_images[i] = origs[i];
    }

  return score;
}

double
ImageComparison::distlqd (double percent, FilterSet &flt, imgtype type)
{
  double sum1, sum2;

  Image *origs[2];

  for (int i = 0; i < 2; i++)
    {
      origs[i] = m_images[i];
      m_images[i] = truncateForLq (*origs[i], percent, flt);
    }

  sum1 = lq_sum (type, true, true);
  sum2 = lq_sum (type, false, true);

  for (int i = 0; i < 2; i++)
    {
      DELETE (m_images[i]);
      m_images[i] = origs[i];
    }
  
  return (sum1 < sum2? sum1: sum2) / (m_xysize * percent / 10000.0);
}

int 
ImageComparison::level (int i)
{
  if (!m_lqcache[i].init)
    {
      m_lqcache[i].init = true;
      m_lqcache[i].val = 1 + (int)floor (log ((double)i) / log ((double)2));
    }
  return m_lqcache[i].val;
}

double 
ImageComparison::weight (int y, int x, imgtype type)
{
  assert (y > 0);
  assert (x > 0);
  int l1 = level (y);
  int l2 = y == x? l1: level (x);
  return m_lqDWeights [type][MIN (MAX (l1, l2), 5)];
}

double 
ImageComparison::lq_sum (imgtype type, bool reverse, bool oldLqd)
{
  double score = 0;
  coeff c0, c1;
  coeff *coeffs[2];

  if (!reverse)
    {
      coeffs[0] = &c0;
      coeffs[1] = &c1;
    }
  else
    {
      coeffs[0] = &c1;
      coeffs[1] = &c0;
    }
  
  for (int row = 1; row < m_ysize; row++)
    {
      for (int col = 1; col < m_xsize; col++)
        {
          c0 = m_images[0]->at (row, col);
          c1 = m_images[1]->at (row, col);
          if (fabs (*coeffs[0]) > COEFF_EPSILON)
            {
              if (fabs (*coeffs[0] - *coeffs[1]) <= COEFF_EPSILON)
                {
                  if (!oldLqd)
                    {
                      score -= weight (row, col, type);
                    }
                }
              else
                {
                  if (oldLqd)
                    {
                      score += weight (row, col, type);
                    }
                }
            }
        }
    }
  return score;
}

Image *
ImageComparison::truncateForLq (const Image &image, double percent,
				FilterSet &flt, const decomp t)
{
  int nKeptCoeffs = (int)((double)(image.size () * percent) / 100.0);
  return truncateForLq (image, nKeptCoeffs, flt, t);
}

double
ImageComparison::calcLqAverageScore (const Image &truncated1,
				     const Image &truncated2, int colorChannel)
{
  //std::cout.setf (std::ios::fixed, std::ios::floatfield);
  //std::cout << "abs1: "
  //	    << std::setprecision (5) << std::setw (11)
  //	    << truncated1.at (0) << ", abs2: "
  //	    << std::setprecision (5) << std::setw (11)
  //	    << truncated2.at (0) << std::endl;
  return m_scWeights[colorChannel][0] * ::fabs (truncated1.at (0) -
						 truncated2.at (0));
}

double
ImageComparison::calcLqDetailScore (const Image &truncated1,
				    const Image &truncated2, int colorChannel)
{
  double res = 0;
  coeff c1, c2;
  int indexCacheSize = MAX (truncated1.rows (), truncated1.cols ());
  int *indexCache = new int[indexCacheSize];
  for (int i = 0; i < indexCacheSize; ++i)
    {
      indexCache[i] = -1;
    }
  int cacheEntry = 0;
  int misses = 0;
  for (int y = 0; y < truncated1.rows (); ++y)
    {
      for (int x = 0; x < truncated1.cols (); ++x)
	{
	  if (x == 0 && y == 0)
	    {
	      continue;
	    }
	  c1 = truncated1.at (y, x);
	  c2 = truncated2.at (y, x);
	  if ((c1 < 0 && c2 < 0) || (c1 > 0 && c2 > 0))
	    {
	      cacheEntry = MAX (y, x);
	      if (indexCache[cacheEntry] < 0)
		{
		  indexCache[cacheEntry] = 1 +
		    (int)floor (log ((double)cacheEntry) / log ((double)2));
		}
	      res -= m_scWeights[colorChannel][MIN (indexCache[cacheEntry],
						     N_WEIGHTS - 1)];
	    }
	  else if (c1 != 0)
	    {
	      ++misses;
	    }
	}
    }
  //std::cout << "detail misses in color channel " << colorChannel
  //	    << ": " << misses << std::endl;
  DELETEAR (indexCache);
  return res;
}

Image *
ImageComparison::truncateForLq (const Image &image, int nKeptCoeffs,
                                FilterSet &flt, const decomp t)
{

  Image *result = image.clone ();
  WaveletTransform *transform = t == STANDARD
    ? (WaveletTransform*)NEW (StandardTransform (*result, flt))
    : (WaveletTransform*)NEW (PyramidTransform (*result, flt));
  transform->analysis (0);
  DELETE (transform);
  coeff overAverage = result->at (0, 0);

  CoeffInformation *ci = NULL;
  ImageInformation *ii = NEW (ImageInformation (*result));

  bool found = false;
  for (int i = 0; ! found && i < ii->size (); ++i)
    {
      // make sure there's nothing to truncate on the overall average
      if (PII_YPOS (ii, i) == 0 && PII_XPOS (ii, i) == 0)
	{
	  ii->at(i).val (0.0);
	  found = true;
	}
    }

  ii->asort ();
  ii->shrink (nKeptCoeffs + 1, false);

  for (int i = 0; i < result->size (); i++)
    {
      result->to (i, 0.0);
    }

  for (int i = 0; i < ii->size (); i++)
    {
      ci =  &ii->at (i);
      result->to (ci->ypos (), ci->xpos (), ci->val () > 0? 1: -1);
    }
  DELETE (ii);
  result->to (0, 0, overAverage);
  return result;
}

ImageInformation *
ImageComparison::imageInfoForLq (const Image &image, int nKeptCoeffs,
                                 FilterSet &flt, const decomp t)
{

  Image *tmp = image.clone ();
  WaveletTransform *transform = t == STANDARD
    ? (WaveletTransform*)NEW (StandardTransform (*tmp, flt))
    : (WaveletTransform*)NEW (PyramidTransform (*tmp, flt));
  transform->analysis (0);
  DELETE (transform);

  ImageInformation *ii = NEW (ImageInformation (*tmp));
  DELETE (tmp);
  CoeffInformation atZero;

  bool found = false;
  for (int i = 0; ! found && i < ii->size (); ++i)
    {
      // make sure there's nothing to truncate on the overall average
      if (PII_YPOS (ii, i) == 0 && PII_XPOS (ii, i) == 0)
	{
	  atZero = ii->at (i);
	  ii->at(i).val (0.0);
	  found = true;
	}
    }

  ii->asort ();
  ii->shrink (nKeptCoeffs, false);
  ImageInformation *result = new ImageInformation (nKeptCoeffs + 1);
  result->to (atZero, 0);
  for (int i = 0; i < nKeptCoeffs; ++i)
    {
      result->to (ii->at (i), i + 1);
    }
  DELETE (ii);
  return result;
}

const float *
ImageComparison::getWeights (const imgtype imgType, int channel)
{
  switch (imgType)
    {
    case DRAWN:
      return m_drWeights[channel];
    case SCANNED:
      return m_scWeights[channel];
    }
  throw std::invalid_argument ("unknown image type.");
}
