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

#include "Wave/PyramidTransform.hh"
#include "WTools/ImageResizer.hh"

#ifdef _WIN32_WCE
#undef DELETE
#endif
#include <cmath>
#include <cassert>
#include <iostream>
#include <iomanip>
#include "WImage/miscdefs.h"

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


ImageResizer::ImageResizer(const ColorImage &img, FilterSet &flt,
			   int *fill, coeff threshold, bool optimizeImage,
			   int scalingStrategy)
  : m_image (img), m_maxDetail (NULL), m_filter (flt), m_qRows (0), m_qCols (0),
    m_scalingStrategy (scalingStrategy), m_innerAvgPerSize (0),
    m_innerSDeviation (0), m_innerRegionSize (0), m_threshold (threshold),
    m_optimizeImage (optimizeImage), m_optimizationCalculated (false),
    m_cropMaxRows (0), m_cropMaxCols (0), m_rowsMapping (0), m_colsMapping (0),
    m_steps (-1)
{
  if (fill == NULL)
    {
      m_fill = fill;
    }
  else
    {
      m_fill = new int [m_image.colors ()];
      for (int i = 0; i < m_image.colors (); i++)
	{
	  m_fill[i] = fill[i];
	}
    }
}


ImageResizer::~ImageResizer(void)
{
  DELETENOTNULLAR (m_fill);
  DELETENOTNULL (m_maxDetail);

}

void
ImageResizer::genMaxDetail (int steps)
{
  Image *channel;
  ColorImage *tmp = m_image.clone ();
  int oldY = tmp->rows();
  int oldX = tmp->cols();
  WaveletTransform *transform = NULL;

  DELETENOTNULL (m_maxDetail);

  /* perform a wavelet transform on all channels and from this calculate the
   * image containing the max values from all channels' detail areas */
  for (int i = 0; i < tmp->colors (); i++)
    {
      DELETENOTNULL (transform);
      transform = NEW (PyramidTransform (tmp->channel (i), m_filter));
      transform->expandImage ();
      transform->analysis (steps);
      channel = transform->highMax (steps);
      if (i == 0)
	{
	  m_maxDetail = channel->clone ();
	}
      else
	{
	  for (int j = 0; j < channel->size(); j++)
	    {
	      if (channel->at (j) > m_maxDetail->at (j))
		{
		  m_maxDetail->to (j, channel->at (j));
		}
	    }
	}
      DELETE (channel);
    }

  /* now restore the old image's ratio for the one produced from the max
   * coefficients in the detail areas */
  int newY = m_maxDetail->rows () * oldY / tmp->rows ();
  int newX = m_maxDetail->cols () * oldX / tmp->cols ();

  m_maxDetail->resize (newY, newX);
  m_colsMapping = m_image.cols () / m_maxDetail->cols ();
  m_rowsMapping = m_image.rows () / m_maxDetail->rows ();

  DELETE (tmp);
  DELETENOTNULL (transform);
}

void
ImageResizer::calcDimensions (int rows, int cols)
{
  /* we determine the optimal size from the image, no factors are needed */
  if (rows == 0 && cols == 0 && m_optimizeImage)
    {
      m_cropWhat = CROP_AUTOMATICALLY;
      return;
    }

  m_qRows = (float)rows / m_image.rows();
  m_qCols = (float)cols / m_image.cols();

  /* find the best fit: we want either the rows or the cols to
   * fit exactly so that the remaining dimension can be cropped */
  if ((int)floor (m_image.rows () * m_qCols + 0.5) >= rows)
    {
      m_cropWhat = CROP_ROWS;
    }
  else if ((int)floor (m_image.cols () * m_qRows + 0.5) > cols)
    {
      m_cropWhat = CROP_COLS;
    }
  else
    {
      m_cropWhat = CROP_BOTH_OR_NONE;
    }
}

