/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001 Topi Mäenpää and Jaakko Viertola
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.10 $
 *********************************************************************/

#ifndef _PROXIMITYMATRIX_H
#define _PROXIMITYMATRIX_H

#include "ProximityMeasure.h"
#include "MultiFeature.h"
#include <Matrix.h>
#include <Exception.h>
#include <List.h>

using namespace util;

namespace prapi
{
	/**
	 * ProximityMatrix calculates the distances between all pairs of
	 * samples and places the results in a double-valued matrix. It can
	 * use any proximity measure and it is able to treat both symmetric
	 * and non-symmetric measures. The complexity of this process is
	 * O(N<sup>2</sup>), where N is the number of samples.<p>
	 *
	 * Where can it be used, then? There are situations when you might
	 * want to scale your proximity measures using the mean or variance
	 * of measured distances between samples. This is needed when you
	 * have many feature vectors for each sample. Here is how to do the
	 * scaling:<br>
	 *
	 * <pre>
	 * //Let us suppose we have two feature vectors for each sample.
	 * //MyProximityMeasure is inherited from ProximityMeasure&lt;int&gt;
	 * MyProximityMeasure measure1, measure2;
	 * List&lt;Sample&lt;List&lt;int&gt; &gt; &gt; samples;
	 * //Create the samples somehow.
	 *
	 * //Create a multi-feature proximity measure
	 * List&lt;ProximityMeasure&lt;int&gt;* &gt; measures;
	 * measures += &amp;measure1;
	 * measures += &amp;measure2;
	 *
	 * ProximityCombiner::WeightedSum combiner;
	 * MultiFeatureProximity&lt;int&gt; mfpm(measures,combiner);
	 *
	 * //First calculate scaling for the first feature vector
	 * mfpm.setEnabled(1,false);
	 * ProximityMatrix mat(mfpm, samples);
	 * //Scale the proximities for the first feature vector by
	 * //the variance of all proximities
	 * combiner.setWeight(0,1.0/util::Math&lt;double&gt;::variance(mat));
	 *
	 * mfpm.setEnabled(1,true);
	 * mfpm.setEnabled(0,false);
	 * ProximityMatrix mat2(mfpm, samples);
	 * combiner.setWeight(1,1.0/util::Math&lt;double&gt;::variance(mat2));
	 * mfpm.setEnabled(1,true);
	 *
	 * //Now, you have a multi-feature proximity measure (mfpm) that
	 * //scales the proximities for each feature vector by the
	 * //variance of all proximities.
	 * </pre>
	 * 
	 * @see util::Math
	 * @see MultiFeatureProximityMeasure
	 * @see ProximityCombiner
	 **/
	class ProximityMatrix : public Matrix<double>
	{
	public:
		/**
		 * Default constructor.
		 **/
		ProximityMatrix() {}
		/**
		 * Copy constructor.
		 **/
		ProximityMatrix(const ProximityMatrix& other) : Matrix<double>(other) {}
		/**
		 * Copy constructor for double matrices.
		 **/
		ProximityMatrix(const Matrix<double>& other) : Matrix<double>(other) {}

		/**
		 * Assignment operator.
		 **/
		ProximityMatrix& operator= (const ProximityMatrix& other) { Matrix<double>::operator=(other); return *this; }
		/**
		 * Assignment operator for double matrices.
		 **/
		ProximityMatrix& operator= (const Matrix<double>& other) { Matrix<double>::operator=(other); return *this; }
		
		/**
		 * Create a proximity matrix out of the given samples using the
		 * given proximity measure.
		 * @param measure the proximity measure
		 * @param samples calculate the distances between these samples.
		 *                The size of the resulting matrix will be NxN,
		 *                where N is the length of the sample list.
		 **/
		template <class T,class I, class C> ProximityMatrix(ProximityMeasure<T>& measure,
																												List<Sample<T,I,C> >& samples);
		/**
		 * Create a proximity matrix for a given feature from a list of
		 * multi-feature samples.
		 * @param measure the proximity measure to be used for the feature vector
		 * @param samples calculate the distances between these samples.
		 *                The size of the resulting matrix will be NxN,
		 *                where N is the length of the sample list.
		 * @param featureIndex the index of the feature to be considered
		 **/
		template <class T, class I, class C> ProximityMatrix(ProximityMeasure<T>& measure,
																												 List<Sample<List<T>,I,C> >& samples,
																												 int featureIndex);

