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

#ifndef _SAMPLE_H
#define _SAMPLE_H

#include <List.h>
#include <iostream>
#include <sstream>
#include <string>
#include <StreamTokenizer.h>
#include <Util.h>
#include <xml/XMLParser.h>
#include <memory>

using namespace util;

namespace prapi
{
	template <class T, class I, class C> class Sample;
	template <class T, class I, class C> std::ostream& operator<< (std::ostream& sout, const Sample<T,I,C>& smpl);
	template <class T, class I, class C> std::istream& operator>> (std::istream& sin, Sample<T,I,C>& smpl);
	template <class T, class I, class C> std::istream& oldRead(std::istream& sin, Sample<T,I,C>& smpl);

	class ImageSampleIdentifier;
	std::ostream& operator<< (std::ostream& sout, const ImageSampleIdentifier& obj);
	std::istream& operator>> (std::istream& sin, ImageSampleIdentifier& obj);
	std::istream& oldRead(std::istream& sin, ImageSampleIdentifier& obj);

	/**
	 * A class used to identify samples taken from images. This
	 * identifier is useful when samples are created from an image or
	 * sub-images of a larger image. It stores the image name and
	 * sub-image coordinates.<p>
	 *
	 * To use this identifier, you must instantiate your samples as
	 * follows:<br>
	 * <pre>
	 * Sample<int,ImageSampleIdentifier> smpl;
	 * </pre>
	 *
	 * If you omit "ImageSampleIdentifier", a string identifier will be
	 * used by default. A sample stored with a properly formatted string
	 * identifier can be typecasted to a sample with an
	 * ImageSampleIdentifier.
	 * @see #setIdentifier(const string&)
	 **/
	class ImageSampleIdentifier : virtual public Object
	{
	public:
		/**
		 * The default constructor. Creates an identifier with no name and
		 * all coordinates set to zero.
		 **/
		ImageSampleIdentifier(void) : _iX(0),_iY(0),_iWidth(0),_iHeight(0) {}
		/**
		 * Initialize a new identifier using an identifier string.
		 * @see setIdentifier(const string&)
		 **/
		ImageSampleIdentifier(const std::string& str) { setIdentifier(str); }
		/**
		 * Create a new identifier with the given image name and sub-image
		 * coordinates.
		 **/
		ImageSampleIdentifier(const std::string& name, int x, int y, int width, int height) :
			_strName(name), _iX(x), _iY(y), _iWidth(width), _iHeight(height) {}
		/**
		 * Create a copy of another identifier.
		 **/
		ImageSampleIdentifier(const ImageSampleIdentifier& other) :
			_strName(other._strName), _iX(other._iX), _iY(other._iY), _iWidth(other._iWidth), _iHeight(other._iHeight) {}

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

		/**
		 * Set the contents of this identifier according to the given
		 * identifier string. The format of the string must be as
		 * follows:<br>
		 *
		 * <pre>
		 * imagename x y width height
		 * </pre>
		 *
		 * The imagename field must not contain whitespace characters. Any
		 * number of whitespace characters can be used to separate the x,
		 * y, width and height fields.

		 * @param str the identifier string
		 **/
		void setIdentifier(const std::string& str);
		/**
		 * Get a formatted identifier string as described in setIdentifier.
		 **/
		std::string getIdentifier(void);

		/**
		 * @see #setIdenfitier(const string&)
		 **/
		ImageSampleIdentifier& operator= (const std::string& str) { setIdentifier(str); return *this; }

		/**
		 * @see #getIdentifier(void)
		 **/
		operator std::string() { return getIdentifier(); }
		
		/**
		 * Compare two identifiers. The result is true if and only if both
		 * identifiers have the same name and the same sub-image
		 * coordinates.
		 **/
		inline friend bool operator== (const ImageSampleIdentifier& obj1, const ImageSampleIdentifier& obj2)
		{
			return (obj1._strName == obj2._strName) &&
				(obj1._iX == obj2._iX) &&
				(obj1._iY == obj2._iY) &&
				(obj1._iWidth == obj2._iWidth) &&
				(obj1._iHeight == obj2._iHeight);
		}
		/**
		 * Compare two identifiers. The result is false if and only if both
		 * identifiers have the same name and the same sub-image
		 * coordinates.
		 **/
		inline friend bool operator!= (const ImageSampleIdentifier& obj1, const ImageSampleIdentifier& obj2)
		{
			return !(obj1==obj2);
		}

