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

#ifndef _DISTRIBUTION_H
#define _DISTRIBUTION_H

#include <List.h>
#include <Exception.h>

using namespace util;

namespace prapi
{
	/**
	 * Distribution is a multi-dimensional list that can be accessed
	 * linearly as any list.
	 **/
	class Distribution : public List<double>
	{
	public:
		/**
		 * Create a one-dimensional distribution.
		 * @param length the length of the single dimension
		 **/
		Distribution(int length);

		/**
		 * Create a new multi-dimensional distribution.
		 * @param dimensions the lengths of each dimension. The size
		 *                   of this list determines the number of
		 *                   dimensions, and each value in the list
		 *                   determines the length of the corresponding
		 *                   dimension.
		 **/
		Distribution(const List<int>& dimensions);

		/**
		 * Create a copy of <i>other</i>.
		 **/
		Distribution(const Distribution& other);

		/**
		 * Set this distribution to be equal to <i>other</i>.
		 **/
		Distribution& operator= (const Distribution& other);
		
		/**
		 * Normalize the distribution by dividing each element by the sum
		 * of all elements.
		 **/
		void normalize(void);

		/**
		 * Set the value of a distribution entry.
		 * @param coordinates the coordinates of the entry. The length of
		 *                    this list must equal to the number of
		 *                    dimensions in this distribution.
		 * @param value the new value
		 **/
		void setElementAt(const List<int>& coordinates, double value);

		/**
		 * Get the value of a distribution entry.
		 * @param coordinates the coordinates of the entry. The length of
		 *                    this list must equal to the number of
		 *                    dimensions in this distribution.
		 * @return the value in the given coordinates
		 **/
		double getElementAt(const List<int>& coordinates) const;
		/**
		 * Get the value of a distribution entry.
		 * @param coordinates the coordinates of the entry. The length of
		 *                    this list must equal to the number of
		 *                    dimensions in this distribution.
		 * @return the value in the given coordinates
		 **/
		double& elementAt(const List<int>& coordinates);

		/**
		 * Calculate a marginal distribution by "flattening", i.e. integrating
		 * over the given dimension.
		 * @param dimension a zero-based dimension index
		 * @return a new distribution with a number of dimensions one
		 *         less than in the current distribution.
		 **/
		Distribution getMarginalDistribution(int dimension) const;

		/**
		 * Add the corresponding entries in <i>lst</i> to this
		 * distribution.
		 **/
		template <class T> void addDistribution(const List<T>& lst) throw (InvalidArgumentException&);
		/**
		 * Remove the corresponding entries in <i>lst</i> from this
		 * distribution.
		 **/
		template <class T> void subtractDistribution(const List<T>& lst) throw (InvalidArgumentException&);

		/**
		 * Add the corresponding entries in <i>other</i> to this
		 * distribution. Note: in List, this operator concatenates two
		 * Lists.
		 **/
		template <class T> void operator+= (const List<T>& other) { addDistribution(other); }
		/**
		 * Remove the corresponding entries in <i>other</i> from this
		 * distribution. Note: in List, this operator removes entries from
		 * the target list.
		 **/
		template <class T> void operator-= (const List<T>& other) { subtractDistribution(other); }

		/**
		 * Multiply all values in the distribution by a constant value.
		 **/
		friend Distribution operator* (const Distribution& dist, double value);
		/**
		 * Divide all values in the distribution by a constant value.
		 **/
		friend Distribution operator/ (const Distribution& dist, double value);

		/**
		 * Sum two distributions together.
		 * @see #operator+=(List)
		 **/
		template <class T> friend Distribution& operator+ (Distribution& dist, List<T>& other);
		/**
		 * Subtract two distributions from each other.
		 * @see #operator-=(List)
		 **/
		template <class T> friend Distribution& operator- (Distribution& dist, List<T>& other);

	private:
		List<int> _lstDimensions, _lstSteps;
	};



	template <class T> Distribution& operator+ (Distribution& dist, List<T>& other)
	{
		Distribution result(dist);
		result.addDistribution(other);
		return result;
	}
	
	template <class T> Distribution& operator- (Distribution& dist, List<T>& other)
	{
		Distribution result(dist);
		result.subtractDistribution(other);
		return result;
	}
	
	template <class T> void Distribution::addDistribution(const List<T>& lst) throw (InvalidArgumentException&)
	{
		if (_iCurrentItems > 0 && _iCurrentItems != lst.getLength())
			throw InvalidArgumentException("Distribution::addDistribution(List<>&): List lengths do not match.");

		if (_iCurrentItems > 0)
			{
				for (int i=0;i<_iCurrentItems;i++)
					_internalArray[i] += (double)lst[i];
			}
		else
			{
				setCapacity(lst.getLength());
				for (int i=0;i<lst.getLength();i++)
					addElement((double)lst[i]);
			}							 
	}

	template <class T> void Distribution::subtractDistribution(const List<T>& lst) throw (InvalidArgumentException&)
	{
		if (_iCurrentItems > 0 && _iCurrentItems != lst.getLength())
			throw InvalidArgumentException("Distribution::subtractDistribution(List<>&): List lengths do not match.");

		if (_iCurrentItems > 0)
			{
				for (int i=0;i<_iCurrentItems;i++)
					_internalArray[i] -= (double)lst[i];
			}
		else
			{
				setCapacity(lst.getLength());
				for (int i=0;i<lst.getLength();i++)
					addElement(-((double)lst[i]));
			}							 
	}
}

#endif