		/**
		 * Generate a list of proximity matrices out of the given
		 * multi-feature samples. A proximity matrix is created for each
		 * feature vector.<p>
		 * Example:<br>
		 * <pre>
		 * MyProximityMeasure measure1, measure2;
		 * List&lt;Sample&lt;List&lt;int&gt; &gt; &gt; samples;
		 * //Create the samples somehow.
		 *
		 * List&lt;ProximityMeasure&lt;int&gt;*&gt; measures;
		 * measures += &amp;measure1;
		 * measures += &amp;measure2;
		 *
		 * List&lt;ProximityMatrix&gt; matrices(ProximityMatrix::generateMatrices(measures,samples));
		 * //Look at the previous code samples for information on how the matrices
		 * //can be utilized.
		 * </pre>
		 * @param measures a list of proximity measures for the corresponding
		 *        feature vectors.
		 * @param samples the samples
		 **/
		template <class T, class I, class C>
		static List<ProximityMatrix> generateMatrices(List<ProximityMeasure<T>*>& measures,
																									List<Sample<List<T>,I,C> >& samples) throw (InvalidArgumentException&);

	private:
		template <class T, class I, class C> void generateMatrix(ProximityMeasure<T>& measure,
																														 List<Sample<T,I,C> >& samples);
	};
	
	template <class T, class I, class C> ProximityMatrix::ProximityMatrix(ProximityMeasure<T>& measure,
																																				List<Sample<T,I,C> >& samples) :
		Matrix<double>(samples.getLength())
	{
		generateMatrix(measure,samples);
	}


	template <class T, class I, class C> ProximityMatrix::ProximityMatrix(ProximityMeasure<T>& measure,
																																				List<Sample<List<T>,I,C> >& samples,
																																				int featureIndex) :
		Matrix<double>(samples.getLength())
	{
		if (samples.getLength() == 0)
			return;

		int featureCount = samples[0].featureVector().getLength();
		List<ProximityMeasure<T>*> measures(featureCount);
		for (int i=featureCount;i--;)
			measures += &measure;

		MultiFeatureProximity<T> mfpm(measures, new ProximityCombiner::Sum);
		for (int i=featureCount;i--;)
			if (i != featureIndex)
				mfpm.setFeatureEnabled(i,false);

		generateMatrix(samples,mfpm);
	}

	template <class T, class I, class C>
	List<ProximityMatrix> ProximityMatrix::generateMatrices(List<ProximityMeasure<T>*>& measures,
																													List<Sample<List<T>,I,C> >& samples) throw (InvalidArgumentException&)
	{
		if (samples.getLength() > 0 && samples[0].featureVector().getLength() != measures.getLength())
			throw InvalidArgumentException("ProximityMatrix::generateMatrices(List<ProximityMeasure<T>*>&, List<Sample<T,I,C> >&): "
																				 "The number of proximity measures is different from the number of feature vectors.");

		List<ProximityMatrix> result(measures.getLength());
		MultiFeatureProximity<T> mfpm(measures, new ProximityCombiner::Sum);

		for (int i=0;i<measures.getLength();i++)
			{
				mfpm.setFeaturesEnabled(false);
				mfpm.setFeatureEnabled(i,true);
				ProximityMatrix mat(mfpm,samples);
				result += mat;
			}
		return result;
	}

	template <class T, class I, class C> void ProximityMatrix::generateMatrix(ProximityMeasure<T>& measure,
																																						List<Sample<T,I,C> >& samples)
	{
		if (!measure.isSymmetric())
			{
				double* ptr = _pData;
				for (int r=0;r<_iRows;r++)
					for (int c=0;c<_iColumns;c++,ptr++)
						*ptr = measure.getProximity(samples[r].featureVector(),samples[c].featureVector());
			}
		else
			{
				double* ptr1 = _pData, *ptr2 = _pData;
				for (int r=0;r<_iRows;r++,ptr1+=r)
					{
						ptr2 = ptr1;
						for (int c=r;c<_iColumns;c++,ptr1++,ptr2+=_iColumns)
							*ptr1 = *ptr2 = measure.getProximity(samples[r].featureVector(),samples[c].featureVector());
					}
			}
	}
}

#endif
