/*  File: jadeserver.c
 *  Author: J D Barnett, Lincoln Stein  and Jean Thierry-Mieg
 *  Copyright (C) L Stein and J Thierry-Mieg, 1996
 * -------------------------------------------------------------------
 * Acedb 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description:
 * Runs tace in the back with a netclient dialog on stdin/out
 * Effectivelly allowing Jade to talk to a standalone tace
 * 
 * Exported functions:
 * HISTORY:
 * Last edited: Apr  3 16:16 2000 (edgrif)
 * * Dec  2 16:34 1998 (edgrif): Corrected decl. of main, added code to
 *              record build time of this module.
 * * Oct 16 17:27 1998 (fw): use messSysErrorText instead of strerror
 * * Jul  7 10:24 1998 (edgrif): Replace references to sys_errlist with
 *                               strerror function.
 * Created: Wed Nov 25 20:02:45 1992 (j barnett)
 *-------------------------------------------------------------------
 */

/* $Id: jadeserver.c,v 1.9 2000/04/03 15:42:51 edgrif Exp $ % */

#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include "regular.h"
#include "array.h"
#include "aceclient.h"
#include "version.h"


#define OK 200
#define GOODBYE 201
#define DEBUG 301
#define COMMENT 302
#define REDIRECT 303
#define PARAMERROR 401
#define SYNTAXERROR 402
#define UNIMPLEMENTED 403
#define TIMEOUTERROR 501
#define COMMERROR 502
#define MEMERROR 503
#define FILEERROR 504

#define FIND "find"
#define SHOW "show"
#define QUERY "query"
#define UNDO "undo"
#define FOLLOW "follow"
#define TABLE "table"
#define NTABLE "ntable"
#define MODEL "model"
#define IS "is"
#define LIST "list"
#define QUIT "quit"
#define HELP "help"

#define COMMENTSTRING "//"
#define ERRORSTRING "//!! "
#define KEYSETSTRING "KeySet"
#define EOT "."

/* I promise never to make my commands larger than this! */
#define COMMAND_MAX 128
/* I promise that Ace class names can't get larger than this! */
#define MAX_CLASSNAME 512
/* I promise never to send a message back to the user larger than this! */
#define MAX_MESSAGE 512

#ifndef FILENAME_MAX
#define FILENAME_MAX 125
#endif

/*******************************************************************/
/********** Missing routines ***************************************/

void graphCleanUp(void)
{ return ;
}

void graphText (char *text, float x, float y)
{ printf(text) ;
}

void graphHelp(void)
{ return ;
}
void graphClear(void)
{ printf("\n\n") ;
  return ;
}

int graphExists(void)
{ return 0 ;
}

void graphRedraw(void)
{ printf("\n") ;
  return ;
}

void graphTextBounds(int w, int h)
{ return ;
}

void graphColor(int color)
{ return ;
}

void * graphCreate()
{ return NULL ;
}

BOOL graphInterruptCalled (void)
{ return FALSE ;
}

BOOL xClientGetKey (KEY key)
{ return TRUE ;
}

/*  end of graph stubs */


static chunkSize = 10;
/* Opaque database handle JC do not need to know what it is */
static Stack mainStack;
/* static KEY option = 0; */

void writeStatus (int status,char* message) {
  fprintf(stdout,"%d %s\r\n",status,message);
  fflush(stdout);
}

void writeln(char* line) {
  fprintf(stdout,"%s\r\n",line);
  fflush(stdout);
}

void die (int status, char* message) {
  writeStatus(status,message);
  exit(-1);
}

void reallocate (void** buffer, int old_size, int new_size) {
  void* newbuffer;
  int bytes_to_copy;

  bytes_to_copy = (old_size < new_size) ? old_size : new_size;

  newbuffer = malloc(new_size);
  if (newbuffer == NULL)
    die(MEMERROR,"ran out of memory allocating input buffer");
  if (*buffer != NULL) {
    memcpy(newbuffer,*buffer,bytes_to_copy);
    free(*buffer);
  }
  *buffer = newbuffer;
}

char* readln() {
  static char* linebuffer = NULL;
  static int linebufsize = 512;
  int length;
  char* result;
  int oldsize = 0;
  int offset = 0;

  if (linebuffer == NULL) {
    reallocate((void**)&linebuffer,oldsize,linebufsize);
  }

  while (1) {

    result = fgets(linebuffer+offset,linebufsize-offset,stdin);
    if (result == NULL)
      return NULL;

    length = strlen(linebuffer);

    /* empty string is OK */
    if (length == 0)
      return linebuffer;

    /* if the last character is a \n, then we got the end of the line
       so we're done */
    if (linebuffer[length-1] == '\n') {
      if ( (length >= 2) && (linebuffer[length-2] == '\r') )
	linebuffer[length-2] = '\0';
      else
	linebuffer[length-1] = '\0';
      return linebuffer;
    }

    /* otherwise we got a truncated, and we need to make the buffer bigger
     and try again */
    oldsize = linebufsize;
    linebufsize *= 2;
    reallocate((void**)&linebuffer,oldsize,linebufsize);
    offset = length;
  }
       
}

