/******************************************************************************
*		       							      *
* engine/command.c (part of rcalc)				       	      *
* Copyright (C) 1999, 2000, 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>		/* The calculation engine		*/
#include <output.h>		/* The output routines			*/
#include <debug.h>		/* Debugging functions			*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

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

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

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

#include "engine-private.h"	/* TODO: Remove */

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

/* Table of commands.
*/
static int cmdExit	( int argc, char **argv );
static int cmdHelp	( int argc, char **argv );
static int cmdList	( int argc, char **argv );
/*  static int cmdWorkspace	( int argc, char **argv ); */
static int cmdRemove	( int argc, char **argv );
static int cmdMode	( int argc, char **argv );
static int cmdDegRad	( int argc, char **argv );
static int cmdAddFunc ( int argc, char **argv );

extern FUNCTION Function_0;

typedef struct
{
	char		*name;
	int		(*func)(int argc, char **argv);
}
COMMAND;

#define NUM_COMMANDS	14

static COMMAND Command[NUM_COMMANDS]=
{
	{ "exit",	cmdExit,	},
	{ "quit",	cmdExit,	},	
	{ "help",	cmdHelp,	},
	{ "helpwin",	cmdHelp,	},
	{ "?",		cmdHelp,	},
	{ "man",	cmdHelp,	},
	{ "ls",		cmdList,	},
	{ "who",	cmdList,	},
	{ "rm",		cmdRemove,	},
	{ "clear",	cmdRemove,	},
	{ "mode",	cmdMode,	},
	{ "deg",	cmdDegRad,	},
	{ "rad",	cmdDegRad,	},
	{ "func",       cmdAddFunc      },
};

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

int file_exists(char *filename)
{
  FILE *testFile;

  if (!(testFile = fopen(filename, "rb")))
  {
    return 0;
  }
  else
  {
    if (fclose(testFile) != 0)
      {
	fprintf(stderr,"Error: can't close file %s", filename);
	exit(1);
      }
    return 1;
  }
}

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

/* Observer functions for the table of commands
*/
int rCalc_engine_GetNumCommands( void )
{
	return NUM_COMMANDS;
}
char *rCalc_engine_GetCommandName( int i )
{
	return _(Command[i].name);
}

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

/* The commands themselves
*/
static int cmdExit( int argc, char **argv )
{
	if( argc != 1 )
	{
		/* Engine | Commands | Prefix for format error messages */
		rc_error( "%s %s", _("format:"), _(argv[0]) );
		return 0;
	}
	return 1;
}

static int cmdHelp( int argc, char **argv )
{
	if( argc == 1 )
	{
		if( !strcasecmp( argv[0], "man" ) )
		{
			rc_error( "%s %s <%s>", _("format:"), argv[0], "topic" );
			return 0;
		}
		rc_display_help( argv[1] );
	}
	else
	{
		if(!( rc_display_help( argv[1] )))
		{
			/* Help | Error message for man and help */
			rc_error(_("no help page available for `%s'."), argv[1]);
		}
	}
	return 0;
}