		/**
		 * Get the name of the image, the sample this object identifies,
		 * was taken from.
		 **/
		std::string getName(void) const { return _strName; }
		/**
		 * Get the name of the image, the sample this object identifies,
		 * was taken from.
		 **/
		void setName(const std::string& name) { _strName = name; }
		/**
		 * Set the name of the image, the sample this object identifies,
		 * was taken from.
		 **/
		std::string& name(void) { return _strName; }

		/**
		 * Get the X coordinate for this sample identifier.
		 **/
		int getX(void) const { return _iX; }
		/**
		 * Get the X coordinate for this sample identifier.
		 **/
		int& x(void) { return _iX; }
		/**
		 * Set the X coordinate for this sample identifier.
		 **/
		void setX(int newX) { _iX = newX; }

		/**
		 * Get the Y coordinate for this sample identifier.
		 **/
		int getY(void) const { return _iY; }
		/**
		 * Get the Y coordinate for this sample identifier.
		 **/
		int& y(void) { return _iY; }
		/**
		 * Set the Y coordinate for this sample identifier.
		 **/
		void setY(int newY) { _iY = newY; }

		/**
		 * Get the width of the image sample.
		 **/
		int getWidth(void) const { return _iWidth; }
		/**
		 * Get the width of the image sample.
		 **/
		int& width(void) { return _iWidth; }
		/**
		 * Set the width of the image sample.
		 **/
		void setWidth(int newWidth) { _iWidth = newWidth; }

		/**
		 * Get the height of the image sample.
		 **/
		int getHeight(void) const { return _iHeight; }
		/**
		 * Get the height of the image sample.
		 **/
		int& height(void) { return _iHeight; }
		/**
		 * Set the height of the image sample.
		 **/
		void setHeight(int newHeight) { _iHeight = newHeight; }

		friend std::ostream& operator<< (std::ostream& sout, const ImageSampleIdentifier& obj);
		friend std::istream& operator>> (std::istream& sin, ImageSampleIdentifier& obj);
		friend std::istream& oldRead(std::istream& sin, ImageSampleIdentifier& obj);
		
	private:
		std::string _strName;
		int _iX, _iY, _iWidth, _iHeight;
	};

