/*
 * class ImageComparison
 *
 * $Date$
 * $Revision$
 *
 */

#ifndef IMAGE_COMPARISON_HH__
#define IMAGE_COMPARISON_HH__

#include "WImage/Image.hh"
#include "Wave/Filter.hh"
#include "WTools/ImageInformation.hh"

/** 
 * @addtogroup WTools
 * @{ */

/** The type of the image. Either hand-drawn or scanned. */
typedef enum { DRAWN = 0, SCANNED } imgtype;

/** The type of transform used for some operations. */
typedef enum { PYRAMID = 0, STANDARD } decomp;

/** A data structure for an $L^q$ norm. */
typedef struct lq {
  /** The weighted distance between the average colors */
  double colors;
  /** The weighted distances between the top coefficients */
  double details;
} lq;

/** A data structure for a cache of (log (i) / log (2)) entries. */
typedef struct logvals {
  int val;
  bool init;
} logvals;

/**
 * A class for differences between two images 
 */
class ImageComparison 
{
public:
  /** Constructor. Creates copies of the two images. If the two images
   * have different dimensions they will be resized to the largest common
   * dimensions.
   * @param img1
   * the first image
   * @param img2
   * the second image */
  ImageComparison (const Image &img1, const Image &img2);
  /** Destructor. Releases allocated memory. */
  ~ImageComparison (void);

  /** Return the number of rows.
   * @return
   * the number of rows */
  inline int rows (void) const { return m_ysize; }
  /** Return the number of columns.
   * @return
   * the number of columns */
  inline int cols (void) const { return m_xsize; }
  /** Return the overall size.
   * @return
   * the overall size */
  inline int size (void) const { return m_xysize; }

  /**
   * Produce an image holding the difference between the first and the 
   * second image
   * @param magnify
   * multiply the result by a factor to increase / decrease visibility
   * @param reverse
   * reverse the subtraction (take img1 - img2)
   * @return
   * a new image containing the difference */
  Image *difference (coeff magnify = 1, bool reverse = false);

  /** Compare with another image producing the PSNR. Stolen from Geoff 
   * Davis' Wavelet Coder kit. 
   * @return
   * The PSNR */
  double cmpsnr (void);
  
  /** Compare with another image producing the mean square error. Stolen 
   * from Geoff Davis' Wavelet Coder kit.
   * @return
   * The MSE */
  double cmpmse (void);
  
  /** Calculate the distance to another image according to the $L^q$ norm.
   * Idea and basic algorithm from Jacobs, Finkelstein, Salesin: "Fast
   * multiresolution image querying", Proc. of SIGGRAPH, 95.
   * Note that this method is deprecated as it does not strictly conform to
   * the original algorithm. I propose using the static methods truncateForLq
   * and calcLqAverageAcore, calcLqDetailScore instead.
   * @exception invalid_argument
   * image is not square
   * @param percent
   * the relative number of Wavelet coefficients to compare
   * @param flt
   * the filter to use
   * @param type
   * the type of the images to be assumed (drawn or scanned)
   * @return
   * The $L^q$ norm */
  lq distlq (double percent, FilterSet &flt, imgtype type);

  /** Calculate the distance to another image according to the old $L^qd$ 
   * norm.
   * Idea and basic algorithm from Jacobs, Finkelstein, Salesin: "Fast
   * multiresolution image querying", Proc. of SIGGRAPH, 95.
   * @exception invalid_argument
   * image is not square
   * @param percent
   * the relative number of Wavelet coefficients to compare
   * @param flt
   * the filter to use
   * @param type
   * the type of the images to be assumed (drawn or scanned)
   * @return
   * The $L^qd$ norm */
  double distlqd (double percent, FilterSet &flt, imgtype type);