static int cmdList( int argc, char **argv )
{
	int num,a,i;
	
	/* Handle the default 'ls'='ls variables'
	** and the alias who='ls variables'
	*/
	if( argc==1 )
	{
		char *argv[2];
		argv[0] = "ls";
		argv[1] = "userdef";
		return cmdList( 2, argv );
	}

	/* 'who' takes no arguments.
	*/
	if( !strcasecmp( argv[0], "who" ) )
	{
		rc_error( "%s %s", _("format:"), argv[0] );
		return 0;
	}

	/* List each item in turn.
	*/
	for( a=1; a<argc; a++ )
	{
		if( strstr( argv[a],"userdef" ))
		{
			num = rCalc_engine_GetNumVariables();
			for( i=0; i<num; i++ )
			{
				rc_printf( "\t%-8s = %.16g\n",
				       rCalc_engine_GetVariableName( i ),
				       rCalc_engine_GetVariableValue( i ) );
			}
			
			num = rCalc_engine_GetNumFunctions()+1;
			if (num > NUM_DEFAULT_FUNCTIONS) {
			  /*rc_printf( "\n\t");
			 	 rc_printf(_("* User-defined functions:\n"));*/
			  for( i=NUM_DEFAULT_FUNCTIONS; i<num; i++ )
			    {
			      rc_printf( "\t%s\t   %s\n",
					 rCalc_engine_GetFunctionName( i-1 ),
					 rCalc_engine_GetFunctionExpr( i-1 ));
			    }
			}
		}
			
	        else if( strstr( argv[a],"var" ))
		{
			if( argc>2 && a!=1 ) rc_printf("\n");
			if( argc>2 ) rc_printf("%s:\n", argv[a] );
			
			num = rCalc_engine_GetNumVariables();
			for( i=0; i<num; i++ )
			{
				rc_printf( "\t%-8s = %.16g\n",
				       rCalc_engine_GetVariableName( i ),
				       rCalc_engine_GetVariableValue( i ) );
			}
		}
		else if(strstr( argv[a], "fun" )) 
		{
			if( argc>2 && a!=1 ) rc_printf("\n");
			if( argc>2 ) rc_printf("%s:\n", argv[a] );
			
			num = rCalc_engine_GetNumFunctions()+1;
			rc_printf(_("* Built-in functions:\n"));
			for( i=1; i<NUM_DEFAULT_FUNCTIONS; i++ )
			{
				rc_printf( "\t%s\t%s\n",
				       rCalc_engine_GetFunctionName( i-1 ),
					   rCalc_engine_GetFunctionExpr( i-1 ));
			}

			if (num > NUM_DEFAULT_FUNCTIONS) {
			  rc_printf(_("* User-defined functions:\n"));
			  for( i=NUM_DEFAULT_FUNCTIONS; i<num; i++ )
			    {
			      rc_printf( "\t%s\t%s\n",
					 rCalc_engine_GetFunctionName( i-1 ),
					 rCalc_engine_GetFunctionExpr( i-1 ));
			    }
			}

		}
		else if( strstr( argv[a], "con" ))
		{
			if( argc>2 && a!=1 ) rc_printf("\n");
			if( argc>2 ) rc_printf("%s:\n", argv[a] );
			
			num = rCalc_engine_GetNumConstants();
			for( i=0; i<num; i++ )
			{
				rc_printf( "\t%-8s = %.16g\n",
				       rCalc_engine_GetConstantName( i ),
				       rCalc_engine_GetConstantValue( i ) );
			}
		}
		else if( strstr( argv[a], "com" ) )
		{
			if( argc>2 && a!=1 ) rc_printf("\n");
			if( argc>2 ) rc_printf("%s:\n", argv[a] );
			
			num = rCalc_engine_GetNumCommands();
			for( i=0; i<num; i++ )
			{
				rc_printf( "\t%s\n",
				       rCalc_engine_GetCommandName( i ) );
			}
		}
		/* Engine | Commands | Error message for ls */
		else rc_error( _("%s: unknown item."), argv[a] );
	}
	return 0;
}

static int cmdRemove( int argc, char **argv )
{
	VARIABLE *Variable = cfgCalc->variables;
	FUNCTION *Function  = cfgCalc->functions;
	int a,i,j;
	char buff[2048];
	G_CONST_RETURN gchar *home = g_get_home_dir();

	g_assert(home);

	/* Handle `clear' on its own
	*/
	if( argc == 1 )
	{
		if( strcasecmp( argv[0], "clear" ) )
		{
			rc_error( "%s %s %s1 [%s2 ...]",
			       _("format:"), argv[0],
			       "variable", "variable" );
			return 0;
		}
		cfgCalc->nVariables = 0;
		/*cfgCalc->variables_mod = TRUE;*/

		return 0;
	}
	
	for( a=1; a<argc; a++ )
	{
		gboolean success=FALSE;
		
		for( i=0; i<cfgCalc->nVariables; i++ )
		{
			if( strcasecmp( Variable[i].name, argv[a] ) )
				continue;

			cfgCalc->nVariables--;
			/*fparser_remove_user_variable(Variable[i].name, 
			  Variable[i].value);*/
			
			for( j=i; j<cfgCalc->nVariables; j++)
				Variable[j]=Variable[j+1];

			/*cfgCalc->variables_mod = TRUE; */
			
			success = TRUE;
			break;
		}

		for( i=NUM_DEFAULT_FUNCTIONS; i<cfgCalc->nFunctions; i++ )
		{
			if( strcasecmp( Function[i].name, argv[a] ) )
				continue;
#ifdef USE_COMPILED_FUNCTIONS
			g_module_close(Function[i].handle);

			/* remove the file */
			sprintf(buff,"%s/%s/%s.so",home,RCALC_DOTDIR,Function[i].name);
			if (remove(buff) == -1) {
			  if (errno != ENOENT) {
			    perror("ERROR: ");
			    return 0;
			  }

			}
#else
                        /*fparser_remove_user_defined_function(Function[i].name,
			 Function[i].expression);	*/		
#endif	/* USE_COMPILED_FUNCTIONS */	
			
			Function[i]=Function_0;

			cfgCalc->nFunctions--;
			for( j=i; j<cfgCalc->nFunctions; j++)
				Function[j]=Function[j+1];

			
			success = TRUE;
			break;
		}

		if( !success ) rc_error( _("%s: undefined variable."), argv[a] );
	}

	return 0;
}