	/**
	 * A template for a sample. A sample consists of a feature vector
	 * (of any type), and identifier and two classifications: the
	 * correct and the supposed one. The template parameters T, I and C
	 * indicate the type of the feature vector, the type of the
	 * indentifier and the type of the classifications, respectively.<p>
	 *
	 * The identifier is used to distinguish different samples. For an
	 * example, image samples could store the image name and coordinates
	 * as an identifier object. The default type for the identifier is
	 * <i>string</i>. And identifier object must have operator== defined
	 * in addition to the usual constructors and an assignment
	 * operator.<p>
	 *
	 * The rationale behind the type of the classification is somewhat
	 * more complicated. In most applications, the default type
	 * (<i>int</i>) works just fine and the application programmer can
	 * totally discard it. By default, the integer-typed classification
	 * represents a class index as such. If more sophisticated (e.g.
	 * fuzzy or probabilistic) classifications are needed, some
	 * non-primitive types must be used. The class used to indicate
	 * classification must provide the following services:
	 * <ul>
	 * <li>Default constructor.
	 * <li>Copy constructor.
	 * <li>Assignment operator.
	 * <li>Constructor for an integer argument. This is needed because
	 * the value -1 is used to indicate unknown classification.
	 * <li>operator= for an integer argument. The reason is the same
	 * as above.
	 * <li>A typecast to int operator. Since the classification must
	 * be converted to class index in some occasions.
	 * </ul>
	 * That is to say, the class must be defined about like this:<br>
	 * <pre>
	 * class MyClassification
	 * {
	 * public:
	 *   MyClassification(const MyClassification& other);
	 *   MyClassification(int classification = -1); //-1 means not defined
	 *
	 *   MyClassification& operator= (const MyClassification& other);
	 *   MyClassification& operator= (int classification);
	 *   operator int() { return classificationSomehowConvertedToClassIndex; }
	 * };
	 * </pre>
	 **/
	template <class T, class I=std::string, class C=int> class Sample : virtual public Object
	{
	public:
		/**
		 * Create a new sample.
		 *
		 * @param trueClass the true class of the sample. The default
		 * value (-1) means not defined.
		 **/
		Sample(C trueClass = -1) : _lstFeatureVector(1), _trueClass(trueClass), _classification(-1) {}
		/**
		 * Create a new sample.
		 *
		 * @param features A feature vector for the sample.
		 * @param trueClass the true class of the sample. The default
		 * value (-1) means not defined.
		 **/
		Sample(const List<T>& features, C trueClass = -1) : _lstFeatureVector(features), _trueClass(trueClass), _classification(-1) {}
		/**
		 * Copy a sample.
		 **/
		Sample(const Sample& other);
		/**
		 * Copy a sample and perform a simultaneous typecast.
		 **/
		template <class U, class V, class W> Sample(const Sample<U,V,W>& other);

		/**
		 * Replace the contents of this sample.
		 **/
		Sample& operator= (const Sample& other);

		/**
		 * Get the feature vector. The feature vector can contain any
		 * values. It may hold an integer-valued histogram, a
		 * continuous-valued multi-dimensional distribution or anything
		 * else.
		 **/
		List<T> getFeatureVector(void) const { return _lstFeatureVector; }
		/**
		 * Get the feature vector. The feature vector can contain any
		 * values. It may hold an integer-valued histogram, a
		 * continuous-valued multi-dimensional distribution or anything
		 * else.
		 **/
		List<T>& featureVector(void) { return _lstFeatureVector; }
		/**
		 * Get the feature vector. The feature vector can contain any
		 * values. It may hold an integer-valued histogram, a
		 * continuous-valued multi-dimensional distribution or anything
		 * else.
		 **/
		const List<T>& featureVector(void) const { return _lstFeatureVector; }
		/**
		 * Set the feature vector.
		 **/
		void setFeatureVector(const List<T>& vec) { _lstFeatureVector = vec; }

		/**
		 * Compare two samples. Samples are defined to be equal if their
		 * identifiers are equal.
		 **/
		template <class U, class V, class W> inline friend bool operator== (const Sample<U,V,W>& smpl1, const Sample<U,V,W>& smpl2);
		/**
		 * Compare two samples. Samples are defined to be different if
		 * their identifiers are different.
		 **/
		template <class U, class V, class W> inline friend bool operator!= (const Sample<U,V,W>& smpl1, const Sample<U,V,W>& smpl2);

		/**
		 * Get the true (known) class for this sample.
		 **/
		C& trueClass(void) { return _trueClass; }
		/**
		 * Get the true (known) class for this sample.
		 **/
		C getTrueClass(void) const { return _trueClass; }
		/**
		 * Set the true (known) class for this sample.
		 **/
		void setTrueClass(C classification) { _trueClass = classification; }
		
		/**
		 * Get the classification of this sample.
		 **/
		C& classification(void) { return _classification; }
		/**
		 * Get the classification of this sample.
		 **/
		C getClassification(void) const { return _classification; }
		/**
		 * Set the classification of this sample.
		 **/
		void setClassification(C classification) { _classification = classification; }

		/**
		 * Get the identifier of this sample. The identifier provides
		 * means to distinguish between different samples of any type.
		 **/
		I& identifier(void) { return _identifier; }
		/**
		 * Get the identifier of this sample. The identifier provides
		 * means to distinguish between different samples of any type.
		 **/
		const I& identifier(void) const { return _identifier; }
		/**
		 * Get the identifier of this sample. The identifier provides
		 * means to distinguish between different samples of any type.
		 **/
		I getIdentifier(void) const { return _identifier; }
		/**
		 * Set the identifier of this sample.
		 **/
		void setIdentifier(const I& identifier) { _identifier = identifier; }

		/**
		 * Pretty-print the contents of a sample to a stream.
		 * @param sout the output stream
		 * @param classList names for the classes a sample may belong to
		 **/
		void print(std::ostream& sout, const List<std::string>& classList);
		/**
		 * Pretty-print all samples in a list.
		 **/
		static void print(const List<Sample>& samples, std::ostream& sout, const List<std::string>& classList);

		/**
		 * Typecast a sample to another type. The conversion will succeed
		 * only if the types of the feature vector, identifier and
		 * classification are typecastable to the wanted types. For
		 * example:<br>
		 * <pre>
		 * Sample<double,string,int> smpl1;
		 * Sample<int,string,int> smpl2;
		 * Sample<double,ImageSampleIdentifier,int> smpl3;
		 * Sample<int,int,int> smpl4;
		 *
		 * smpl1 = (Sample<double,string,int>)smpl2; //valid
		 * smpl3 = (Sample<double,ImageSampleIdentifier,int)smpl1; //valid
		 * smpl2 = (Sample<int,string,int>)smpl4; //invalid! int is not typecastable to string
		 * </pre>

		 * Actually, the typecast operators in the previous examples are
		 * not necessary because the assignment operator makes automatic
		 * typecasts when possible.
		 **/
		template <class U, class V, class W> operator Sample<U,V,W>();
		/**
		 * Copy the contents of another sample and perform a simultaneous
		 * typecast. See the typecast operator for more information.
		 **/
		template <class U, class V, class W> Sample& operator= (const Sample<U,V,W>& other);
		

		template <class U, class V, class W> friend std::ostream& operator<< (std::ostream& sout, const Sample<U,V,W>& smpl);
		template <class U, class V, class W> friend std::istream& operator>> (std::istream& sin, Sample<U,V,W>& smpl);

		template <class U, class V, class W> friend std::istream& oldRead(std::istream& sin, Sample<U,V,W>& smpl);
	protected:
		/**
		 * The feature vector for this sample.
		 **/
		List<T> _lstFeatureVector;
		/**
		 * The true (known) class for this sample.
		 **/
		C _trueClass;
		/**
		 * The classification for this sample.
		 **/
		C _classification;
		/**
		 * The identifier used to distinguish between samples. Typically a
		 * string.
		 **/
		I _identifier;
	};

