/******************************************************************************
*		       							      *
* engine/engine.c (part of rcalc)				       	      *
* Copyright (C) 2001 Free Software Foundation, Inc.			      *
*								       	      *
* This program is free software; you can redistribute it and/or modify 	      *
* it under the terms of the GNU General Public License as published by 	      *
* the Free Software Foundation; either version 2 of the License, or    	      *
* (at your option) any later version.				       	      *
*								       	      *
* This program is distributed in the hope that it will be useful,      	      *
* but WITHOUT ANY WARRANTY; without even the implied warranty of       	      *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	       	      *
* GNU General Public License for more details.			       	      *
*								       	      *
* You should have received a copy of the GNU General Public License    	      *
* along with this program; if not, write to the Free Software	       	      *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.	       	      *
*								       	      *
******************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <engine.h>		/* This file's header	     		*/
#include <output.h>

#ifndef RCALC_TEXT_ONLY
 #include <gnome.h>
#else
 #include <glib.h>
 #include <libintl.h>
 #define _(String) gettext (String)
 #define RCALC_CONFIG_FILE "config_txt"
#endif

#include <stdarg.h>
#include <sys/stat.h>
#include <limits.h>

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <dirent.h>

#ifdef USE_COMPILED_FUNCTIONS
#include <gmodule.h>
#endif
#include <errno.h>

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

/* Class definition.
*/
#include "engine-private.h"	/* TODO: Move class def from ep.h to here. */

/* Names for the entries in RcAngleUnit.
*/
static gchar *angle_units[_RC_NUMBER_OF_ANGLE_UNITS]=
{
	"degrees", "radians"
};

#define MAX_PATH 2048

#ifdef RCALC_TEXT_ONLY
FILE * text_open_config_file(char *flags)
{
	FILE *the_file;
	
	G_CONST_RETURN gchar *home = g_get_home_dir();
	char buff[MAX_PATH+12];
	
	g_assert(home);
	
	sprintf(buff,"%s/%s",home,RCALC_DOTDIR);
   mkdir (buff, 0777);
	
	strcat (buff,"/");
   strcat (buff,RCALC_CONFIG_FILE);
	
	the_file = fopen(buff, flags);

  /* NULL check has to be done later */
  return the_file;
}

int text_config_get_int(FILE *cfg_file, char *cfg_string)
{
	char buff[512];
	int value = -1;
	
	do {
      fgets(buff, sizeof(buff), cfg_file);
      if (!feof(cfg_file))
	{
	  buff[strlen(buff)-1] = '\0';

	   if (strstr(buff, cfg_string) == buff)
	    {
	       value = atoi(buff + strlen(cfg_string));
			 /*printf("len of %s = %d\n",cfg_string,strlen(cfg_string));
			 printf("  value = %d\n",value);*/
	      
	    }
	}

    }
    while ( (!feof(cfg_file)) && (value < 0) );
	 
	 return value;
}

double text_config_get_float(FILE *cfg_file, char *cfg_string)
{
	char buff[512];
	double value = -1;
	
	do {
      fgets(buff, sizeof(buff), cfg_file);
      if (!feof(cfg_file))
	{
	  buff[strlen(buff)-1] = '\0';

	   if (strstr(buff, cfg_string) == buff)
	    {
	       value = atof(buff + strlen(cfg_string));
			/* printf("  value = %f\n",value);*/
	      
	    }
	}

    }
    while( (!feof(cfg_file)) && (value < 0));
	 
	 return value;
}


gchar * text_config_get_string(FILE *cfg_file, char *cfg_string)
{
	char buff[512];
	gchar *value = NULL;
	
	do {
      fgets(buff, sizeof(buff), cfg_file);
      if (!feof(cfg_file))
	{
	  buff[strlen(buff)-1] = '\0';

	   if (strstr(buff, cfg_string) == buff)
	    {
	       value = g_strdup_printf("%s",buff + strlen(cfg_string));
			 /*printf("value of %s = %s\n",cfg_string,value);*/
	      
	    }
	}

    }
    while( (!feof(cfg_file)) && (value == NULL));
	 
	 return value;
}

#endif /* RCALC_TEXT_ONLY */

