/*  File: action.c
 *  Author: Jean Thierry-Mieg (mieg@mrc-lmb.cam.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1992
 * -------------------------------------------------------------------
 * 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:
 **    Calls an action application.
 * Exported functions:
 * HISTORY:
 * Last edited: Jun 27 12:31 2003 (rnc)
 * * Dec  3 14:44 1998 (edgrif): Change calls to new interface to aceversion.
 * * Dec  2 15:56 1998 (edgrif): Fix minor bug in acedb version message.
 * * (I assume these changes made by rbrusk)
 *		-	'externalAsynchroneCommand' must return a value
 *		-	'SIGUSR1' : undefined in WIN32; user can user windows mgr "End Task"
 * * May  7 17:40 1996 (rd)
 * * May 12 17:39 1993 (mieg): pick_me_to_call now expects: command parameters
 * Created: Fri Jan 10 23:40:16 1992 (mieg)
 *-------------------------------------------------------------------
 */

/* $Id: action.c,v 1.45 2003/06/30 09:08:05 rnc Exp $ */

#include "acedb.h"
#include "aceio.h"
#include "bs.h"
#include "lex.h"
#include "whooks/systags.h"
#include "whooks/sysclass.h"
#include "whooks/tags.h"
#include "pick.h"
#include "call.h"
#include "dbpath.h"

#include "display.h"

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


void externalDisplay (KEY key)  
{
  OBJ obj = bsCreate(key) ;
  char *prog = 0, *args = 0 ;
  char *progpath;

  if (!obj)
    messcrash ("Pick_me_to_call called on unkown obj %s", name(key)) ;
  if (!bsGetData (obj, _Pick_me_to_call, _Text, &prog))
    { messout ("Sorry, a program and its parameters are expected here") ;
      goto abort ;
    }
 
  bsGetData (obj, _bsRight, _Text, &args) ;  /* get the parameters */
  
  /* If the prog exists in wscripts, use that, else run the command
     bare and assume it's on the user's path */

  progpath = dbPathFilName(WSCRIPTS, prog, "", "x", 0);
  
  if (progpath)
    {
      callScript(progpath, args);
      messfree(progpath);
    }
  else
    callScript (prog, args) ; 
  
 abort:
  bsDestroy (obj) ;
}

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

ACETMP exportStackToMail (Stack s)
{
  ACETMP tmpFile = NULL;
  char *cp;
  
  tmpFile = aceTmpCreate ("w", 0);
  if (tmpFile)

  if (stackExists(s))
    { 
      stackCursor (s, 0) ;
      while ((cp = stackNextText(s)))
	aceOutPrint (aceTmpGetOutput(tmpFile), "%s\n", cp) ;
    }
  aceTmpClose (tmpFile);

  return tmpFile ;
} /* exportStackToMail */

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

void acedbMailComments (void)
{
  Stack s ;
  ACETMP tmpFile;

  if (!messQuery ("Do you want to send a comment "
		  "to the authors of this program"))
    return ;

  s = stackCreate (100) ;
  pushText(s, messprintf("Code release:   %s,  %s\n",
			 aceGetVersionString(), aceGetLinkDateString())) ;
  tmpFile = exportStackToMail (s);
  stackDestroy (s);

  if (tmpFile)
    {
      char *script = dbPathFilName(WSCRIPTS, "acedb_mailer", "", "x", 0);
      if (script)
	{
	  callScript (script, 
		      messprintf("\"mieg@crbm.cnrs-mop.fr rd@sanger.ac.uk\" %s", aceTmpGetFileName(tmpFile))) ;
	  messfree(script);
	}
      aceTmpDestroy (tmpFile);
    }

  return;
} /* acedbMailComments */

