/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * 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.6 $
 *********************************************************************/

#ifndef _COMPUTABLE_H
#define _COMPUTABLE_H

#include "SmartPtr.h"
#include "Exception.h"
#include <iostream>

namespace util
{
	/**
	 * Computation exception is thrown whenever a computation cannot be
	 * successfully carried out.
	 **/
	class ComputationException : public Exception
	{
	public:
		ComputationException(std::string msg) : Exception(msg) {}
	};

	/**
	 * OperationNotDefinedException is thrown when an operation for
	 * certain computable types is not defined.
	 **/
	class OperationNotDefinedException : public ComputationException
	{
	public:
		OperationNotDefinedException(std::string msg) : ComputationException(msg) {}
		OperationNotDefinedException(const OperationNotDefinedException& other) : ComputationException(other.getMessage()) {}
	};

	/**
	 * Computable is a base class for all objects that can be used in
	 * arithmetic operations unaware of their type. That is, Computable
	 * references can be used even though one does not know the actual
	 * types they point to.
	 **/
	class Computable : virtual public Object
	{
	public:
		/**
		 * Calculate the sum of <i>this</i> and <i>c</i>. Return a pointer
		 * to a newly allocated object which may be of any computable
		 * type. The caller is responsible for releasing the pointer. If
		 * <i>switched</i> is true, the result should be c + (<i>this</i>)
		 * instead of (<i>this</i>) + c, although for most types it should
		 * not make a difference. The <i>switched</i> helps developers as
		 * they do not need to implement the plus operation twice for any
		 * new type. The very same routine will calculate both
		 * String+Integer and Integer+String, for example, and there is no
		 * need to alter both classes.
		 *
		 * @exception ComputationException& if the operation cannot be
		 * performed. This exception is throw by default by all
		 * computation methods. Subclasses must override what is
		 * necessary.
		 **/
		virtual SmartPtr<Computable> plus(const Computable& c, bool switched = false) const throw (ComputationException&)
		{ throw createException("+",c); }
		/**
		 * Calculate the difference of <i>this</i> and <i>c</i>.
		 *
		 * @see #plus(Computable&)
		 **/
		virtual SmartPtr<Computable> minus(const Computable& c) const throw (ComputationException&)
		{ throw createException("-",c); }
		/**
		 * Calculate the product of <i>this</i> and <i>c</i>.
		 *
		 * @see #plus(Computable&)
		 **/
		virtual SmartPtr<Computable> multiply(const Computable& c, bool switched = false) const throw (ComputationException&)
		{ throw createException("*",c); }
		/**
		 * Calculate the quotient of <i>this</i> and <i>c</i>.
		 *
		 * @see #plus(Computable&)
		 **/
		virtual SmartPtr<Computable> divide(const Computable& c) const throw (ComputationException&)
		{ throw createException("/",c); }
		/**
		 * Calculate the modulus of <i>this</i> and <i>c</i>.
		 *
		 * @see #plus(Computable&)
		 **/
		virtual SmartPtr<Computable> modulus(const Computable& c) const throw (ComputationException&)
		{ throw createException("%",c); }

		/**
		 * Array index operator calls arrayIndex(c).
		 **/
		SmartPtr<Computable> operator[] (const Computable& c) const throw (ComputationException&) { return arrayIndex(c); }
		/**
		 * If <i>this</i> is an array of something, then return the
		 * <i>c</i>th something. As a default, if the parameter is a
		 * numeric type with a value of 0, then a clone of the object
		 * itself is returned. This way it is not necessary to distinguish
		 * between one-element vectors and scalars.
		 **/
		virtual SmartPtr<Computable> arrayIndex(const Computable& c) const throw (ComputationException&);

		/**
		 * Function call operator calls functionCall(c).
		 **/
		SmartPtr<Computable> operator() (const Computable& c) const throw (ComputationException&) { return functionCall(c); }
		/**
		 * If <i>this</i> is a thing that can be called, then call it with
		 * <i>c</i> as a parameter.
		 **/
		virtual SmartPtr<Computable> functionCall(const Computable& c) const throw (ComputationException&)
		{
			throw OperationNotDefinedException("Computable::arrayIndex(const Computable&): " +
																				 getClassName() + "(" + c.getClassName() + ") not defined.");
		}