static int cmdMode( int argc, char **argv )
{
	if( argc == 1 )
	{
		rc_printf( "\t%s\n",
		       cfgCalc->angleMode == RC_ANGLE_DEGREES ?
		       "degrees" : "radians" );

		return 0;
	}
	else if( argc == 2 )
	{
		if( !strcasecmp( argv[1], "deg" ) ||
		    !strcasecmp( argv[1], "rad" ) )
		{
			cmdDegRad( 1, argv+1 );
			return 0;
		}
	}

	rc_error( "%s %s [%s|%s]",
	       _("format:"), argv[0], "deg", "rad" );
	return 0;
}

static int cmdDegRad( int argc, char **argv )
{
	RcAngleUnit newstate = cfgCalc->angleMode;
	
	if( argc != 1 )
	{
		rc_error( "%s %s", _("format:"), argv[0] );
		return 0;
	}

	newstate = strcasecmp( argv[0], "deg" ) ? RC_ANGLE_RADIANS : RC_ANGLE_DEGREES;
	if( cfgCalc->angleMode != newstate )
	{
		cfgCalc->angleMode = newstate;
		/*cfgCalc->angleMode_mod = TRUE;*/
	}
	return 0;
}

#ifdef USE_COMPILED_FUNCTIONS
int compile_function(char *name, char *expression, FUNCTION *function)
{
  char *error,*suberror=NULL;
  FILE *fctfile=NULL;
  GModule *functHandle = NULL;
  G_CONST_RETURN gchar *home = g_get_home_dir();
  char *command1 = calloc(2048+32,sizeof(char));
  char *command2 = calloc(2048+64,sizeof(char));
  char *fullpath_so = calloc(1024,sizeof(char));
  char *fullpath_o = calloc(1024,sizeof(char));
  char *fullpath_c = calloc(1024,sizeof(char));
  gboolean sym_value;
  gpointer symbol;

  tFunction funct;

  g_assert(home);

  sprintf(fullpath_so,"%s/%s/%s",home,RCALC_DOTDIR,name);

  strncpy(fullpath_o, fullpath_so, 1000);
  strncpy(fullpath_c, fullpath_so, 1000);
  strcat(fullpath_so,".so");
  strcat(fullpath_o,".o");
  strcat(fullpath_c,".c");

  sprintf(command1,"gcc %s -fPIC -c -o %s",fullpath_c,fullpath_o);
  sprintf(command2,"gcc -shared %s -o %s",fullpath_o,fullpath_so);

  fctfile = fopen(fullpath_c,"w");
  fprintf(fctfile,"#include <math.h>\n");
  fprintf(fctfile,"double %s(double x) {return %s;}\n",name,expression);
  fclose(fctfile);

  /* remove the old .so */
  if (remove(fullpath_so) == -1) {
    if (errno != ENOENT) {
      perror("ERROR: ");
      free(fullpath_o); free(fullpath_so); free(fullpath_c); 
      free(command1); free(command2); 
      return 0;
    }

  }

  /* remove the old .o */
  if (remove(fullpath_o) == -1) {
    if (errno != ENOENT) {
      perror("ERROR: ");
      free(fullpath_o); free(fullpath_so); free(fullpath_c); 
      free(command1); free(command2);
      return 0;
    }
  }

  /* compile .c -> .o */
  rc_printf( _("compiling function: %s\n"), name );
  system (command1);

  /* remove the new .c */
  if (remove(fullpath_c) == -1) {
    if (errno != ENOENT) {
      perror("ERROR: ");
      free(fullpath_o); free(fullpath_so); free(fullpath_c); 
      free(command1); free(command2);
      return 0;
    }
  }

  if (file_exists(fullpath_o)) {
    /* transform the new .o -> .so */
    system (command2);

    /* remove the new .o */
    if (remove(fullpath_o) == -1) {
      perror("ERROR: ");
      free(fullpath_o); free(fullpath_so); free(fullpath_c); 
      free(command1); free(command2);
      return 0;
    }
  }

  else {
    rc_error( _("Could not compile function.\nCheck the syntax."));
    free(fullpath_o); free(fullpath_so); free(fullpath_c);
    free(command1); free(command2);
    return 0;
  }

  if (file_exists(fullpath_so)) {

    /* dynamically link our new .so */
    printf("Linking...\n");
    /*functHandle = dlopen(fullpath_so, RTLD_NOW);*/
    functHandle = g_module_open(fullpath_so, 0);
    
    if (!functHandle)
      {
	error=(char *)g_module_error();
	fputs (error, stderr);
	if ( (suberror=strstr(error,"undefined")) != NULL)
	  rc_error( _("%s\n"),suberror);
	else
	  rc_error( _("%s\n"),error);
	
	free(fullpath_o); free(fullpath_so); free(fullpath_c);
	free(command1); free(command2);
	return 0;
      }
    
    /*funct = (tFunction)dlsym (functHandle, name);*/
    sym_value = g_module_symbol (functHandle, name, &symbol);
    
    if (sym_value != TRUE)
      {
    error =  (char *)g_module_error();
	fprintf (stderr, "%s", error);
	rc_error( _("error: %s\n"),error);
	free(fullpath_o); free(fullpath_so); free(fullpath_c);
	free(command1); free(command2);
	return 0;
      }
		
    funct = (tFunction)symbol;
    
    printf("OK.\n");
    rc_printf( _("done.\n"));
  }

  else {
    rc_error( _("Could not compile function.\nCheck the syntax."));
    free(fullpath_o); free(fullpath_so); free(fullpath_c);
    free(command1); free(command2);
    return 0;
  }

  function->func = (tFunction) funct;
  function->handle = functHandle;

  free(fullpath_o); free(fullpath_so); free(fullpath_c);
  free(command1); free(command2);
  return 1; /* success */
}
#endif  /* USE_COMPILED_FUNCTIONS */