ColorImage*
ImageResizer::getCroppedImage (int discardRows, int discardCols)
{
  ColorImage *result = NULL;
  if (discardRows > 0 || discardCols > 0)
    {
      int fromY = discardRows;
      int fromX = discardCols;

      int sizeY = m_image.rows () - 2 * discardRows;
      int sizeX = m_image.cols () - 2 * discardCols;

      /* TOOD this is far from optimal, but let's just see how it works first */
      result = m_image.crop (fromY, fromX, sizeY, sizeX);
    }
  else
    {
      result = m_image.clone ();
    }

  return result;
}

ColorImage*
ImageResizer::getUnscaledImage (int rows, int cols, int discardRows, int discardCols)
{
  ColorImage *tmp = getCroppedImage (discardRows, discardCols);

  if (m_fill == NULL)
    {
      return tmp;
    }

  int yoffs, xoffs, ysize, xsize;
  double oldRatio = (double)tmp->rows () / tmp->cols ();
  double newRatio = (double)rows / cols;
  if (oldRatio < newRatio)
    {
      // need to add rows
      // ysize / tmp->cols () == newRatio
      ysize = (int)(newRatio * tmp->cols () + 0.5);
      yoffs = (ysize - tmp->rows ()) / 2;
      xsize = tmp->cols ();
      xoffs = 0;
      assert (ysize >= tmp->rows ());
    }
  else
    {
      // need to add cols
      // tmp->rows () / xsize == newRatio
      xsize = (int)(tmp->rows () / newRatio + 0.5);
      xoffs = (xsize - tmp->cols ()) / 2;
      ysize = tmp->rows ();
      yoffs = 0;
      assert (xsize >= tmp->cols ());
    }
  assert (!(xsize != tmp->cols () && ysize != tmp->rows ()));

  if (ysize != tmp->rows () || xsize != tmp->cols ())
    {
      ColorImage *result =
	NEW (ColorImage (ysize, xsize, tmp->colors (), tmp->colormodel ()));

      result->fill (m_fill, 0, 0, result->rows (), result->cols ());
      result->paste (yoffs, xoffs, *tmp);

      DELETE (tmp);
      return result;
    }
  return tmp;
}

ColorImage*
ImageResizer::getScaledImage (int rows, int cols, int discardRows, int discardCols)
{
  ColorImage *tmp = getCroppedImage (discardRows, discardCols);

  if (m_cropWhat == CROP_AUTOMATICALLY)
    {
      return tmp;
    }

  ColorImage *result = tmp->fitInto (rows, cols, m_fill, m_scalingStrategy);
  DELETE (tmp);
  return result;
}

void
ImageResizer::calcOuterStats (coeff &avg, coeff &variance,
			      int firstTop, int firstLeft,
			      int firstBottom, int firstRight,
			      int secondTop, int secondLeft,
			      int secondBottom, int secondRight)
{
  avg = (m_maxDetail->aaverage (firstTop, firstLeft,
				firstBottom, firstRight)
	 + m_maxDetail->aaverage (secondTop, secondLeft,
				  secondBottom, secondRight)) / 2.0;

  variance = (m_maxDetail->variance (firstTop, firstLeft,
				     firstBottom, firstRight,
				     avg, true)
	      + m_maxDetail->variance (secondTop, secondLeft,
				       secondBottom, secondRight,
				       avg, true)) / 2;
}

void
ImageResizer::calcInnerStats (coeff &avg, coeff &sDev,
			      int top, int left, int bottom, int right)
{
  avg = m_maxDetail->aaverage (top, left, bottom, right);
  sDev = sqrt (m_maxDetail->variance (top, left, bottom, right, avg, true));
  m_innerRegionSize = (m_maxDetail->rows () - 2 * top)
    * (m_maxDetail->cols () - 2 * left);
}

