/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<math.h>

#include	"base.h"
#include	"base_ct.h"
#include	"config.h"
#include	"ConfigKey.h"
#include	"ConfigKeyValue.h"
#include	"ConfigKeyValueList.h"
#include	"List.h"
#include	"log.h"
#include	"SimpleCalculator.h"
#include	"String.h"



/*\\\ CONSTANTES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ CONSTANTES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

const int	SimpleCalculator::NBR_OPERANDS [NBR_OPERATIONS] =
{
	2, 2, 2, 2, 1, 1
};



/*\\\ TYPES & STRUCTURES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\*/

double	SimpleCalculator::spec_var [NBR_SPEC_VAR] =
{
	44100.0,		// Frequence d'echantillonnage
	120.0			// Tempo
};



/*\\\ METHODES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise                                             */
/*==========================================================================*/

SimpleCalculator::SimpleCalculator (void)
:	_nbr_arg (0)
,	_final_stack_size (0)
{
}



/*==========================================================================*/
/*      Nom: (destructeur)                                                  */
/*      Description: Detruit                                                */
/*==========================================================================*/

SimpleCalculator::~SimpleCalculator (void)
{
}



/*==========================================================================*/
/*      Nom: check_ok                                                       */
/*      Description: Verifie l'intergrite de l'objet.                       */
/*      Retour: 0 si l'objet parait correct.                                */
/*==========================================================================*/

signed int	SimpleCalculator::check_ok (void) const
{
	if (this == NULL)
	{
		LOG_printf ("SimpleCalculator::check_ok: Error: \"this\" pointer is NULL.\n");
		return (-1);
	}

	/*** A faire ***/

	return (0);
}



/*==========================================================================*/
/*      Nom: self_display                                                   */
/*      Description: Affiche "textuellement" le contenu courant de l'objet. */
/*==========================================================================*/

void	SimpleCalculator::self_display (void) const
{

	/*** A faire ***/

}



void	SimpleCalculator::reset (void)
{
	_nbr_arg = 0;
}



signed int	SimpleCalculator::add_argument (double arg)
{
	if (_nbr_arg >= MAX_NBR_ARG)
	{
		return (-1);
	}

	_type [_nbr_arg] = VAL;
	_val [_nbr_arg] = arg;
	++ _nbr_arg;
	++ _final_stack_size;

	return (0);
}



signed int	SimpleCalculator::add_spec_var (SpecVar type)
{
	if (_nbr_arg >= MAX_NBR_ARG)
	{
		return (-1);
	}
	if (type >= NBR_SPEC_VAR || type < 0)
	{
		return (-1);
	}

	_type [_nbr_arg] = SPEC_VAR | type;
	++ _nbr_arg;
	++ _final_stack_size;

	return (0);
}



signed int	SimpleCalculator::add_operation (Operation op)
{
	if (_nbr_arg >= MAX_NBR_ARG)
	{
		return (-1);
	}
	if (op >= NBR_OPERATIONS || op < 0)
	{
		return (-1);
	}
	if (_final_stack_size < NBR_OPERANDS [op])
	{
		return (-1);
	}

	_type [_nbr_arg] = OPERATION | op;
	++ _nbr_arg;
	_final_stack_size += NBR_OPERANDS [op] - 1;

	return (0);
}



signed int	SimpleCalculator::add_parameter (void)
{
	if (_nbr_arg >= MAX_NBR_ARG)
	{
		return (-1);
	}

	_type [_nbr_arg] = PARAMETER;
	++ _nbr_arg;
	++ _final_stack_size;

	return (0);
}



signed int	SimpleCalculator::parse_op_list (const ConfigKeyValueList &op_list, const char *path_0)
{
	const std::list <ConfigKey> &	key_list = op_list.get_list ();

	reset ();

	for (std::list <ConfigKey>::const_iterator it = key_list.begin ()
	;	it != key_list.end ()
	;	++ it)
	{
		/* Recupere la cle */
		const ConfigKey &	key = *it;

		/* Recupere son nom */
		const String	key_name = key.get_name ();
		const ConfigKeyValue &	key_val = key.get_value ();
		String		val_str;
		if (CFG_get_string (key_val, path_0, val_str))
		{
			if (BASE_compare_string (val_str.c_str (), "val") == 0)
			{
				add_parameter ();
			}

			else if (BASE_compare_string (val_str.c_str (), "fs") == 0)
			{
				add_spec_var (SAMPLE_FREQ);
			}

			else if (BASE_compare_string (val_str.c_str (), "tempo") == 0)
			{
				add_spec_var (TEMPO);
			}

			else if (BASE_compare_string (val_str.c_str (), "exp") == 0)
			{
				add_operation (EXP);
			}

			else if (BASE_compare_string (val_str.c_str (), "log") == 0)
			{
				add_operation (LOG);
			}

			else if (BASE_compare_string (val_str.c_str (), "+") == 0)
			{
				add_operation (ADD);
			}

			else if (BASE_compare_string (val_str.c_str (), "-") == 0)
			{
				add_operation (SUBSTRACT);
			}

			else if (BASE_compare_string (val_str.c_str (), "*") == 0)
			{
				add_operation (MULTIPLY);
			}

			else if (BASE_compare_string (val_str.c_str (), "/") == 0)
			{
				add_operation (DIVIDE);
			}

			else
			{
				add_argument (atof (val_str.c_str ()));
			}
		}
	}

	if (_final_stack_size < 1)
	{
		return (-1);
	}

	return (0);
}



double	SimpleCalculator::calculate (double param) const
{
	double	stack [MAX_NBR_ARG];
	long		stack_size = 0;

	for (int arg_cnt = 0; arg_cnt < _nbr_arg; ++ arg_cnt)
	{
		switch (_type [arg_cnt] & MASK)
		{
		/* Valeur constante */
		case	VAL:
			stack [stack_size] = _val [arg_cnt];
			++ stack_size;
			break;

		/* Variable globale speciale */
		case	SPEC_VAR:
			stack [stack_size] = spec_var [_type [arg_cnt] & ~MASK];
			++ stack_size;
			break;

		/* Operation */
		case	OPERATION:
			{
				const int	op = _type [arg_cnt] & ~MASK;
				stack_size -= NBR_OPERANDS [op] - 1;

				switch (op)
				{
				case	ADD:
					stack [stack_size - 1] += stack [stack_size];
					break;

				case	SUBSTRACT:
					stack [stack_size - 1] -= stack [stack_size];
					break;

				case	MULTIPLY:
					stack [stack_size - 1] *= stack [stack_size];
					break;

				case	DIVIDE:
					stack [stack_size - 1] /= stack [stack_size];
					break;

				case	EXP:
					stack [stack_size - 1] = exp (stack [stack_size - 1]);
					break;

				case	LOG:
					stack [stack_size - 1] = log (stack [stack_size - 1]);
					break;
				}
			}

			break;

		/* Valeur parametre */
		case	PARAMETER:
			stack [stack_size] = param;
			++ stack_size;
			break;
		}
	}

	return (stack [0]);
}



signed int	SimpleCalculator::set_spec_var (SpecVar type, double val)
{
	if (type >= NBR_SPEC_VAR || type < 0)
	{
		return (-1);
	}

	spec_var [type] = val;

	return (0);
}



/*\\\ METHODES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/****************************************************************************/
/*                                                                          */
/*                                                                          */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