static int cmdAddFunc ( int argc, char **argv )
{
  int i=0,j=0;
  char *buffer = calloc(MAX_FUNC_EXPR_LEN+MAX_VARIABLE_NAME_LEN,sizeof(char));
  char *fct_expr;
  char *fct_name;
  int namesize=0;
  char *ptr;
  FUNCTION *funct;

  FUNCTION *Function  = cfgCalc->functions;
  int	 nFunctions = cfgCalc->nFunctions;

#ifdef USE_COMPILED_FUNCTIONS
  if (g_module_supported() != TRUE) {
   g_print(_("Dynamically loaded functions are not supported on your system.\n"));
   return -1;
  }
#endif /* USE_COMPILED_FUNCTIONS */
  for (i=1; i < argc; i++) {
    strcat(buffer,argv[i]);
  }

  if( (fct_expr = strchr(buffer,'='))==NULL) 
	{
		rc_error( _("malformed expression.") );
	}

  else {
    if ( (ptr=strchr(buffer,'('))==NULL  )
      {
                rc_error( _("malformed expression.") );
      }

    else {
      fct_expr++;
      namesize = strlen(buffer)-strlen(ptr)+1;
      fct_name = calloc(namesize,sizeof(char));
      strncpy(fct_name,buffer,namesize-1);

      if( !validVariableName(fct_name) ) return 0;

      /* check if the function already exist */
      for( i=0; i<nFunctions; i++ )
	{
		if( strcasecmp( fct_name, Function[i].name ) ) continue;

		if (i < NUM_DEFAULT_FUNCTIONS) {
		  rc_error( _("This is already a default function\n") );
		  return 0;
		}

		else {
		  /* function already exist, we remove the old one first */
#ifdef USE_COMPILED_FUNCTIONS		  
		  g_module_close(Function[i].handle);
#endif /* USE_COMPILED_FUNCTIONS */		  
		  Function[i]=Function_0;
		  cfgCalc->nFunctions--;
		  for( j=i; j<cfgCalc->nFunctions; j++)
		    Function[j]=Function[j+1];
		  rc_printf( _("Replacing existing function\n") );
		}
	}

      /*printf("Adding new function\n");
      printf("  name: %s\n",fct_name);
      printf("  expression: %s\n",fct_expr);*/

      funct = &Function[cfgCalc->nFunctions];
/*#ifdef USE_COMPILED_FUNCTIONS      
      if (compile_function(fct_name, fct_expr, funct)) {
#else
      if (parser_verify_func(fct_expr, fct_name)) {
         fparser_add_user_defined_function(fct_name, fct_expr);
#endif    */  
	strncpy(funct->name,fct_name,MAX_VARIABLE_NAME_LEN);
	strncpy(funct->expression,fct_expr,MAX_FUNC_EXPR_LEN);
	cfgCalc->nFunctions++;
      /*}
      else {
#ifndef USE_COMPILED_FUNCTIONS 
	rc_error( _("malformed expression.") );
#endif
      }*/
    }
  }

  return 0;
}

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