void
ImageResizer::calcStats(int nRows, int nCols, coeff &outerAvg,
			coeff &outerSDev, coeff &innerAvg, coeff &innerSDev)
{
  coeff outerVariance = 0;

  //int valsOuterCols = 0;

  int secondTop = m_maxDetail->rows () - nRows;
  int secondLeft = m_maxDetail->cols () - nCols;
  int secondBottom = m_maxDetail->rows ();
  int secondRight = m_maxDetail->cols ();

  if (nCols > 0)
    {
      calcOuterStats (outerAvg, outerVariance, nRows, 0, secondTop, nCols,
		      nRows, secondLeft, secondTop, secondRight);

      //valsOuterCols = (secondTop - nRows) * nCols;
    }
  else if (nRows > 0)
    {
      calcOuterStats (outerAvg, outerVariance, 0, nCols, nRows, secondLeft,
		      secondTop, nCols, secondBottom, secondRight);

      //valsOuterCols = (nRows - secondTop) * (secondLeft - nCols);
    }
  else
    {
      throw invalid_argument ("can operate only one one out of rows/cols");
    }

  calcInnerStats (innerAvg, innerSDev, nRows, nCols, secondTop, secondLeft);

  outerSDev = sqrt (outerVariance);
}

void
ImageResizer::calcOptimization (void)
{
  coeff outerAvg;
  coeff outerSDev;
  coeff innerAvg;
  coeff innerSDev;
  double outerByInner;
  int max = m_image.rows () / 2;

  for (int row = 1; row < max && m_cropMaxRows == 0; row++)
    {
      calcStats(row, 0, outerAvg, outerSDev, innerAvg, innerSDev);
      outerByInner = outerAvg / innerAvg;
      /*cout.setf (ios::fixed, ios::floatfield);
      cout << "row: " << std::setw (2) << row
	   << std::setw (2) << ", oa: " << outerAvg
	   << std::setw (2) << ", os: " << outerSDev
	   << std::setw (2) << ", ia: " << innerAvg
	   << std::setw (2) << ", is: " << innerSDev
	   << std::setw (2) << ", o / row: " << outerByInner
	   << endl;*/
      if (outerByInner > m_threshold)
	{
	  m_cropMaxRows = row - 1;
	}
    }

  max = m_image.cols () / 2;
  for (int col = 1; col < max && m_cropMaxCols == 0; col++)
    {
      calcStats(0, col, outerAvg, outerSDev, innerAvg, innerSDev);
      outerByInner = outerAvg / innerAvg;
      /*cout.setf (ios::fixed, ios::floatfield);
      cout << "col: " << std::setw (2) << col
	   << std::setw (2) << ", oa: " << outerAvg
	   << std::setw (2) << ", os: " << outerSDev
	   << std::setw (2) << ", ia: " << innerAvg
	   << std::setw (2) << ", is: " << innerSDev
	   << std::setw (2) << ", o / col: " << outerByInner
	   << endl;*/
      if (outerByInner > m_threshold)
	{
	  m_cropMaxCols = col - 1;
	}
    }
  //cout << "calcOptimization: discard " << m_cropMaxRows
  //     << " rows and " << m_cropMaxCols << " cols." << endl;
  calcInnerStats (innerAvg, innerSDev, m_cropMaxRows, m_cropMaxCols,
		  m_maxDetail->rows () - m_cropMaxRows,
		  m_maxDetail->cols () - m_cropMaxCols);

  m_innerSDeviation = innerSDev;
  m_innerAvgPerSize = innerAvg / m_innerRegionSize;
}

