/*********************************************************************
 * 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.15 $
 *********************************************************************/

#ifndef _MULTIFEATURE_H
#define _MULTIFEATURE_H

#include <List.h>
#include <Hashtable.h>
#include <string>
#include "ProximityMeasure.h"
#include "Sample.h"

using namespace util;

namespace prapi
{
	/**
	 * An interface for classes that combine the proximities given by a
	 * set of different proximity measures.
	 **/
	class ProximityCombiner : virtual public Object
	{
	public:
		virtual double combine(const List<double>& proximities) = 0;
		class Sum;
		class WeightedSum;
		class Minimum;
		class Maximum;
	};

	/**
	 * A simple combiner that returns the sum of the given proximities.
	 **/
	class ProximityCombiner::Sum : public ProximityCombiner
	{
	public:
		double combine(const List<double>& proximities)
		{
			double sum = 0;
			for (int i=proximities.getLength();i--;)
				sum += proximities[i];
			return sum;
		}
	};

	/**
	 * A simple combiner that returns a weighted sum of the given
	 * proximities. This combiner is useful when you need to scale your
	 * distance measures. See ProximityMatrix for an explanation on how
	 * to perform this. The functionality provided by the weighted sum
	 * combiner can be obtained using a ProximityMultiplier for each
	 * proximity measure.
	 * @see ProximityMatrix
	 * @see ProximityMultiplier
	 **/
	class ProximityCombiner::WeightedSum : public ProximityCombiner
	{
	public:
		WeightedSum() {}
		virtual ~WeightedSum() {}

		/**
		 * Set the weight for a given proximity index.
		 * @param the index of the proximity to be weighted
		 * @param weight the weight. Default value is 1.
		 **/
		void setWeight(int index, double weight) { _tblWeights.put(index,weight); }
		/**
		 * Get the weight for a given feature index.
		 **/
		double getWeight(int index)
		{
			double *ptr = _tblWeights[index];
			return (ptr)? *ptr : 1.0;
		}
		
		double combine(const List<double>& proximities)
		{
			double sum = 0;
			for (int i=proximities.getLength();i--;)
				sum += getWeight(i) * proximities[i];
			return sum;
		}

	private:
		Hashtable<int,double> _tblWeights;
	};

	/**
	 * A simple combiner that returns the minimum of the given proximities.
	 **/
	class ProximityCombiner::Minimum : public ProximityCombiner
	{
	public:
		double combine(const List<double>& proximities)
		{
			return Math::min(proximities);
		}
	};

	/**
	 * A simple combiner that returns the maximum of the given proximities.
	 **/
	class ProximityCombiner::Maximum : public ProximityCombiner
	{
	public:
		double combine(const List<double>& proximities)
		{
			return Math::max(proximities);
		}
	};
	
	/**
	 * MultifeatureProximity provides convenient means to combine
	 * different features in classification. It is able to calculate
	 * proximities between multi-feature samples using different
	 * measures for different feature vectors. A multi-feature sample is
	 * a sample whose feature vector is a list of feature vectors. Each
	 * 'feature' in the sample may thus represent a distribution or any
	 * other type of a feature vector. A different proximity measure may
	 * be provided for each of these vectors, and combining the results
	 * given by the measures can be performed in any way by writing a
	 * custom ProximityCombiner.<p>
	 * Example:<br>
	 * <pre>
	 * MyProximity meas1, meas2;
	 * List<ProximityMeasure<double> > lst;
	 * lst += &meas1;
	 * lst += &meas2;
	 *
	 * //Autodelete combiner upon the deletion of the MultiFeatureProximity
	 * MultiFeatureProximity mfpm(lst,new ProximityCombiner::Minimum);
	 * //No autodelete
	 * ProximityCombiner::Minimum min;
	 * MultiFeatureProximity mfpm2(lst,min);
	 * </pre>
	 *
	 * @see ProximityMatrix
	 **/
	template <class T> class MultiFeatureProximity : public ProximityMeasure<List<T> >
	{
	public:
		/**
		 * Default constructor which does not do anything.
		 **/
		MultiFeatureProximity();
		
		/**
		 * Create a new multi-feature proximity measure. Note that the
		 * memory pointed to by <i>combiner</i> is automatically released.
		 *
		 * @param measures a list of proximity measure pointers that are
		 *        to be used in measuring the proximities between the
		 *        corresponding feature vectors. The length of this list
		 *        must be equal to the length of each feature vector list
		 *        in the samples to be classified.
		 * @param combiner a class that combines the proximities obtained
		 *        from the proximity measures to a single proximity value.
		 **/
		MultiFeatureProximity(List<ProximityMeasure<T>*>& measures, ProximityCombiner* combiner);
			 

		
		/**
		 * Create a new multi-feature proximity measure.
		 *
		 * @param measures a list of proximity measure pointers that are
		 *        to be used in measuring the proximities between the
		 *        corresponding feature vectors. The length of this list
		 *        must be equal to the length of each feature vector list
		 *        in the samples to be classified.
		 * @param combiner a class that combines the proximities obtained
		 *        from the proximity measures to a single proximity value.
		 **/
		MultiFeatureProximity(List<ProximityMeasure<T>*>& measures, ProximityCombiner& combiner);

		/**
		 * Create a copy of another multifeature proximity measure.
		 **/
		MultiFeatureProximity(const MultiFeatureProximity& other) : _lstProximityMeasures(other._lstProximityMeasures),
																																_pCombiner(other._pCombiner),
																																_iFeatureCount(other._iFeatureCount),
																																_lstEnabledFeatures(other._lstEnabledFeatures),
																																_bDeleteCombiner(false) {}
		
		double getProximity(const List<List<T> >& lst, const List<List<T> >& model,
												double stopAfter = MAXDOUBLE) const throw (ProximityException&);

		//template<class I,class C> double getProximity(Sample<List<T>,I,C>& sample, Sample<List<T>,I,C>& model) throw (ProximityException&);

		/**
		 * Enable or disable all features/proximities.
		 * @see #setFeatureEnabled(int,bool)
		 **/
		void setFeaturesEnabled(bool enabled) { _lstEnabledFeatures = enabled; }
		/**
		 * Enable or disable a given feature/proximity.
		 * @param index the index of the feature to be enabled/disabled
		 * @param enabled if false, the feature at index will not be taken
		 *        into account when calculating proximities. The length of
		 *        the list of proximity values given to ProximityCombiner
		 *        equals to the number of enabled features.
		 **/
		void setFeatureEnabled(int index, bool enabled);
		/**
		 * Check whether a feature/proximity is enabled.
		 * @param index the index of the feature
		 * @return true if and only if the feature is enabled
		 **/
		bool isFeatureEnabled(int index) { return (index < _lstEnabledFeatures.getLength())? _lstEnabledFeatures[index] : false; }

		/**
		 * See whether this measure is symmetric. A multi-feature
		 * proximity is symmetric if all of its enabled internal
		 * proximities are symmetric or there are no enabled proximities.
		 **/
		bool isSymmetric(void);

		/**
		 * Tells how many feature vectors are used by this MultiFeatureProximity
		 * @return number of featureVectors
		 **/
		int getFeatureCount(void) {return _iFeatureCount;}

		/**
		 * Copy the contents of another MultiFeatureProximity.
		 **/
		MultiFeatureProximity& operator= (const MultiFeatureProximity& other);

	private:
		List<ProximityMeasure<T>*> _lstProximityMeasures;
		ProximityCombiner* _pCombiner;
		int _iFeatureCount;
		List<bool> _lstEnabledFeatures;
		bool _bDeleteCombiner;
	};
	template <class T> MultiFeatureProximity<T>::MultiFeatureProximity(){}