/* returns function index that matches 'name', 
or -1 if it does not exist.
*/
int search_function_by_name(char *name,  RcEngine *engine)
{
	int i; int fct_index=-1;
	
	for( i=NUM_DEFAULT_FUNCTIONS; i<engine->nFunctions; i++ )
	{
		if (g_ascii_strcasecmp(name, engine->functions[i].name) == 0)
		{
			fct_index = i;
			break;
		}
	}
	
	return fct_index;
}

#ifdef USE_COMPILED_FUNCTIONS
int link_existing_func(char *fullname, char *basedir, RcEngine *engine )
{
  char fct_expr[MAX_FUNC_EXPR_LEN] = "UNKNOWN (loaded at startup)";
  char fullpath_so[2048];
  char *ptr;
  GModule *functHandle;
  char *error;
  FUNCTION *new_func=NULL;
  gboolean sym_value;
  gpointer symbol;

  new_func = &(engine->functions[engine->nFunctions]);
  sprintf(fullpath_so,"%s/%s",basedir,fullname);

  ptr=strstr(fullname,".so");
  *ptr='\0'; *(ptr+1)='\0'; *(ptr+2)='\0';

  /*functHandle = dlopen(fullpath_so, RTLD_NOW);*/
  functHandle = g_module_open(fullpath_so, 0);
  
  if (!functHandle)
      {
	error= (char *)g_module_error();
	fputs (error, stderr);
	return 0;
      }
		    
  /*loaded_func = (tFunction)dlsym (functHandle, fullname);*/
  sym_value = g_module_symbol (functHandle, fullname, &symbol);
  
  if (sym_value != TRUE) {
 	fprintf (stderr, "%s", g_module_error());
	return 0;
      }

  printf("Loaded function %s at start time\n",fullname);
  new_func->func = (tFunction)symbol;
  new_func->handle = functHandle;

  strncpy(new_func->name,fullname,MAX_VARIABLE_NAME_LEN);
  strncpy(new_func->expression,fct_expr,MAX_FUNC_EXPR_LEN);
  engine->nFunctions++;
  return 1;
}

void load_user_functions( RcEngine *engine )
{

  int j=0;
  char buff[MAX_PATH];
  char curdir[MAX_PATH];

  G_CONST_RETURN gchar *home = g_get_home_dir();
  DIR *dirp;
  struct dirent *dp;
  struct stat buf;
  
  if (g_module_supported() != TRUE) {
    g_print(_("Dynamically loaded functions are not supported on your system.\n"));
	 return;
  }

  g_assert(home);

  if ( getcwd( curdir, MAX_PATH - 1 ) == NULL ) {
	fprintf(stderr, "Error: getcwd failed");
	exit(1);
  }

  chdir(curdir);

  sprintf(buff,"%s/%s",home,RCALC_DOTDIR);
  mkdir (buff, 0777);

  chdir(buff);

  dirp = opendir(".");
  if (!dirp) {
    fprintf(stderr,"Error: couldn't open directory\n");
    exit(1);
  }
  while ((dp = readdir(dirp))!=0) {
    if (stat(dp->d_name,&buf) == 0)
      if (strstr(dp->d_name, ".so")) {
	if (strlen(dp->d_name) > MAX_VARIABLE_NAME_LEN) {
	  fprintf(stderr,"Error: name is too long\n");
	  exit(1);
	}
	link_existing_func((char *)dp->d_name, buff, engine);
	j++;
      }
  }
  closedir(dirp);

  chdir(curdir);
}
#endif

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

/* The constructor and destructor are very dirty since the various
** modules require global access to the configuration in a variable
** called cfgCalc. The loading and saving routines are fine and need
** no work for the object-orientation.
*/

RcEngine *cfgCalc = NULL;		/* TODO: Remove */

/* Contructor.
*/
RcEngine *rc_engine_new( void )
{
	RcEngine *engine;
	
	g_assert( cfgCalc==NULL );	/* TODO: remove */
	
	/* Allocate memory for the engine.
	*/
	engine = g_new0( RcEngine, 1 );


	rCalc_engine_Initialise();	/* TODO: remove */
	rCalc_engine_setDefaultFunctions(engine);
	cfgCalc = engine;		/* TODO: remove */

	return engine;
}