int
ImageResizer::fixDimensions (int rows, int cols)
{
  coeff outerAvg;
  coeff outerSDev;
  coeff innerAvg;
  coeff innerSDev;
  int nDiscardEach = 0;

  double ratio = (double)rows / (double)cols;
  double mapping = 0;
  int maxI = 0;

  if (m_cropWhat == CROP_COLS)
    {
      maxI = - (int)(m_maxDetail->rows () / ratio - m_maxDetail->cols () + 0.5) / 2;
      mapping = m_colsMapping;
      if (maxI < m_cropMaxCols)
	{
	  return (int)(mapping * maxI);
	}
      maxI -= m_cropMaxCols;
      assert (maxI <= m_maxDetail->cols ());
    }
  else if (m_cropWhat == CROP_ROWS)
    {
      maxI = - (int)(m_maxDetail->cols () * ratio - m_maxDetail->rows () + 0.5) / 2;
      mapping = m_rowsMapping;
      if (maxI < m_cropMaxRows)
	{
	  return (int)(mapping * maxI);
	}
      maxI -= m_cropMaxRows;
      assert (maxI <= m_maxDetail->rows ());
    }
  else
    {
      /* dimensions already match, nothing to do */
      return 0;
    }

  double outerByInner;

  for (int i = 1; i < maxI && nDiscardEach == 0; i++)
    {
      calcStats (m_cropWhat == CROP_ROWS? i: 0, m_cropWhat == CROP_COLS? i: 0,
		 outerAvg, outerSDev, innerAvg, innerSDev);
      outerByInner = outerAvg / innerAvg;
      /*cout.setf (ios::fixed, ios::floatfield);
      cout << "i: " << std::setw (2) << i
	   << std::setw (2) << ", oa: " << outerAvg
	   << std::setw (2) << ", os: " << outerSDev
	   << std::setw (2) << ", ia: " << innerAvg
	   << std::setw (2) << ", is: " << innerSDev
	   << std::setw (2) << ", o / i: " << outerByInner
	   << endl;*/
      if (outerByInner > m_threshold)
	{
	  nDiscardEach = (i > 1)? (int)(mapping * (i - 1)) : 0;
	}
    }
  return nDiscardEach;
}

ColorImage*
ImageResizer::doResize (int rows, int cols, int steps, bool redimensionOnly)
{
  if ((rows <= 0 || cols <= 0) && !(rows == 0 && cols == 0 && m_optimizeImage))
    {
      throw invalid_argument ("rows or columns arg is <= zero");
    }

  calcDimensions (rows, cols);

  /* no dimension change necessary? do the job and go away */
  if (m_cropWhat == CROP_BOTH_OR_NONE && !m_optimizeImage && !redimensionOnly)
    {
      return m_image.scale (m_qRows, m_scalingStrategy);
    }

  /* We call genMaxDetail only once to save time */
  if (m_maxDetail == NULL || steps != m_steps)
    {
      m_steps = steps;
      genMaxDetail (steps);
    }

  if (m_optimizeImage && !m_optimizationCalculated)
    {
      calcOptimization ();
      m_optimizationCalculated = true;
    }

  int nDiscard = fixDimensions (rows, cols);
  int nDiscardRows = (int)(m_cropMaxRows * m_rowsMapping + 0.5);
  int nDiscardCols = (int)(m_cropMaxCols * m_colsMapping + 0.5);

  if (!m_optimizationCalculated)
    {
      coeff innerAvg, innerSDev;
      calcInnerStats (innerAvg, innerSDev,
		      nDiscardRows, nDiscardCols,
		      m_maxDetail->rows () - nDiscardRows,
		      m_maxDetail->cols () - nDiscardCols);
      m_innerSDeviation = innerSDev;
      m_innerAvgPerSize = innerAvg / m_innerRegionSize;
    }

  if (m_cropWhat != CROP_AUTOMATICALLY)
    {
      /* can we crop even more than needed? */
      if (m_image.rows () > rows && 2 * nDiscardRows > m_image.rows () - rows)
	{
	  nDiscardRows = (m_image.rows () - rows) / 2;
	}
      if (m_image.cols () > cols && 2 * nDiscardCols > m_image.cols () - cols)
	{
	  nDiscardCols = (m_image.cols () - cols) / 2;
	}

      /* have we gained some more to crop with differing dimensions? */
      if (m_cropWhat == CROP_ROWS && nDiscard > nDiscardRows)
	{
	  nDiscardRows = nDiscard;
	}
      else if (m_cropWhat == CROP_COLS && nDiscard > nDiscardCols)
	{
	  nDiscardCols = nDiscard;
	}
    }

  return redimensionOnly?
    getUnscaledImage (rows, cols, nDiscardRows, nDiscardCols) :
    getScaledImage (rows, cols, nDiscardRows, nDiscardCols);
}


ColorImage*
ImageResizer::resize (int rows, int cols, int steps)
{
  return doResize (rows, cols, steps, false);
}

ColorImage*
ImageResizer::redimension (int rows, int cols, int steps)
{
  return doResize (rows, cols, steps, true);
}