	typedef Sample<int> IntegerSample;
	typedef Sample<float> FloatSample;
	typedef Sample<double> DoubleSample;

	typedef List<Sample<int> > IntegerSampleList;
	typedef List<Sample<float> > FloatSampleList;
	typedef List<Sample<double> > DoubleSampleList;

	template <class T, class I, class C> bool operator== (const Sample<T,I,C>& smpl1, const Sample<T,I,C>& smpl2)
	{
		return (smpl1._identifier == smpl2._identifier);
	}
	template <class T, class I, class C> bool operator!= (const Sample<T,I,C>& smpl1, const Sample<T,I,C>& smpl2)
	{
		return (smpl1._identifier != smpl2._identifier);
	}

	template <class T, class I, class C> Sample<T,I,C>::Sample(const Sample<T,I,C>& other) :
		_lstFeatureVector(other._lstFeatureVector), _trueClass(other._trueClass),
		_classification(other._classification), _identifier(other._identifier) {}

	template <class T, class I, class C>
	template <class U, class V, class W> Sample<T,I,C>::Sample(const Sample<U,V,W>& other) :
		_lstFeatureVector(other.getFeatureVector()), _trueClass(other.getTrueClass()),
		_classification(other.getClassification()), _identifier(other.getIdentifier()) {}

	template <class T, class I, class C> Sample<T,I,C>& Sample<T,I,C>::operator= (const Sample<T,I,C>& other)
	{
		_lstFeatureVector = other._lstFeatureVector;
		_trueClass = other._trueClass;
		_classification = other._classification;
		_identifier = other._identifier;
		return *this;
	}

	template <class T, class I, class C>
	template <class U, class V, class W> Sample<T,I,C>& Sample<T,I,C>::operator= (const Sample<U,V,W>& other)
	{
		_lstFeatureVector = other.getFeatureVector();
		_trueClass = other.getTrueClass();
		_classification = other.getClassification();
		_identifier = other.getIdentifier();
		return *this;
	}

	template <class T, class I, class C>
	template <class U, class V, class W> Sample<T,I,C>::operator Sample<U,V,W>()
	{
		Sample<U,V,W> result(lstFeatureVector, (W)_trueClass);
		result.setClassification((W)_classification);
		result.setIdentifier((V)_identifier);
		return *this;
	}