void acedbMailer (KEY key, KEYSET ks, Stack sText)
{ 
  char *cp ;
  Stack  sm = stackCreate(50) ;
  int i, nmails = 0 ;
  OBJ obj ;
  int manip = 0 ;
  ACETMP tmpFile;

  tmpFile = exportStackToMail (sText);
  if (!tmpFile)
    return;

  if (!keySetExists(ks))
    { ks = keySetCreate() ;  /* I will destroy at end */
      manip = 10 ;
    }
  if (key)
    { keySet(ks, keySetMax(ks)) = key ;
      manip++ ;
    }
  
  for (i = 0 ; i < keySetMax(ks) ; i++)
    { if (pickType(keySet(ks,i)) != 'B')
	continue ;
      obj = bsCreate(keySet(ks,i)) ;
  
      if (!obj)
	continue ;
      if (bsGetData(obj, _E_mail, _Text,&cp))
	do
	  { 
	    if (nmails++)
	      catText(sm, ", ") ;
	    else
	      pushText(sm,"\'") ;
	    catText(sm, cp) ;
	  } while (bsGetData(obj, _bsDown, _Text,&cp)) ;
      if (stackMark(sm) > 800)
	{ messout ("%d recipients is enough, I stop at %s",
		   nmails, name(keySet(ks,i))) ;
	  break ;
	}

      bsDestroy(obj) ;
    }

  catText (sm, "\'  ") ;
  catText (sm, aceTmpGetFileName(tmpFile)) ;
  /*  catText (sm, " & ") ; */
  /* must wait for script to finish, so we can safely remove the
   * temp-file after its termination */
  
  if (!nmails)
    messout("No E_mail address in this keySet, sorry") ;
  else
     {
       char *script = dbPathFilName(WSCRIPTS, "acedb_mailer", "", "x", 0);
       if (script)
	 {
	   callScript (script, stackText(sm,0)) ;
	   messfree(script);
	 }
     }

  aceTmpDestroy (tmpFile);

  stackDestroy(sm) ;

  if (manip >= 10)
    keySetDestroy(ks) ;
  else if (manip == 1)
    keySetMax(ks)-- ;

  return;
} /* acedbMailer */

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

/* call an external shell command and print output in a text_scroll window
   from Erik Sonnhammer, 92-01-12
*/

static void doExternalFileDisplay (char *title, FILE *f, Stack s, BOOL isPipe)
{
  int line = 2, level = 0, max = 60 ;
  char *cp ;
  Graph old = graphActive() ; 
  Stack s1 = 0 ;

  graphCreate (TEXT_SCROLL, title ? title : "Action Display" , 0, 0, 0.8, 0.5);

  if (title)
    graphText(title, 1,1) ; 
  if (stackExists(s))
    {
      s1 = stackCreate (1000) ;
      stackCursor(s, 0) ;
      while ((cp = stackNextText (s)))
	{ catText(s1, cp) ; catText(s1, "\n") ; }
      level = freesettext (stackText(s1, 0),"") ;
    }
  else if (f)
    level = isPipe ? freesetpipe(f,"")  : freesetfile(f,"") ;

  if (level)
    {
      freespecial ("\n\t") ;
      while (line++, (cp = freecard(level))) /* closes the file */
	{ if (max < strlen(cp))
	  max = strlen(cp) ;
	graphText(cp, 0, line) ;
	}
      freeclose (level) ;
    }
  stackDestroy (s1) ;
  graphTextBounds (max, line);
  graphRedraw() ;
  graphActivate (old) ;
}

void externalFileDisplay (char *title, FILE *f, Stack s)
{ doExternalFileDisplay (title, f, s, FALSE) ; }

void externalPipeDisplay (char *title, FILE *f, Stack s)
{ doExternalFileDisplay (title, f, s, TRUE) ; }

void externalCommand (char *command)
{ char *cp = command ;

  while (*cp && *cp != ' ') cp++ ;
  *cp++ = 0 ; /* first space or terminal 0 */

#if !defined(MACINTOSH)
  doExternalFileDisplay (command, callScriptPipe(command, cp), 0, TRUE) ;
#endif
}

#if defined(MACINTOSH)
BOOL externalAsynchroneCommand (char *command, char *parms,
                                void *look, void(*g)(ACEIN fi, void *lk))
{ return ; }
#else
#include <signal.h>

/* This system is very simple minded, 
   We create a communication file xx->tmp_file
   then call the command in the background
   and test for the existence of xx->tmp_file.out 
   when a SIGUSER1 signal is received.
    If the outfile exists, the registered function is
   called on that output file, and cleanup is done
   */
   
    
static Array xxA = 0 ;

typedef struct xxLookStruct 
{ 
  int i ; /* index in xxA, 0: reusable */
  ExternalFuncType g;
  /*  void (*g)(FILE *f, void *lk) ;*/
  char *command ;
  ACETMP tmp_file;
  void *look ;
} XX ;

static void xxDestroy (XX *xx)
{ 	  /* I think that xx->look should be destroyed in the calling code */
  xx->i = 0 ;  /* slot can be reused */
  messfree(xx->command) ;

  /* remove signaling file /tmp/xxxx.out */
  filremove (aceTmpGetFileName(xx->tmp_file), "out") ;

  aceTmpDestroy (xx->tmp_file);
}

static void externalCompletion (int result)
{
  ACEIN fi = 0 ;
  XX *xx ;
  int i ;
  char *filename;

  if (!arrayExists (xxA))
    return ;

  for (i = 0 ; i < arrayMax(xxA) ; i++)
    {
      xx = arrp (xxA, i, XX) ;

      filename = filGetName(aceTmpGetFileName(xx->tmp_file), "out", "r", 0);
      if (xx->i &&                               /* not destroyed */
	  filename &&     /* no messout if absent  */
	  (fi = aceInCreateFromFile (filename, "r", "", 0))) /* would messout if absent */
	{
	  if (xx->g)  /* must destroy xx->look */
	    (*(xx->g))(fi, xx->look) ; /* will destroy fi */
	  xxDestroy (xx) ;
	}
    }
  
  if (filename)
    messfree(filename);

  return;
} /* externalCompletion */

