/*
 * Copyright (C) 2007-2008 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#ifndef FORMULA_H
#define FORMULA_H

#include <stdint.h>
#include <stdlib.h>

#include <math.h>
//#include "sbuffer.h"

#include "bu/stack.h"
#include "bu/exceptionbase.h"
#include "bu/hash.h"
#include "bu/fstring.h"

namespace Bu
{
	subExceptionDecl( FormulaException );
	/**
	 * Implements a very simple formula parser that allows use of variables and
	 * custom functions.  This is based on a simple calculator-type parser that
	 * executes as it processes, accounting for operator precedence and
	 * grouping.
	 *
	 * prec = precision, a type to use for all math (except binary ops)
	 * bin = binary type, a type to hard cast all data to for binary ops
	 */
	template<typename prec, typename bin=uint32_t>
	class Formula
	{
	public:
		class Func
		{
		public:
			virtual prec operator()( prec )=0;
		};

		typedef Hash<Bu::FString, prec> varHash;
		typedef Hash<Bu::FString, Func *> funcHash;

		Formula()
		{
		}

		virtual ~Formula()
		{
			for( typename funcHash::iterator i = hFunc.begin();
				 i != hFunc.end(); i++ )
			{
				delete (*i);
			}
		}

		prec run( const Bu::FString &sFormulaSrc )
		{
			if( sFormulaSrc.isEmpty() )
				throw FormulaException("Empty formula, nothing to do.");
			try
			{
				const char *sFormula = sFormulaSrc.getStr();
				for(;;)
				{
					uint8_t tNum = nextToken( &sFormula );
					if( tNum == symSubtract )
					{
						sOper.push( symNegate );
						continue;
					}
					else if( tNum == symNot )
					{
						sOper.push( symNot );
						continue;
					}
					else if( tNum == symOpenParen )
					{
						sOper.push( tNum );
						continue;
					}
					else if( tNum == symFunction )
					{
						sOper.push( symFunction );
						continue;
					}
					else if( tNum == symEOS )
					{
						throw Bu::FormulaException(
							"Cannot end with an operator.");
					}

			oppart:	uint8_t tOpr = nextToken( &sFormula );
					if( tOpr == symEOS )
					{
						reduce();
						prec ret = sValue.top();
						sValue.clear();
						sFunc.clear();
						sOper.clear();
						return ret;
					}
					if( !sOper.isEmpty() && getPrec( sOper.top() ) >
						getPrec( tOpr ) )
					{
						reduce();
					}
					if( tOpr != symCloseParen )
					{
						sOper.push( tOpr );
					}
					else
					{
						reduce( true );
						goto oppart;
					}
				}
			}
			catch( ... )
			{
				sValue.clear();
				sFunc.clear();
				sOper.clear();
				throw;
			}
		}

		varHash hVars;
		funcHash hFunc;

	private:
		enum
		{
			symEOS,
			symAdd,
			symSubtract,
			symMultiply,
			symDivide,
			symOpenParen,
			symCloseParen,
			symNumber,
			symVariable,
			symFunction,
			symExponent,
			symNegate,
			symModulus,

			symAnd,
			symOr,
			symXor,
			symNot
		};

		typedef uint8_t symType;

		Bu::Stack<symType> sOper;
		Bu::Stack<prec> sValue;
		Bu::Stack<Bu::FString> sFunc;

	private:
		symType getPrec( symType nOper )
		{
			switch( nOper )
			{
				case symNumber:
				case symVariable:
				case symOpenParen:
				case symCloseParen:
					return 0;

				case symAdd:
				case symSubtract:
					return 1;

				case symMultiply:
				case symDivide:
				case symModulus:
					return 2;

				case symAnd:
				case symOr:
				case symXor:
					return 2;

				case symExponent:
				case symNot:
				case symNegate:
				case symFunction:
					return 3;

				default:
					return 0;
			}
		}

		symType nextToken( const char **sBuf )
		{
			for(;;)
			{
				char cbuf = **sBuf;
				++(*sBuf);
				switch( cbuf )
				{
					case '+':
						return symAdd;

					case '-':
						return symSubtract;

					case '*':
						return symMultiply;

					case '/':
						return symDivide;

					case '^':
						return symExponent;

					case '%':
						return symModulus;

					case '(':
						return symOpenParen;

					case ')':
						return symCloseParen;

					case '|':
						return symOr;

					case '&':
						return symAnd;

					case '#':
						return symXor;

					case '~':
						return symNot;

					case ' ':
					case '\t':
					case '\n':
					case '\r':
						break;

					case '\0':
						return symEOS;

					default:
						if( cbuf == '.' || (cbuf >= '0' && cbuf <= '9') )
						{
							char num[50]={cbuf};
							int nPos = 1;
							bool bDot = false;

							for(;;)
							{
								cbuf = **sBuf;
								if( cbuf == '.' )
								{
									if( bDot == false )
										bDot = true;
									else
										throw FormulaException(
											"Numbers cannot have more than one "
											". in them."
											);
								}
								if( cbuf == '.' ||
									(cbuf >= '0' && cbuf <= '9') )
								{
									num[nPos++] = cbuf;
								}
								else
								{
									num[nPos] = '\0';
									sValue.push(
										static_cast<prec>(
											strtod( num, NULL )
											)
										);
									return symNumber;
								}
								++(*sBuf);
							}
						}
						else if( (cbuf >= 'a' && cbuf <= 'z') || 
								 (cbuf >= 'A' && cbuf <= 'Z') ||
								 (cbuf == '_') )
						{
							char tok[50]={cbuf};
							int nPos = 1;

							for(;;)
							{
								cbuf = **sBuf;
								if( (cbuf >= 'a' && cbuf <= 'z') || 
									(cbuf >= 'A' && cbuf <= 'Z') ||
									(cbuf >= '0' && cbuf <= '9') ||
									cbuf == '_' || cbuf == '.' || cbuf == ':' )
								{
									tok[nPos++] = cbuf;
								}
								else
								{
									tok[nPos] = '\0';
									if( hVars.has( tok ) )
									{
										sValue.push( hVars[tok] );
										return symNumber;
									}
									else if( hFunc.has( tok ) )
									{
										sFunc.push( tok );
										return symFunction;
									}
									else
									{
										throw FormulaException(
											"No variable or function named "
											"\"%s\" exists.",
											tok
											);
									}
								}
								++(*sBuf);
							}
						}
						break;
				}
			}
		}

		void reduce( bool bCloseParen = false )
		{
			while( !sOper.isEmpty() )
			{
				uint8_t nOpr = sOper.top();
				if( nOpr == symOpenParen )
				{
					if( bCloseParen == true )
						sOper.pop();
					return;
				}
				sOper.pop();

				prec dTop = sValue.top();
				sValue.pop();

				switch( nOpr )
				{
					case symAdd:
						sValue.top() += dTop;
						break;

					case symSubtract:
						sValue.top() -= dTop;
						break;

					case symMultiply:
						sValue.top() *= dTop;
						break;

					case symDivide:
						sValue.top() /= dTop;
						break;

					case symExponent:
						sValue.top() = static_cast<prec>(
								pow( sValue.top(), dTop )
							);
						break;

					case symModulus:
						sValue.top() = static_cast<prec>(
								fmod( sValue.top(), dTop )
							);
						break;

					case symOr:
						sValue.top() = static_cast<prec>(
								static_cast<bin>(sValue.top()) |
								static_cast<bin>(dTop)
							);
						break;
					
					case symAnd:
						sValue.top() = static_cast<prec>(
								static_cast<bin>(sValue.top()) &
								static_cast<bin>(dTop)
							);
						break;
					
					case symXor:
						sValue.top() = static_cast<prec>(
								static_cast<bin>(sValue.top()) ^
								static_cast<bin>(dTop)
							);
						break;

					case symFunction:
						sValue.push( (*hFunc.get( sFunc.pop() ))( dTop ) );
						break;

					case symNegate:
						sValue.push( -dTop );
						break;

					case symNot:
						sValue.push( static_cast<prec>(
								~static_cast<bin>(dTop)
							) );
						break;
				}
			}

			if( bCloseParen == true )
			{
				throw FormulaException(
					"Close-paren found without matching open-paren."
					);
			}
		}
	};
}

#endif