/* Destructor.
*/
void rc_engine_delete( RcEngine *engine )
{
	g_assert( engine );
	g_free( engine );
	cfgCalc = NULL;			/* TODO: remove */
}

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

void rc_engine_set_streams( RcEngine *engine, FILE *output, FILE *error )
{
	rc_output_set_streams( output, error );
}

void rc_engine_set_help_func( RcEngine *engine, RcDisplayHelpFunc func )
{
	rc_output_set_help_func( func );
}

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

#ifndef RCALC_TEXT_ONLY
/* Load the state using gnome.
*/
void rc_engine_load_state_gnome( RcEngine *engine )
{
	gint config_version;
	gint tmp_nfunctions;
	int i;
	gchar *angle_unit;
	gchar *func_var_name;

	g_assert( engine );
	gnome_config_push_prefix( "/" PACKAGE "/engine/" );	

	/* Configuration version control.
	*/
	config_version = gnome_config_get_int( "config_version=3" );
	g_assert( config_version==3 );

	/* Read the angle unit.
	*/
	angle_unit = gnome_config_get_string( "angle_unit=degrees" );
	for( i=0; ; i++ )
	{
		g_assert( i<_RC_NUMBER_OF_ANGLE_UNITS );

		if( strcmp( angle_unit, angle_units[i] ) ) continue;

		engine->angleMode = i;
		break;
	}
	g_free( angle_unit );

        engine->func_var_name = gnome_config_get_string( "func_var_name=x" );

	/* Read the variables.
	*/
	engine->nVariables = gnome_config_get_int( "num_variables=0" );
	for( i=0; i<engine->nVariables; i++ )
	{
		gchar *name;
		gchar key[32];
			
		sprintf( key, "variable[%d].name=%s", i, "<error>" );
		name = gnome_config_get_string( key );
		strcpy( engine->variables[i].name, name );
		g_free( name );

		sprintf( key, "variable[%d].real=%s", i, "0" );
		engine->variables[i].value =
			gnome_config_get_float( key );
			
		/*fparser_add_user_variable(engine->variables[i].name, 
		  engine->variables[i].value);	*/
	}

#ifdef USE_COMPILED_FUNCTIONS	
	/* Load functions */
	load_user_functions( engine );


	/* Read function names.
	*/
	
	if  ( (engine->nFunctions-NUM_DEFAULT_FUNCTIONS) > 0 ) {

	
	tmp_nfunctions = gnome_config_get_int( "num_functions=0" );
	
	for( i=0; i< tmp_nfunctions; i++ )
	{
		gchar *name;
		gchar *expr;
		gchar key[32];
		gint result;
			
		sprintf( key, "function[%d].name=%s", i, "<error>" );
		name = gnome_config_get_string( key );
	
		result = search_function_by_name(name, engine);
		
		if ( result >= 0 ) {
	
		 	sprintf( key, "function[%d].expr=%s", i, "0" );
			expr = gnome_config_get_string( key );
			g_strlcpy( engine->functions[result].expression, expr, MAX_FUNC_EXPR_LEN);
			g_print("recognized %s as %s(x)=%s\n", name, name, expr);
			g_free( expr );			
		}		

		g_free( name );
	}
	}

#else
	
	tmp_nfunctions = gnome_config_get_int( "num_functions=0" );
	
	for( i=0; i< tmp_nfunctions; i++ )
	{
		gchar *name;
		gchar *expr;
		gchar key[32];
		gint result;
			
		sprintf( key, "function[%d].name=%s", i, "<error>" );
		name = gnome_config_get_string( key );
	
		sprintf( key, "function[%d].expr=%s", i, "0" );
		expr = gnome_config_get_string( key );
		g_strlcpy( engine->functions[engine->nFunctions].expression, expr, MAX_FUNC_EXPR_LEN);
		g_strlcpy( engine->functions[engine->nFunctions].name, name, MAX_VARIABLE_NAME_LEN);
		engine->nFunctions++;
		/*g_print("recognized %s as %s(x)=%s\n", name, name, expr);*/
		/*fparser_add_user_defined_function(name, expr);*/
		
		g_free( expr );
		g_free( name );
	}

#endif /* USE_COMPILED_FUNCTIONS */


	gnome_config_pop_prefix();
}