BOOL externalAsynchroneCommand (char *command, char *parms,
                                void *look, ExternalFuncType g)/*void(*g)(ACEIN fi, void *lk))*/
{
  int i ;
  ACETMP tmp_file;
  XX  *xx ; 
  Stack s = 0 ;
  int pid = getpid() ;
  BOOL ret;

  if (!xxA)
    xxA = arrayCreate (20, XX) ;

  tmp_file = aceTmpCreate ("w", 0);
  if (!tmp_file)
    return FALSE ;

  aceOutPrint(aceTmpGetOutput(tmp_file), "// Call to: %s\n", command) ;
  aceTmpClose (tmp_file) ;

  for (i = 1 ; i < arrayMax(xxA) ; i++) /* start at i = 1 ! */
    if (!array (xxA, i, XX).i)
      break ;
  xx = arrayp (xxA, i, XX) ;  
  xx->i = i ;
  xx->command = dbPathFilName(WSCRIPTS, command, "",  "x", 0) ;
  xx->look = look ;
  xx->g = g ;
  xx->tmp_file = tmp_file ;

  signal (SIGUSR1, &externalCompletion) ;  /* could be called only once */

  s  = stackCreate (250) ;
  pushText (s, messprintf(" %d ", pid)) ;
  catText (s, aceTmpGetFileName(xx->tmp_file)) ;
  catText (s, "  ") ;
  if (*parms) catText (s, parms) ;
  catText (s, " &") ;
  
  /* because of &background, callScript always returns TRUE */
  if (xx->command)
    {
      callScript (command, stackText (s, 0));  
      ret = TRUE ;
    }
  else
    {
      messout ("Cannot find script %s", command) ;  
      if (xx->i && xx->g)
	(*(xx->g))(0, xx->look) ;
      xxDestroy (xx) ;
      ret = FALSE ;
    }
  
  stackDestroy(s);
  return ret ;
}

#endif /* MACINTOSH */

/***************************************************************/
/************ action display (currently unused) **************/

typedef struct LOOKSTUFF
  { int   magic;        /* == MAGIC */
    KEY   key ;
    Graph graph ;
  } *LOOK ;

static MENUOPT actionMenu[] = {
  {graphDestroy, "Quit"},
  {help,"Help"},
  {graphPrint,"Print"},
  {displayPreserve,"Preserve"},
   {0, 0}
} ;

static int MAGIC = 723544 ;	/* use also for graphAssociate pointer */

#define LOOKGET(name)     LOOK look ; \
                          if (!graphAssFind (&MAGIC, &look)) \
		            messcrash ("graph not found in %s", name) ; \
			  if (!look) \
                            messcrash ("%s received a null pointer", name) ; \
                          if (look->magic != MAGIC) \
                            messcrash ("%s received a wrong pointer", name)

static BOOL actionExecute (LOOK look)
{
  graphText(messprintf ("Action on %s not yet code sorry", 
			name(look->key)), 3, 3) ;
  graphRedraw() ;
  return TRUE ;
}

static void actionDestroy (void)
{
  LOOKGET("actionDestroy") ;

  look->magic = 0 ;
  messfree (look) ;
  
  graphAssRemove (&MAGIC) ;
}

BOOL actionDisplay (KEY key, KEY from, BOOL isOldGraph)
{
  LOOK look ;
  OBJ Action = 0 ;
  int _VAction = 0 ;
  KEY akey ;

  lexword2key("Action", &akey, _VMainClasses) ;
  _VAction = KEYKEY(akey) ;

  if (!key || !_VAction || class(key) != _VAction ||
      !(Action = bsCreate(key)))
    return FALSE ;

  bsDestroy(Action) ;

  look=(LOOK)messalloc(sizeof(struct LOOKSTUFF)) ;
  look->key = key ;
  look->magic = MAGIC;

  if (isOldGraph)
    { actionDestroy () ;
      graphClear () ;
      graphGoto (0,0) ;
      graphRetitle (name(key)) ;
      graphAssRemove (&MAGIC) ;
    }
  else 
    {
      displayCreate ("DtAction");
      
      graphRetitle (name(key)) ;
      graphRegister (DESTROY, actionDestroy) ;
      graphMenu (actionMenu) ;
    }
  graphAssociate (&MAGIC, look) ;
  look->graph = graphActive () ;
  actionExecute (look) ;
  
  return TRUE ;
}

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