  /**
   * Calculates a feature vector for the $L^q$ and $L^qd$ metrics.
   * @param image
   * the image
   * @param percent
   * the relative number of Wavelet coefficients to keep
   * @param flt
   * the filter to use
   * @param t
   * the decomposition type to use (default: Standard)
   * @return an image of overall average and truncated coeffs */
  static Image *truncateForLq (const Image &image, double percent,
                               FilterSet &flt, const decomp t = STANDARD);
  /**
   * Calculate an $L^q$ detail score for a greyscale image / single color
   * channel.
   * @param truncated1
   * the first image already truncated (see truncateForLq)
   * @param truncated2
   * the second image already truncated (see truncateForLq)
   * @param colorChannel
   * the number of the color channel to which the two images correspond, this
   * is important as it has an effect on the weights chosen when calculating
   * the score
   * @result the detail score
   */
  static double calcLqDetailScore (const Image &truncated1,
				   const Image &truncated2,
				   int colorChannel = 0);
  /**
   * Calculate an $L^q$ average score for a greyscale image / single color
   * channel.
   * @param truncated1
   * the first image already truncated (see truncateForLq)
   * @param truncated2
   * the second image already truncated (see truncateForLq)
   * @param colorChannel
   * the number of the color channel to which the two images correspond, this
   * is important as it has an effect on the weights chosen when calculating
   * the score
   * @result the average score
   */
  static double calcLqAverageScore (const Image &truncated1,
				    const Image &truncated2,
				    int colorChannel = 0);
  /**
   * Calculates a feature vector for the $L^q$ and $L^qd$ metrics.
   * @param image
   * the image
   * @param nKeptCoeffs
   * the absolute number of Wavelet coefficients to keep
   * @param flt
   * the filter to use 
   * @param t
   * the decomposition type to use (default: Standard)
   * @return an image of overall average and truncated coeffs */
  static Image *truncateForLq (const Image &image, int nKeptCoeffs,
                               FilterSet &flt, const decomp t = STANDARD);
  /**
   * Calculates a feature vector for the $L^q$ and $L^qd$ metrics.
   * @param image
   * the image
   * @param nKeptCoeffs
   * the absolute number of Wavelet coefficients to keep
   * @param flt
   * the filter to use
   * @param t
   * the decomposition type to use (default: Standard)
   * @return an image information of overall average the n most significant
   * coeffs sorted by absolute */
  static ImageInformation *imageInfoForLq (const Image &image, int nKeptCoeffs,
                                           FilterSet &flt, const decomp t = STANDARD);

  /** The number of weights per colour channel. */
#define N_WEIGHTS 6

  /**
   * Return a reference to the weight table for a given type.
   * @param imgType the type (scanned or drawn)
   * @param channel the channel (Y, U, V)
   * @return the weight table
   */
  static const float *getWeights (const imgtype imgType, int channel);

protected:
  /** Number of rows. */
  int m_ysize;
  /** Number of cols. */
  int m_xsize;
  /** Overall size. */
  int m_xysize;
  
  /** An array holding the two images to be compared. */
  Image *m_images[2];
  
  /** A cache needed by distlq() to avoid repeated calculation of 
   * floor (log (i) / log (2)). */
  logvals * m_lqcache;

  /** The six weights for scanned images, needed by distlq(), 
   * Y channel, according to Jacobs et.al. */ 
  double m_lqDWeights[2][N_WEIGHTS];

  /** The default weight tables for scanned images. */
  static const float m_scWeights[][N_WEIGHTS];
  /** The default weight tables for drawn images. */
  static const float m_drWeights[][N_WEIGHTS];

  /** The size of the m_lqcache. Always as big as MAX (rows, cols). */
  int m_lqcachesize;

  /** Helper method for distlq(). Calculates the weighing level
   * for a position
   * @param pos
   * the position (row or col)
   * @return 
   * the level */
  int level (int pos);

  /** Helper method for distlq(). Calculate the weight for a position (y, x). 
   * @param y
   * the row
   * @param x
   * the col
   * @param type
   * the images' type to be assumed (drawn or scanned)
   * @return 
   * the position's weight */
  double weight (int y, int x, imgtype type);
  
  /** Helper method for distlq(). Calculate the sum of differences of
   * the two decomposed images.
   * @param type
   * the type (drawn or scanned) to be assumed
   * @param reverse
   * if true, subract the first from the second image 
   * @param oldLqd
   * if true, calculate the old Lqd way: add up the score for differing 
   * locations instead of subtracting the scores for identical values */
  double lq_sum (imgtype type, bool reverse, bool oldLqd = false);
};

/** @} */

#endif // IMAGE_COMPARISON_HH__