	template <class T> MultiFeatureProximity<T>::MultiFeatureProximity(List<ProximityMeasure<T>*>& measures,
																																	   ProximityCombiner* combiner) :
		_lstProximityMeasures(measures), _pCombiner(combiner),
		_iFeatureCount(measures.getLength()), _lstEnabledFeatures(_iFeatureCount),
		_bDeleteCombiner(true)
	{
		_lstEnabledFeatures.setLength(_iFeatureCount);
		_lstEnabledFeatures = true;
	}

	template <class T> MultiFeatureProximity<T>::MultiFeatureProximity(List<ProximityMeasure<T>*>& measures,
																																		 ProximityCombiner& combiner) :
		_lstProximityMeasures(measures), _pCombiner(&combiner),
		_iFeatureCount(measures.getLength()), _lstEnabledFeatures(_iFeatureCount),
		_bDeleteCombiner(false)
	{
		_lstEnabledFeatures.setLength(_iFeatureCount);
		_lstEnabledFeatures = true;
	}

	template <class T> MultiFeatureProximity<T>& MultiFeatureProximity<T>::operator= (const MultiFeatureProximity& other)
	{
		_lstProximityMeasures = other._lstProximityMeasures;
		_pCombiner = other._pCombiner;
		_iFeatureCount = other._iFeatureCount;
		_lstEnabledFeatures = other._lstEnabledFeatures;
		_bDeleteCombiner = false;
		return *this;
	}

	template <class T> bool MultiFeatureProximity<T>::isSymmetric(void)
	{
		for (int i=_lstProximityMeasures.getLength();i--;)
			if (_lstEnabledFeatures[i] && !_lstProximityMeasures[i]->isSymmetric())
				return false;
		return true;
	}

	template <class T> void MultiFeatureProximity<T>::setFeatureEnabled(int index, bool enabled)
	{
		if (index < _lstEnabledFeatures.getLength())
			_lstEnabledFeatures[index] = enabled;
	}

//  	template <class T>
//  	template <class I, class C> double MultiFeatureProximity<T>::getProximity(Sample<List<T>,I,C>& sample,
//  																																						Sample<List<T>,I,C>& model)
//  		throw (ProximityException&)
//  	{
//  		return getProximity(sample.featureVector(),model.featureVector());
//  	}
	
	template <class T> double MultiFeatureProximity<T>::getProximity(const List<List<T> >& lst,
																																	 const List<List<T> >& model,
																																	 double stopAfter) const
		throw (ProximityException&)
	{
		int len = _lstProximityMeasures.getLength();
		if (lst.getLength() != model.getLength())
				throw ProximityException("MultiFeatureProximity::getProximity(List<List<T> >&, List<List<T> >&): Feature counts differ.");
		if (len != lst.getLength())
			throw ProximityException("MultiFeatureProximity::getProximity(List<List<T> >&, List<List<T> >&): Wrong number of proximity measures.");

		List<double> proximities(len);

		for (int i=0;i<len;i++)
			if (_lstEnabledFeatures[i])
				proximities += _lstProximityMeasures[i]->getProximity(lst[i],model[i]);

		return _pCombiner->combine(proximities);
	}
}

#endif
