/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * 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.9 $
 *********************************************************************/

#ifndef _VECTORQUANTIZER_H
#define _VECTORQUANTIZER_H

#include "Classifier.h"

namespace prapi
{
	/**
	 * A VQException is thrown when a vector quantizer cannot find a
	 * code vector index.
	 **/
	class VQException : public ClassificationException
	{
	public:
		VQException(std::string message) : ClassificationException(message) {}
	};
	
	/**
	 * VectorQuantizer is an interface for classes that are able to
	 * quantize a multi-dimensional feature space into a one-dimensional
	 * one.
	 **/
	class VectorQuantizer : virtual public util::Object
	{
	public:
		/**
		 * Get the scalar correspondent for a multi-dimensional vector.
		 *
		 * @param vec a multi-dimensional vector to be quantized
		 * @return the index of the closest vector in the code book
		 **/
		virtual int getBinIndex(const util::List<double>& vector) const throw (ClassificationException&) = 0;

		/**
		 * Get the maximum bin index this quantizer will give for a
		 * vector.
		 **/
		virtual int getMaxIndex() const = 0;
	};

	/**
	 * VQClassifier is a vector quantizer that uses a code book and
	 * works as a classifier at the same time.
	 **/
	template <class I=std::string,class C=int> class VQClassifier : public VectorQuantizer, public Classifier<double,I,C>
	{
	public:
		/**
		 * Create a new VectorQuantizer. There are no training samples as
		 * the internal code book is used as such.
		 *
		 * @param measure the proximity measure to be used in measuring
		 * distances between vectors (autorelease).
		 * @param classCount the number of classes in the data
		 **/
		VQClassifier(ProximityMeasure<double>* measure,
								 int classCount=1);
		/**
		 * Create a new VectorQuantizer. There are no training samples as
		 * the internal code book is used as such.
		 *
		 * @param measure the proximity measure to be used in measuring
		 * distances between vectors.
		 * @param classCount the number of classes in the data
		 **/
		VQClassifier(ProximityMeasure<double>& measure,
								 int classCount=1);
		
		/**
		 * Get the scalar correspondent (codebook bin index) for a
		 * multi-dimensional vector.
		 *
		 * @param vec a multi-dimensional vector to be quantized
		 * @return the index of the closest vector in the code book
		 **/
		virtual int getBinIndex(const util::List<double>& vector) const throw (ClassificationException&);
		/**
		 * Get the scalar correspondent (codebook bin index) for a
		 * multi-dimensional vector.
		 *
		 * @param sample the sample whose feature vector is to be
		 * quantized
		 * @return the index of the closest vector in the code book
		 **/
		int getBinIndex(const Sample<double,I,C>& sample) const throw (ClassificationException&)
		{ return getBinIndex(sample.featureVector()); }

		/**
		 * Returns the length of the code book minus one.
		 **/
		int getMaxIndex() const { return _lstCodeBook.getLength() - 1; }

		/**
		 * Get the classification given to the closest code book vector.
		 **/
		virtual int getClassification(Sample<double,I,C>& sample) throw (ClassificationException&)
		{ return !_bBinIndexMode ? (int)_lstCodeBook[getBinIndex(sample)].trueClass() : getBinIndex(sample); }

		/**
		 * Get a copy of the current code book.
		 **/
		util::List<Sample<double,I,C> > getCodeBook() const { return _lstCodeBook; }
		/**
		 * Get a reference to the current code book.
		 **/
		util::List<Sample<double,I,C> >& codeBook() { return _lstCodeBook; }
		/**
		 * Get a const reference to the current code book.
		 **/
		const util::List<Sample<double,I,C> >& codeBook() const { return _lstCodeBook; }
		/**
		 * Set the code book.
		 **/
		void setCodeBook(util::List<Sample<double,I,C> > codeBook) { _lstCodeBook = codeBook; }

		/**
		 * Change classification mode. VQClassifier can work in two
		 * different modes. The default (intuitive) way is to assign a
		 * classification according to the trueClass field of the best
		 * matching code vector. In "bin index" mode, classification is
		 * assigned according to the index of the best matching code
		 * vector. In this mode, each code vector represents a "class". 
		 * The first code vector has the index 0 and so on.
		 **/
		void setBinIndexMode(bool binIndexMode) { _bBinIndexMode = binIndexMode; }

		/**
		 * Get the current classification mode.
		 **/
		bool getBinIndexMode() const { return _bBinIndexMode; }
	protected:
		bool _bBinIndexMode;
		util::List<Sample<double,I,C> > _lstCodeBook;
	};