	template <class T, class I, class C> void Sample<T,I,C>::print(std::ostream& sout, const List<std::string>& classList)
	{
		sout << _identifier << ": " << classList[(int)_trueClass] << " -> " << classList[(int)_classification];
		//<< _iClassIndex << " -> " << _iClassification << ", "
	}

	template <class T, class I, class C> void Sample<T,I,C>::print(const List<Sample<T,I,C> >& samples,
																																 std::ostream& sout, const List<std::string>& classList)
	{
		for (int i=0;i<samples.getLength();i++)
			{
				samples[i].print(sout,classList);
				sout << endl;
			}
	}

	template <class T, class I, class C> std::ostream& operator<< (std::ostream& sout, const Sample<T,I,C>& smpl)
	{
		sout << "<sample>" << endl;
		sout << "<trueClass>";
		Util::writeXMLItem(sout,smpl._trueClass);
		sout << "</trueClass>" << endl;
		sout << "<classification>";
		Util::writeXMLItem(sout,smpl._classification);
		sout << "</classification>" << endl;
		sout << "<identifier>";
		Util::writeXMLItem(sout,smpl._identifier);
		sout << "</identifier>" << endl;
		sout << "<featureVector>" << smpl._lstFeatureVector << "</featureVector>" << endl;
		sout << "</sample>";
		return sout;
	}

#define checkClosingTag(tag,name) \
(tag.get() && tag->getNodeType() == Node::ELEMENT_NODE && \
 ((Element*)tag.get())->tagName == name && \
 ((Element*)tag.get())->tagType == Element::TAG_CLOSING)
	
	template <class T, class I, class C> std::istream& operator>> (std::istream& sin, Sample<T,I,C>& smpl)
	{
		using namespace util::xml;
		
		XMLParser parser;
		sin >> ws;
		if (sin && sin.peek() != '<')
			return oldRead(sin,smpl);

		std::auto_ptr<Node> node(parser.getNextNode(sin));
		
		if (node.get() && node->getNodeType() == Node::ELEMENT_NODE)
			{
				Element* element = (Element*)node.get();
				if (element->tagType != Element::TAG_OPENING || element->tagName != "sample")
					throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): Expecting <sample>.");

				while (true)
					{
						
						sin >> ws;
						std::auto_ptr<Node> child(parser.getNextNode(sin));
						if (!child.get() || child->getNodeType() != Node::ELEMENT_NODE)
							throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): Expecting a tag.");
						
						Element *childElement = (Element*)child.get();
						if (childElement->tagType == Element::TAG_CLOSING)
							{
								if (childElement->tagName == "sample")
									break;
								else
									throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): Misplaced </" + childElement->tagName + ">.");
							}
				
						std::string properties[] = { "trueClass","classification","identifier","featureVector" };
						int i;
						for (i=4;i--;)
							if (childElement->tagName == properties[i])
								break;
						//Unknown property
						if (i<0)
							{
								//Put the tag back and discard the whole section
								std::ostringstream stream;
								childElement->printOut(stream);
								const std::string &contents(stream.str());
								for (int i=contents.size();i--;)
									sin.putback(contents[i]);
								std::auto_ptr<Node> discarded(parser.readFragment(sin));
							}
						else
							{
								switch (i)
									{
									case 0:
										Util::readXMLItem(sin,smpl._trueClass);
										break;
									case 1:
										Util::readXMLItem(sin,smpl._classification);
										break;
									case 2:
										Util::readXMLItem(sin,smpl._identifier);
										break;
									case 3:
										sin >> smpl._lstFeatureVector;
										break;
									default:
										break;
									}
								sin >> ws;
								std::auto_ptr<Node> closing(parser.getNextNode(sin));
								if (!checkClosingTag(closing,properties[i]))
									throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): </" + properties[i] + "> missing.");
							}
					}
			}
		else
			throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): Input stream does not contain a sample.");
		return sin;
	}

#undef checkClosingTag
	
	template <class T, class I, class C> std::istream& oldRead(std::istream& sin, Sample<T,I,C>& smpl)
	{
		throw util::io::IOException("operator>> (istream&, Sample<T,I,C>&): Sorry, the support for the old format has been removed.");
	}
}

#endif