/* Return a pointer to the command structure if the input is a valid command.
*/
static COMMAND *getCommand( char *command )
{
	int i;

	for( i=0; i<NUM_COMMANDS; i++ )
	{
		int len = strlen( _(Command[i].name) );
		char c;

		if( strlen(command) < len ) continue;
		if( strncasecmp( _(Command[i].name), command, len ) ) continue;
		c = command[len];
		if( c && !isspace(c) ) continue;

		return &Command[i];
	}
	return NULL;
}

/* Execute a command, returning non-zero if we are to exit the program.
*/
static int executeCommand( COMMAND *c, char *command )
{
	int argc; char **argv;
	char *line;
	int i;
	
	/* Count the number of arguments (including the command)
	*/
	for( line=command, argc=0; *line; argc++ )
	{
		while( *line &&  isspace(*line) ) line++;
		if( ! *line ) break;
		while( *line && !isspace(*line) ) line++;
	}
	
	/* Build the array of arguments.
	*/
	if(!( argv = calloc( argc, sizeof(char *) ) ))
	{
		/* Engine | Commands | Can't allocate any memory */
		rc_error( _("not enough memory.") );
		return 0;
	}
	for( line=command, i=0; i<argc; i++ )
	{
		while( *line &&  isspace(*line) ) line++;
		argv[i] = line;
		while( *line && !isspace(*line) ) line++;
		*line++ = 0;
	}

	/* Execute, free and return.
	*/
	i = c->func( argc, argv );
	free( argv );
	return i;
}

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

/* Process a line of text as either a command or an expression, or
** a bash-style semicolon-separated list of any mixture of the two.
*/
static int doCommandOrExpression( char *command )
{
	char *semi;
	COMMAND *c;
	int return_flag;

	if(( semi = strchr( command, ';' ) ))
	{
		/* Handle bash-style semicolon-separated lists of commands.
		*/
		*semi++ = 0;
		if(!( return_flag = doCommandOrExpression( command ) ))
		{
			return_flag = doCommandOrExpression( semi );
		}
	}
	else
	{
		/* Strip leading space from the command.
		*/
		while( *command && isspace(*command) ) command++;
		if( ! *command ) return 0;

		if(( c=getCommand( command ) ))
		{
			/* It is a command, so execute it...
			*/
			return_flag = executeCommand( c, command );
		}
		else
		{
			/* ...otherwise pass it on to the evaluator.
			*/
			rCalc_engine_Evaluate( command );
			return_flag = 0;
		}
	}
	return return_flag;
}

/* Wrapper for the above, so that stage changes only occur
** after each line has been processed, rather than each
** command/expression on each line.
*/
gboolean rc_engine_execute( RcEngine *engine, const gchar *command )
{
	gboolean return_flag;
	gchar *copy;
	
	g_assert( engine );
	g_assert( engine==cfgCalc );
	g_assert( command );

	rc_debug( RC_DBG_ENGINE, "evaluating `%s'.", command );

	copy = g_strdup( command );
	return_flag = doCommandOrExpression( copy ) ? TRUE : FALSE;
	g_free( copy );

	return return_flag;
}

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