		/**
		 * Negation operator calls negate().
		 **/
		SmartPtr<Computable> operator- () const throw (ComputationException&) { return negate(); }
		/**
		 * If a negation can be defined for <i>this</i>, then return it.
		 **/
		virtual SmartPtr<Computable> negate() const throw (ComputationException&)
		{
			throw OperationNotDefinedException("Computable::negate(): " + getClassName() + " cannot be negated.");
		}

		/**
		 * Complement operator calls complement().
		 **/
		SmartPtr<Computable> operator! () const throw (ComputationException&) { return complement(); }
		/**
		 * If a complement can be defined for <i>this</i>, then return it.
		 **/
		virtual SmartPtr<Computable> complement() const throw (ComputationException&);
		
		/**
		 * Compare two computable objects. This method should return a
		 * negative number, zero, or a positive number if <i>c</i> is
		 * larger than, equal to or smaller than <i>this</i>,
		 * respectively. Override this method if a simple one-dimensional
		 * comparison is sufficient for your custom type. If not, then you
		 * must override the less, greater etc. methods.
		 **/
		virtual int compare(const Computable& c) const throw (ComputationException&)
		{
			throw OperationNotDefinedException("Computable::compare(Computable&): " + getClassName() + " and " +
																				 c.getClassName() + " cannot be compared.");
		}

		/**
		 * Check if <i>this</i> is less than <i>c</i>. The default return
		 * type is Boolean, and it is formed by calling compare(c).
		 **/
		virtual SmartPtr<Computable> less(const Computable& c) const throw (ComputationException&);
		/**
		 * Check if <i>this</i> is greater than <i>c</i>. The default return
		 * type is Boolean, and it is formed by calling compare(c).
		 **/
		virtual SmartPtr<Computable> greater(const Computable& c) const throw (ComputationException&);
		/**
		 * Check if <i>this</i> is less than or equal to <i>c</i>. The
		 * default return type is Boolean, and it is formed by calling
		 * compare(c).
		 **/
		virtual SmartPtr<Computable> lessOrEqual(const Computable& c) const throw (ComputationException&);
		/**
		 * Check if <i>this</i> is greater than or equal to <i>c</i>. The
		 * default return type is Boolean, and it is formed by calling
		 * compare(c).
		 **/
		virtual SmartPtr<Computable> greaterOrEqual(const Computable& c) const throw (ComputationException&);
		/**
		 * Check if <i>this</i> is equal to <i>c</i>. The default return
		 * type is Boolean, and it is formed by calling compare(c).
		 **/
		virtual SmartPtr<Computable> equal(const Computable& c) const throw (ComputationException&);
		/**
		 * Check if <i>this</i> is different from <i>c</i>. The default
		 * return type is Boolean, and it is formed by calling compare(c).
		 **/
		virtual SmartPtr<Computable> different(const Computable& c) const throw (ComputationException&);

		/**
		 * Perform a logical and operation on <i>this</i> and <i>c</i>.
		 * The default implementation tries to cast both objects to the
		 * primitive type 'bool'. It then performs a logical and operation
		 * on the primitive types and returns a new Boolean object
		 * containing th result.
		 **/
		virtual SmartPtr<Computable> logAnd(const Computable& c) const throw (ComputationException&);
		/**
		 * Perform a logical or operation on <i>this</i> and <i>c</i>. The
		 * default implementation tries to cast both objects to the
		 * primitive type 'bool'. It then performs a logical or operation
		 * on the primitive types and returns a new Boolean object
		 * containing th result.
		 **/
		virtual SmartPtr<Computable> logOr(const Computable& c) const throw (ComputationException&);

		/**
		 * Calculate a binary AND operation on <i>this</i> and <i>c</i>.
		 **/
		virtual SmartPtr<Computable> binAnd(const Computable& c) const throw (ComputationException&)
		{	throw createException("&",c);	}
		/**
		 * Calculate a binary OR operation on <i>this</i> and <i>c</i>.
		 **/
		virtual SmartPtr<Computable> binOr(const Computable& c) const throw (ComputationException&)
		{ throw createException("|",c); }
		/**
		 * Calculate a binary XOR operation on <i>this</i> and <i>c</i>.
		 **/
		virtual SmartPtr<Computable> binXor(const Computable& c) const throw (ComputationException&)
		{ throw createException("^",c); }

		//  		virtual bool and(Computable& c) throw (ComputationException&) { throw createException("&&",c); }
		//  		virtual bool or(Computable& c) throw (ComputationException&) { throw createException("||",c); }