	template <class I, class C> VQClassifier<I,C>::VQClassifier(ProximityMeasure<double>* measure,
																															int classCount) :
		Classifier<double,I,C>(NULL,measure,classCount), _bBinIndexMode(false)
	{
		_lstpTrainingSamples = &_lstCodeBook;
	}

	template <class I, class C> VQClassifier<I,C>::VQClassifier(ProximityMeasure<double>& measure,
																															int classCount) :
		Classifier<double,I,C>(NULL,measure,classCount), _bBinIndexMode(false)
	{
		_lstpTrainingSamples = &_lstCodeBook;
	}

	template <class I, class C> int VQClassifier<I,C>::getBinIndex(const util::List<double>& sample) const
		throw (ClassificationException&)
	{
		double minDist = MAXDOUBLE;
		int minIndex = -1;
		for (int i=_lstCodeBook.getLength();i--;)
			{
				double dist = _proximityMeasure->getProximity(sample,_lstCodeBook[i].featureVector(), minDist);
				if (dist < minDist)
					{
						minDist = dist;
						minIndex = i;
					}
			}
		if (minIndex == -1)
			throw ClassificationException("VQClassifier::getBinIndex(const List&): Cannot find closest code vector. Did you forget to set the code book?");
		return minIndex;
	}

	/**
	 * OLVQ1 implements the optimized-learning-rate learning vector
	 * quantization algorithm for training the code book of a vector
	 * quantizer.
	 **/
	template <class I=std::string, class C=int> class OLVQ1 : public VQClassifier<I,C>
	{
	public:
		/**
		 * Create a new OLVQ1 vector quantizer. There are no training
		 * samples as the internal code book is used as such.
		 *
		 * @param measure the proximity measure to be used in measuring
		 * distances between vectors (autorelease).
		 * @param classCount the number of classes in the data
		 **/
		OLVQ1(ProximityMeasure<double>* measure = new EuclideanDistance<double>,
						int classCount=1) : VQClassifier<I,C>(measure,classCount) {}
		/**
		 * Create a new OLVQ1 vector quantizer. There are no training
		 * samples as the internal code book is used as such.
		 *
		 * @param measure the proximity measure to be used in measuring
		 * distances between vectors.
		 * @param classCount the number of classes in the data
		 **/
		OLVQ1(ProximityMeasure<double>& measure,
					int classCount=1) : VQClassifier<I,C>(measure,classCount) {}

		/**
		 * Train the code book.
		 *
		 * @param trainingSamples the samples to use in training
		 * @param initialCodeBook the code book to start with. Typically
		 * randomly selected training samples.
		 * @param initialAlpha the learning rate parameter, [0,1].
		 * @param iterations the number of iterations to run. If set to
		 * zero, 40 times the number of code vectors is used.
		 **/
		void train(const util::List<Sample<double,I,C> >& trainingSamples,
							 const util::List<Sample<double,I,C> >& initialCodeBook,
							 double initialAlpha = 0.3,
							 unsigned int iterations = 0);
	};
	

	template <class I, class C> void OLVQ1<I,C>::train(const util::List<Sample<double,I,C> >& trainingSamples,
																										 const util::List<Sample<double,I,C> >& initialCodeBook,
																										 double initialAlpha,
																										 unsigned int iterations)
	{
		_lstCodeBook = initialCodeBook;
		util::List<double> alpha(_lstCodeBook.getLength());
		alpha.setLength(_lstCodeBook.getLength(),initialAlpha);

		if (!iterations)
			iterations = 40*_lstCodeBook.getLength();

		for (int t=iterations;t--;)
			{
				for (int i=trainingSamples.getLength();i--;)
					{
						int index = getBinIndex(trainingSamples[i]);
						util::List<double> tmp(trainingSamples[i].featureVector());
						tmp.subtract(_lstCodeBook[index].featureVector());
						if ((int)_lstCodeBook[index].trueClass() == -1 ||
								_lstCodeBook[index].trueClass() == trainingSamples[i].getTrueClass())
							{
								alpha[index] /= (1+alpha[index]);
								_lstCodeBook[index].featureVector().add(tmp*alpha[index]);
							}
						else
							{
								alpha[index] /= (1-alpha[index]);
								_lstCodeBook[index].featureVector().subtract(tmp*alpha[index]);
							}
						if (alpha[index] > initialAlpha)
							alpha[index] = initialAlpha;
					}
			}
	}
}	

#endif