char* readCommand(char** extraData) {
  static char command[COMMAND_MAX]; /* command can't be longer than 512 characters */
  char *line;
  char nextchar;
  int i=0,j=0;

  if ( (line = readln()) == NULL)
    return NULL;

  /* Skip over whitespace and other non-alphanumeric chars at the beginning of the line.  */
  while ((i < COMMAND_MAX-1) && line[i] && !isalnum(line[i]))
    i++;


  while (i < COMMAND_MAX-1 && line[i] && !isspace(nextchar = line[i])) {
    command[j++] = nextchar;
    i++;
  }

  command[j]='\0';
  *extraData = isspace(line[i]) ? line + i + 1 : line + i;

  return command;
}

BOOL streq (char* s1, char* s2) {
  int length;
  length = strlen(s2);
  if (strncasecmp(s1,s2,length) == 0)
    return TRUE;
  return FALSE;
}

/*************************************************************************/
/* Find the "%d Active Objects" line and return the count */
/*************************************************************************/
int countActive (char* text) {
  int objectCount;
  while (text) {
    if (sscanf(text,"// %d Active Objects",&objectCount))
      return objectCount;
    /* if we didn't find it, skip to the next line and try again */
    text = strchr(text,'\n');
    if (!text) break;
    if (!*(++text)) break;
  }
  /* if we get here, we never found the string.  Return -1 */
  return -1;
}

/*************************************************************************/
/* replace newlines with CRLF pairs.  Strip // comments entirely. */
/*************************************************************************/
void stripAndPrint (char* text,int length,
		    BOOL stripBlanks,BOOL stripLastBlank, int encore) {
  char* end_of_line;
  char* current = text;
  
  if (encore && *text) /* mieg: because writeln adds an extra \n */
    {
      end_of_line = text + strlen(text) - 1 ;
      if (*end_of_line == '\n') *end_of_line = 0 ;
    }
  
  if (current[0] == '\n') current++;

  while (current < text + length) {

    if ((strncmp(current,COMMENTSTRING, 2) == 0) ||
	(strncmp(current,KEYSETSTRING, 6) == 0) ) 
      {

       if ((end_of_line = strchr(current,'\n')) == NULL)
	 return;

       current = end_of_line + 1;
       continue;
    }

    end_of_line = strchr(current,'\n');

    /* sorry, this is a little weird because the number of lines that come back
       from the ace server is not entirely predictable */
    if (end_of_line != NULL) {

      *end_of_line = '\0';
      
      if (!stripBlanks || strlen(current) > 0)
	writeln(current);

      current = end_of_line + 1;

    } else {

      if ((!stripLastBlank && !stripBlanks) || (strlen(current) > 0) )
	writeln(current);

      return;

    }

  }

}

/*************************************************************************/
/* All-purpose query handler.  If show is TRUE, then  */
/*************************************************************************/
void doQuery(void* commandLook, char* query,BOOL show) {
  char message[MAX_MESSAGE];
  char subclass[MAX_CLASSNAME],superclass[MAX_CLASSNAME];
  char* answer;
  int retval, length, active = 0, level;
  BOOL encoring = FALSE;

    level = freesettext (query,"");
    mainStack = stackReCreate(mainStack, 4000);
    aceCommandDoExecute (commandLook, level, FALSE, 0, 0);
    answer = stackText (mainStack, 0);

    /*  What to do about retval??? */
    /*      retval = askServerBinary(handle,query,&answer,&length,&encore,chunkSize);

    if (retval > 0) {
      sprintf(message,"ACE: error code %d",retval);
      writeStatus(TIMEOUTERROR,message);
      if (answer != NULL) free(answer);
      break;
    }
    */
    if (show) {

      /* Some error detection and reporting.  This has to be much expanded.*/
      *message = '\0'; /* paranoia */
      if (2 == sscanf((char*)answer,"// %s is a sub class of %[a-zA-Z_0-9]",subclass,superclass)) {
	sprintf(message,"%s is the superclass for %s",superclass,subclass);
	writeStatus(REDIRECT,message);
	return;
      } else if (1 == sscanf((char*)answer,"// Sorry, %[a-z A-Z_0-9:]",message)) {
	writeStatus(SYNTAXERROR,message);
	return;
      } else if (!strncmp((char*)answer,"// This table has a problem",22)) {
	writeStatus(SYNTAXERROR,"table syntax error");
	return;
      }

      writeStatus(OK,"Results follow, terminated by \".\"");
      stripAndPrint((char*)answer,strlen(answer),FALSE,FALSE, 0);
    }
    else
      if ((active = countActive((char*)answer)) >= 0) {
	sprintf(message,"%d objects",active);
	writeStatus(OK,message);
      }
  if (show) writeln(EOT);
}

/*************************************************************************/
/* Handling of externally-defined tables:
   1. opens up a temporary file using tempnam() function
   2. writes the user's table into that file
   3. asks the server to process the file
   4. prints the result
   5. unlinks the table
   */