		/**
		 * Cast a computable object to the primitive type 'bool'.
		 **/
		virtual operator bool() const throw (ComputationException&)
		{
			throw OperationNotDefinedException("Computable::operator bool(): " + getClassName() +
																				 " cannot be converted to a boolean expression.");
		}

	protected:
		ComputationException createException(std::string op, const Computable& c) const
		{
			return OperationNotDefinedException("Computable::createException(string,Computable&): " +
																					getClassName() + " " + op + " " +
																					c.getClassName() + " is not defined.");
		}
	};

	/**
	 * Sum two computable objects together. If c1+c2 is not defined,
	 * then c2+c1 is tried with the switched flag on.
	 *
	 * @return c1+c2 if defined or c2+c1 if defined
	 * @see Computable::plus(const Computable&, bool)
	 **/
	inline SmartPtr<Computable> operator+ (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{
		try	{	return c1.plus(c2); }
		catch (ComputationException& onde)
			{
				return c2.plus(c1,true);
			}
		//  		catch (Exception& ex)
		//  			{
		//  				std::cerr << "Caught " << ex.getClassName() << std::endl;
		//  			}
	}
	/**
	 * Calculate the difference between any two computable objects. If
	 * the operation is not defined, c1 + (-c2) is tried.
	 *
	 * @return c1-c2 if defined, or c1 + (-c2) if defined
	 **/
	inline SmartPtr<Computable> operator- (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{
		try { return c1.minus(c2); }
		catch (ComputationException& onde)
			{
				return c1 + -c2;
			}
	}
	/**
	 * Calculate the product of two computable objects. If c1*c2 is not
	 * defined, then c2*c1 is tried with the switched flag on.
	 *
	 * @return c1*c2 if defined or c2*c1 if defined
	 * @see Computable::plus(const Computable&, bool)
	 **/
	inline SmartPtr<Computable> operator* (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{
		try { return c1.multiply(c2); }
		catch (OperationNotDefinedException& onde)
			{
				return c2.multiply(c1,true);
			}
	}
	/**
	 * Calculate the quotient of two computable objects.
	 *
	 * @return c1/c2
	 **/
	inline SmartPtr<Computable> operator/ (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.divide(c2); }
	/**
	 * Calculate the modulus of two computable objects.
	 *
	 * @return c1%c2
	 **/
	inline SmartPtr<Computable> operator% (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.modulus(c2); }

	/**
	 * Perform a binary and operation on two computable objects.
	 *
	 * @return c1 & c2
	 **/
	inline SmartPtr<Computable> operator& (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.binAnd(c2); }
	/**
	 * Perform a binary or operation on two computable objects.
	 *
	 * @return c1 | c2
	 **/
	inline SmartPtr<Computable> operator| (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.binOr(c2); }
	/**
	 * Perform a binary xor operation on two computable objects.
	 *
	 * @return a c1 ^ c2
	 **/
	inline SmartPtr<Computable> operator^ (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.binXor(c2); }

	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 == c2)
	 **/
	inline SmartPtr<Computable> operator== (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.equal(c2); }
	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 != c2)
	 **/
	inline SmartPtr<Computable> operator!= (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.different(c2); }
	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 < c2)
	 **/
	inline SmartPtr<Computable> operator< (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.less(c2); }
	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 > c2)
	 **/
	inline SmartPtr<Computable> operator> (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.greater(c2); }
	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 <= c2)
	 **/
	inline SmartPtr<Computable> operator<= (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.lessOrEqual(c2); }
	/**
	 * Compare two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 >= c2)
	 **/
	inline SmartPtr<Computable> operator>= (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.greaterOrEqual(c2); }
	/**
	 * Perform a logical and operation on two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 && c2)
	 **/
	inline SmartPtr<Computable> operator&& (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.logAnd(c2); }
	/**
	 * Perform a logical or operation on two computable objects.
	 *
	 * @return a computable object that represents the result of (c1 || c2)
	 **/
	inline SmartPtr<Computable> operator|| (const Computable& c1, const Computable& c2) throw (ComputationException&)
	{	return c1.logOr(c2); }


#if __GNUC__ < 3
	inline SmartPtr<Computable> operator== (const SmartPtr<Computable>& c1, const SmartPtr<Computable>& c2) throw (ComputationException&)
	{	return *c1 == *c2; }
#endif
}

#endif