/* Save the state using gnome
*/
void rc_engine_save_state_gnome ( const RcEngine *engine )
{
	int i;

	g_assert( engine );

	gnome_config_clean_section( "/" PACKAGE "/engine/" );
	gnome_config_push_prefix(   "/" PACKAGE "/engine/" );
	
	/* Configuration version control.
	*/
	gnome_config_set_int( "config_version", 3 );

	/* Write the angle unit.
	*/
	gnome_config_set_string( "angle_unit", angle_units[engine->angleMode] );

	gnome_config_set_string( "func_var_name", engine->func_var_name);

	/* Write the variables.
	*/
	gnome_config_set_int( "num_variables", engine->nVariables );
	for( i=0; i<engine->nVariables; i++ )
	{
		gchar key[32];
			
		sprintf( key, "variable[%d].name", i  );
		gnome_config_set_string( key, engine->variables[i].name );

		sprintf( key, "variable[%d].real", i );
		gnome_config_set_float( key, engine->variables[i].value );
	}
	
	gnome_config_set_int( "num_functions", engine->nFunctions-NUM_DEFAULT_FUNCTIONS );
	for( i=NUM_DEFAULT_FUNCTIONS; i<engine->nFunctions; i++ )
	{
		gchar key[32];
			
		sprintf( key, "function[%d].name", i-NUM_DEFAULT_FUNCTIONS );
		gnome_config_set_string( key, engine->functions[i].name );

		sprintf( key, "function[%d].expr", i-NUM_DEFAULT_FUNCTIONS );
		gnome_config_set_string( key, engine->functions[i].expression );
	}
	
	gnome_config_pop_prefix();
	gnome_config_sync();
}

/*****************************************************************************/
#else /*  RCALC_TEXT_ONLY is _defined_ */

/* Load the state without using gnome.
*/
void rc_engine_load_state_no_gnome ( RcEngine *engine )
{
	gint config_version;
	gint tmp_nfunctions;
	int i;
	int angle_unit;
	FILE *config_file;
	gchar func_var_name[32];
	
	g_assert( engine );
	
	config_file = text_open_config_file("r");
	if (config_file == NULL) {
    return;
   }
	
	else {
	  config_version = text_config_get_int(config_file, "config_version=");
	  g_assert( config_version==3 );
	  
	  angle_unit = text_config_get_int(config_file, "angle_unit=");
	  g_assert( angle_unit<_RC_NUMBER_OF_ANGLE_UNITS );
	  engine->angleMode = angle_unit;
	  
	  engine->func_var_name = text_config_get_string( "func_var_name=" );	  
	  engine->nVariables = text_config_get_int(config_file, "num_variables=" );
	  
	  for( i=0; i<engine->nVariables; i++ )
	{
		gchar *name;
		gchar key[32];
			
		sprintf( key, "variable[%d].name=", i );
		name = text_config_get_string(config_file, key );
		strcpy( engine->variables[i].name, name );
		g_free( name );

		sprintf( key, "variable[%d].real=", i);
		engine->variables[i].value =
			text_config_get_float(config_file, key );
			
		/*fparser_add_user_variable(engine->variables[i].name, 
		  engine->variables[i].value);	*/
	}

#ifdef USE_COMPILED_FUNCTIONS	
	/* Load functions */
	load_user_functions( engine );


	/* Read function names.
	*/
	
	if  ( (engine->nFunctions-NUM_DEFAULT_FUNCTIONS) > 0 ) {

	
	tmp_nfunctions = text_config_get_int( config_file, "num_functions=" );
	
	for( i=0; i< tmp_nfunctions; i++ )
	{
		gchar *name;
		gchar *expr;
		gchar key[32];
		gint result;
			
		sprintf( key, "function[%d].name=", i );
		name = text_config_get_string( config_file, key );
	
		result = search_function_by_name(name, engine);
		
		if ( result >= 0 ) {
	
		 	sprintf( key, "function[%d].expr=", i );
			expr = text_config_get_string( config_file, key );
			g_strlcpy( engine->functions[result].expression, expr, MAX_FUNC_EXPR_LEN);
			/*g_print("recognized %s as %s(x)=%s\n", name, name, expr);*/
			g_free( expr );			
		}		

		g_free( name );
	}
	}

#else
	
	tmp_nfunctions = text_config_get_int( config_file, "num_functions=" );
	
	for( i=0; i< tmp_nfunctions; i++ )
	{
		gchar *name;
		gchar *expr;
		gchar key[32];
		gint result;
			
		sprintf( key, "function[%d].name=", i );
		name = text_config_get_string( config_file, key );

		sprintf( key, "function[%d].expr=", i );
		expr = text_config_get_string( config_file, key );
		g_strlcpy( engine->functions[engine->nFunctions].expression, expr, MAX_FUNC_EXPR_LEN);
		g_strlcpy( engine->functions[engine->nFunctions].name, name, MAX_VARIABLE_NAME_LEN);
		engine->nFunctions++;
		/*fparser_add_user_defined_function(name, expr);*/
		/*g_print("recognized %s as %s(x)=%s\n", name, name, expr);*/
		
		g_free( expr );
		g_free( name );
	}

#endif /* USE_COMPILED_FUNCTIONS */

	
	fclose (config_file);
	}
}


