#ifndef __GDAM_MATH_FUNCTION_H_
#define __GDAM_MATH_FUNCTION_H_

typedef struct _GdamMathFunctionClass GdamMathFunctionClass;
typedef union _GdamMathFunction GdamMathFunction;
typedef struct _GdamMathFunctionAny GdamMathFunctionAny;
typedef struct _GdamMathFunctionNamed GdamMathFunctionNamed;
typedef struct _GdamMathFunctionOperator GdamMathFunctionOperator;
typedef struct _GdamMathFunctionConstant GdamMathFunctionConstant;
typedef struct _GdamMathFunctionVariable GdamMathFunctionVariable;


#include <glib.h>

typedef enum
{
  GDAM_MATH_FUNCTION_NAMED,
  GDAM_MATH_FUNCTION_OPERATOR,
  GDAM_MATH_FUNCTION_VARIABLE,
  GDAM_MATH_FUNCTION_CONSTANT
} GdamMathFunctionCategory;

typedef gdouble (*GdamEvalFunc)(GdamMathFunction*     function,
			        gdouble*              input_vector);

struct _GdamMathFunctionClass
{
  const char              *class_name;
  GdamMathFunctionCategory category;
  void                   (*append_to_string)(GdamMathFunction *function,
					     GString          *output);
  GdamEvalFunc             evaluator;
  void                   (*destroy)         (GdamMathFunction*);
  int			   precedence;
};


/* Ways to define classes of functions. */
void                   gdam_math_function_class_new_binary
				(const char*	major_op,
				 const char*    minor_op,
				 int            precedence,
				 GdamEvalFunc   evaluator);
void                   gdam_math_function_class_new_named_function
				(const char*	named_function,
				 GdamEvalFunc   evaluator);

GdamMathFunctionClass* gdam_math_function_class_get_by_name
			        (const char*    name);


struct _GdamMathFunctionAny {
	/* All GdamMathFunctions must have these members. */
	GdamMathFunctionCategory category;
	GdamMathFunctionClass*	klass;
	GdamEvalFunc            evaluator;
};

struct _GdamMathFunctionNamed {
	/* All GdamMathFunctions must have these members. */
	GdamMathFunctionCategory category;
	GdamMathFunctionClass*	klass;
	GdamEvalFunc            evaluator;

	/* Named-specific members. */
	GdamMathFunction*	argument;
};
GdamMathFunction*  gdam_math_function_named_new
				(GdamMathFunctionClass* klass,
				 GdamMathFunction*      argument);
struct _GdamMathFunctionOperator {
	/* All GdamMathFunctions must have these members. */
	GdamMathFunctionCategory category;
	GdamMathFunctionClass*	klass;
	GdamEvalFunc            evaluator;

	gpointer		operator;

	/* Operator-specific members. */
	int			num_args;
	GdamMathFunction**	args;
	/* For multiply, which doubles as divide,
	 * or add which doubles as subtracts. */
	gboolean*		whether_inverted;
};

GdamMathFunction*  gdam_math_function_operator_new
				(GdamMathFunctionClass* klass,
				 int                 num_args,
				 GdamMathFunction**  args,
				 gboolean*           whether_inverted);

struct _GdamMathFunctionConstant {
	/* All GdamMathFunctions must have these members. */
	GdamMathFunctionCategory category;
	GdamMathFunctionClass*	klass;
	GdamEvalFunc            evaluator;

	/* Constant-specific members. */
	gdouble			return_value;
};

GdamMathFunction* gdam_math_function_constant_new(gdouble constant);

struct _GdamMathFunctionVariable {
	/* All GdamMathFunctions must have these members. */
	GdamMathFunctionCategory category;
	GdamMathFunctionClass*	klass;
	GdamEvalFunc            evaluator;

	/* Variable-specific members. */
	int			index;
};
GdamMathFunction* gdam_math_function_variable_new(int index);

union _GdamMathFunction {
	GdamMathFunctionAny      any;
	GdamMathFunctionCategory category;
	GdamMathFunctionVariable variable;
	GdamMathFunctionConstant constant;
	GdamMathFunctionNamed    named;
};

/* in gdammathfunction-default.c */
void gdam_math_function_init();

typedef struct _GdamMathParseContext GdamMathParseContext;
struct _GdamMathParseContext
{
  gboolean has_var_names;
  guint num_var_names;
  char **var_names;
};

GdamMathFunction* gdam_math_function_parse(const char* string,
				           GdamMathParseContext *context);
gdouble gdam_math_function_eval   (GdamMathFunction*        function,
                                   gdouble*                 inputs);
void    gdam_math_function_append (GdamMathFunction*        function,
                                   GString*                 string,
				   int                      precedence);
char*   gdam_math_function_to_string(GdamMathFunction*);
void    gdam_math_function_destroy(GdamMathFunction*);

#endif