#define TMP "/tmp"
void doTable( void* commandLook)
{
  char* tempName;
  char message[FILENAME_MAX + COMMAND_MAX + 1];
  char* line;
  FILE* temp;

  if (!(tempName = tempnam("/var/tmp", ".acenetcl"))) {
    sprintf(message,"Error getting temp file name: %s", 
	    messSysErrorText()) ;
    writeStatus(FILEERROR,message);
    return;
  }
  unlink(tempName); /* just to be safe */
  temp = fopen(tempName,"w");

  if (temp == NULL) {
    sprintf(message,"Error creating temp table file \"%s\":%s", 
	    tempName, messSysErrorText()) ;
    writeStatus(FILEERROR,message);
    return;
  }

  /* Here is where we accept lines from the remote and write them into the
     temp file.  This is very, very hacky */
  writeStatus(OK,"Enter table data.  End with \".\" on a line by itself.");
  while ((line = readln()) && (line[0] != '.') ) {
    fprintf(temp,"%s\n",line);
  }
  fclose(temp);

  /* it's possible that the input suddenly died.  In this case we just
     clean up and get out of here */
  if (line != NULL) {
    /* Perform a "table" style query using the current file for input
     This is the obviously gross part. */
    sprintf(message,"table -j %s",tempName);
    doQuery(commandLook, message,TRUE);
  }

  unlink(tempName);
  free((void*) tempName);
}

/*************************************************************************/
void printHelp () {
  char* helpText[] = {
    "- Commands:",
    "-    FIND   SHOW   FOLLOW   TABLE  NTABLE MODEL",
    "-    LIST     IS   QUERY     UNDO    QUIT  HELP",
    "- You need to know the Ace query language to use this",
    "- service.  Sorry.",
    "End of HELP info",
    NULL
  };
  char** m = helpText;
  while (*m != NULL)
    writeStatus(COMMENT,*m++);
}

/*************************************************************************/
void unimplemented (char* command) {
  char message[MAX_MESSAGE];
  sprintf(message,"%s is currently unimplemented",command);
  writeStatus(UNIMPLEMENTED,message);
}


/*************************************************************************/
BOOL processCommand(void* commandLook, char* command,char* data) {

  /* the following stuff is a slightly awkward dynamic
     string copy.  It works and is not particularly inefficient
     since the commands are really quite small.*/
  static char* message = NULL;
  static int currentsize = 0;
  BOOL show = FALSE;
  BOOL pCheck = TRUE;

  int sizeneeded = strlen(data) + COMMAND_MAX + 1; 
  if ((message == NULL) || (currentsize < sizeneeded)) {
    reallocate((void**)&message,currentsize,sizeneeded);
    currentsize = sizeneeded;
  }
  *message = '\0';

  if (streq(command,QUIT))
    return TRUE;
  if (streq(command,HELP))
    printHelp();
  else if (streq(command,FIND))
    strcat(message,"find ");
  else if (streq(command,FOLLOW))
    strcat(message,"follow ");
  else if (streq(command,IS))
    strcat(message,"is ");
  else if (streq(command,QUERY))
    strcat(message,"query ");
  else if (streq(command,UNDO)) {
    strcat(message,"undo ");
    pCheck = FALSE;
  }
  else if (streq(command,SHOW)) {
    strcat(message,"show -j ");
    pCheck = FALSE;
    show = TRUE;
  }
  else if (streq(command,NTABLE)) {
    strcat(message,"table -j -n ");
    show = TRUE;
  }
  else if (streq(command,MODEL)) {
    strcat(message,"model ");
    show = TRUE;
  }
  else if (streq(command,LIST)) {
    strcat(message,"list -j ");
    pCheck = FALSE;
    show = TRUE;
  }
  else if (streq(command,TABLE))
    doTable(commandLook);
  else if (!command[0]) /* do nothing for empty commands */
      ;
  else
    writeStatus(SYNTAXERROR,"unknown command");

  if (*message != '\0') {
    if (pCheck && !*data) { 
      writeStatus(PARAMERROR,"missing parameter(s)");
      return FALSE;
    }
    strcat(message,data);
    doQuery(commandLook, message,show);
  }

  return FALSE;
}


/* Defines a routine to return the compile date of this file.                */
UT_MAKE_GETCOMPILEDATEROUTINE()


/*************************************************************************/
int main(int argc, char *argv[] )
{
  char *host = "localhost";
  unsigned long port = DEFAULT_PORT;
  int timeOut = 300;

  BOOL isQuit = FALSE;
  char *command,*extraData ;

  void *commandLook;

  sessionInit (argc>1 ? argv[1]: 0) ;

  mainStack = stackCreate(4000);
  commandLook =( void*)aceCommandCreate (1, NULL, mainStack);

  writeStatus(OK,"Ace server ready and waiting");
  while (!isQuit && (command = readCommand(&extraData)) )
    isQuit = processCommand(commandLook, command,extraData);

  writeStatus(GOODBYE,"A bientot");

  return(EXIT_SUCCESS) ;
}

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