/* Save the state without using gnome
*/
void rc_engine_save_state_no_gnome ( const RcEngine *engine )
{
	int i;
	FILE *config_file;

	g_assert( engine );
	
	config_file = text_open_config_file("w");
	if (config_file == NULL) {
    return;
   }
	
	else {
		fprintf(config_file, "config_version=%d\n",3);
		fprintf(config_file, "angle_unit=%d\n",engine->angleMode);
		fprintf(config_file, "func_var_name=%s\n", engine->func_var_name );
		
		fprintf(config_file, "num_variables=%d\n",engine->nVariables);
		for( i=0; i<engine->nVariables; i++ )
	{			
		fprintf(config_file, "variable[%d].name=%s\n", i, engine->variables[i].name );
		fprintf(config_file, "variable[%d].real=%.13f\n", i, engine->variables[i].value );
	}
	
	fprintf(config_file, "num_functions=%d\n",engine->nFunctions-NUM_DEFAULT_FUNCTIONS);
	for( i=NUM_DEFAULT_FUNCTIONS; i<engine->nFunctions; i++ )
	{
		fprintf(config_file, "function[%d].name=%s\n", i-NUM_DEFAULT_FUNCTIONS, engine->functions[i].name );
		fprintf(config_file, "function[%d].expr=%s\n", i-NUM_DEFAULT_FUNCTIONS, engine->functions[i].expression );
	}
		
		fclose (config_file);
	}
	
}
#endif /* #else of #ifndef RCALC_TEXT_ONLY */
/*****************************************************************************/

/* Load the state.
*/
void rc_engine_load_state( RcEngine *engine )
{
	/* this is a bit messy... */
	
#ifndef RCALC_TEXT_ONLY
	rc_engine_load_state_gnome( engine );
#else
	rc_engine_load_state_no_gnome( engine );	
#endif
}


/* Save the state.
*/
void rc_engine_save_state( const RcEngine *engine )
{

#ifndef RCALC_TEXT_ONLY	
	rc_engine_save_state_gnome ( engine );
#else	
	rc_engine_save_state_no_gnome( engine );
#endif /* RCALC_TEXT_ONLY */
}

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

/* Get and set the angle unit.
*/
RcAngleUnit rc_engine_get_angleunit( const RcEngine *engine )
{
	g_assert( engine );
	return engine->angleMode;
}
void rc_engine_set_angleunit( RcEngine *engine, RcAngleUnit unit )
{
	g_assert( engine );
	g_assert( unit>=0 && unit<_RC_NUMBER_OF_ANGLE_UNITS );

	engine->angleMode = unit;
}

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

/* Execute a line of user input
*/
/* TODO: this is currently in command.c */
/* gboolean rc_engine_execute( RcEngine *engine, const gchar *command ) */

/*** end of engine/engine.c ***************************************************/
