/**
*** XPG - Graphical User Interface for Postgres
*** Copyright (C) 1993  Ranen Goren (ranen@cs.huji.ac.il).

*** 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.
**/


#include <stdio.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h> 
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#include <Xm/Xm.h>
#include <Xm/SelectioB.h>
#include "xpg.h"

#define String JUST_A_DUMB_STRING
#include "tmp/libpq.h"       /* postgres */
#undef String

#ifdef MEM_DEBUG
#include "/CS/system/ranen/Src/Lib/Malloc/malloc.h"
#endif



typedef struct
{
    char *desc;
    char *fname;
} scriptInfo;


enum cmdErrorCheck
{
    ERR_IGNORE,
    ERR_WARN,
    ERR_ABORT,
};



void scriptCancel();
void scriptNoMatch();
void scriptPlayOk();
extern char *removeSpacesFromName();
extern char *skipSpaces();
extern char *execAndWarn();
char *xpgPQexec();
char *createUniqueName();



/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
int openScriptSelection(rel, okCB, cancelCB, noMatchCB)
  relInfo *rel;
  void (*okCB)(), (*cancelCB)(), (*noMatchCB)();   /* the last can be null */
{
    Widget scriptDialog, helpButton, applyButton;
    PortalBuffer *p;
    int i,n,m;
    XmString *compStrList;
    char *pgResult;
    permStruct perms;

    setBusy(rel, RETR);
    checkPermissions(rel, "xpg_scripts", &perms);
    if (!perms.read)
    {
	warn(rel->top, 
	     "You are not permitted to run scripts in this database");
	setBusy(rel, NOBUSY);
	return;
    }
    for (i=0; i<2; i++)
    {
	pgResult = xpgPQexec("retrieve (xpg_scripts.all)");
	if (pgResult[0]=='R' && i==0) /* first failure, try to create class */
	{
	    status(rel, "class xpg_scripts not found. Creating it...");
	    pgResult = xpgPQexec("create xpg_scripts (desc=text, fname=text)");
	    continue;
	}
	else if (pgResult[0]=='R')   /* second time failed too! */
        {
	    warn(rel->top, "Error reading class xpg_script!\n%s", pgResult+1);
	    setBusy(rel, NOBUSY);
	    return 1;
	}
    }
    p = PQparray("blank");
    n = PQntuples(p);
    if (!n && noMatchCB)   /* no tuples and it's not a RECORD operation */
    {
	warn(rel->top, "No scripts found");
	setBusy(rel, NOBUSY);
	return 1;
    }
    
    scriptDialog = XmCreateSelectionDialog(rel->top,
					       "scriptList", NULL, 0);
    helpButton = XmSelectionBoxGetChild(scriptDialog, 
					XmDIALOG_HELP_BUTTON);
    XtUnmanageChild(helpButton);
    applyButton = XmSelectionBoxGetChild(scriptDialog, 
					 XmDIALOG_APPLY_BUTTON);
    XtUnmanageChild(applyButton);
    XtAddCallback(scriptDialog, XmNokCallback, okCB, rel);
    XtAddCallback(scriptDialog, XmNcancelCallback, cancelCB, scriptDialog);
    if (noMatchCB)
	XtAddCallback(scriptDialog, XmNnoMatchCallback, noMatchCB, rel);

    compStrList = (XmString *)XtMalloc(n * sizeof(XmString));
    for(i = 0; i < n; i++)
	compStrList[i] = XmStringCreateSimple(PQgetvalue(p, i, 0));
    XtVaSetValues(scriptDialog,
		  XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL,
		  XmNlistItems, compStrList,
		  XmNlistItemCount, n,
		  XmNmustMatch, (noMatchCB ? True : False),
		  NULL);
    while(--n >= 0)
	XmStringFree(compStrList[n]);
    XtFree(compStrList);
    XtManageChild(scriptDialog);
    setBusy(rel, NOBUSY);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void scriptCancel(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XtPointer cbs;
{
    XtDestroyWidget(w);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void scriptNoMatch(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmSelectionBoxCallbackStruct *cbs;
{
    warn(rel->top, "Entry does not match an existing script");
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
scriptGetSelectionData(rel, list, item, info)
  relInfo *rel;
  Widget list;
  XmString item;
  scriptInfo *info;
{
    int numMatches;
    int *matches;
    PortalBuffer *p;
    
    if (XmListGetMatchPos(list, item, &matches, &numMatches) == False)
	return 1;    /* list does not contain item - this can't be */
    if (numMatches != 1)
    {
	warn(rel->top, "There are several scripts with the same name!\nScript descriptions must be unique.\nOperation aborted.");
	XtFree(matches);
	return 1;
    }
    p = PQparray("blank");
    info->desc     = strdup(PQgetvalue(p,*matches-1,0));
    info->fname    = strdup(PQgetvalue(p,*matches-1,1));
    
    XtFree(matches);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
scriptFreeSelectionData(info)
  scriptInfo *info;
{
    free(info->desc);
    free(info->fname);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
scriptPlay(rel)
  relInfo *rel;
{
    openScriptSelection(rel, scriptPlayOk, scriptCancel, scriptNoMatch);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void scriptPlayOk(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmSelectionBoxCallbackStruct *cbs;
{
    scriptInfo info;
    Widget list;
    FILE *in;
    char buf[MAX_PG_CMD], cmdBuf[MAX_PG_CMD];
    char *msg, *tmp, *name;
    char *cmdPtr;
    enum cmdErrorCheck errorCheck, defaultErrorCheck;
    char *pgResult;
    Boolean inTransactionBlock = False;

    list = XmSelectionBoxGetChild(w, XmDIALOG_LIST);
    if (scriptGetSelectionData(rel, list, cbs->value, &info))
	return;    /* error */
    status(rel, "Running script '%s'", info.desc, info.fname);
    if ((in = fopen(info.fname, "r")) == NULL)
    {
	warn(rel->top, "Cannot read script file:\n'%s'", info.fname);
	scriptFreeSelectionData(&info);
	status(rel, "Script error: file '%s'", info.fname);
	return;
    }
    setBusy(rel, SCRIPT);
    defaultErrorCheck = ERR_WARN;    /* default */
    while (fgets(buf, MAX_PG_CMD, in))
    {
	buf[strlen(buf)-1] = '\0';
#ifdef XPG_DEBUG
	printf("Script cmd: %s\n", buf);
#endif
	cmdPtr = skipSpaces(buf);
	if (*buf == '#'  ||  *buf == '\0')     /* comment or empty line */
	    continue;
	/* check for error directives */
	errorCheck = defaultErrorCheck;   /* default, if no directive */
	if (!strncmp(cmdPtr, "ERR_IGNORE", strlen("ERR_IGNORE")))
	{
	    errorCheck = ERR_IGNORE;
	    cmdPtr += strlen("ERR_IGNORE");
	}
	else if (!strncmp(cmdPtr, "ERR_ABORT", strlen("ERR_ABORT")))
	{
	    errorCheck = ERR_ABORT;
	    cmdPtr += strlen("ERR_ABORT");
	}
	else if (!strncmp(cmdPtr, "ERR_WARN", strlen("ERR_WARN")))
	{
	    errorCheck = ERR_WARN;
	    cmdPtr += strlen("ERR_WARN");
	}
	cmdPtr = skipSpaces(cmdPtr);
	if (*cmdPtr == '\0')       /* error directive on a line of its own */
	    defaultErrorCheck = errorCheck;         /* changes the default */
	/* check for display directives */
	cmdPtr = skipSpaces(cmdPtr);
	if (!strncmp(cmdPtr, "VIEWCLASS", strlen("VIEWCLASS")))
	{
	    status(rel, "script: creating a new class viewer...");
	    cmdPtr += strlen("VIEWCLASS");
	    if ((cmdPtr = removeSpacesFromName(cmdPtr)) != NULL)
		openView(cmdPtr, cmdPtr);
	    else
		warn(rel->top, "VIEW command with no argument??\nSkipping...");
	    continue;
	}
	if (!strncmp(cmdPtr, "VIEWPORTAL", strlen("VIEWPORTAL")))
	{
	    status(rel, "script: creating a new portal viewer...");
	    cmdPtr += strlen("VIEWPORTAL");
	    cmdPtr = skipSpaces(cmdPtr);
	    if ((cmdPtr = removeSpacesFromName(cmdPtr)) != NULL)
		openView(NULL, cmdPtr);
	    else
		warn(rel->top, "VIEW command with no argument??\nSkipping...");
	    continue;
	}
	/* check for begin/end commands */
	cmdPtr = skipSpaces(cmdPtr);
	if (!strncmp(cmdPtr, "begin", strlen("begin")))
	    inTransactionBlock = True;
	else if (!strncmp(cmdPtr, "end", strlen("end")))
	    inTransactionBlock = False;

	/* execute a postgres command */
	if (*cmdPtr == '\0')    /* do not give postgres an empty string */
	    continue;
	/* prepare the messages to popup in case of execution errors */
	msg = NULL;
	if (errorCheck == ERR_WARN)
	    msg = "Script command failed!\n(trying to continue)";
	else if (errorCheck == ERR_ABORT)
	    msg = "Script caused fatal error!\n(aborting script)";

	/* check for a retrieve command which target is the blank portal */
	if (!strncmp(cmdPtr, "retrieve", 8) && 
	    ((*(tmp=skipSpaces(cmdPtr+8))=='(') || !strncmp(tmp, "unique", 6)))
	{
	    if (!inTransactionBlock)  /* because of the named portal */
		xpgPQexec("begin");
	    sprintf(cmdBuf, "retrieve portal %s%s", 
		    name=createUniqueName("xpg_tmp"), cmdPtr+8);
	    pgResult = execAndWarn(rel->top, cmdBuf, msg);
	    sprintf(cmdBuf, "fetch all in %s", name);
	    execAndWarn(rel->top, cmdBuf, "Fetch failed!");
	    if (!inTransactionBlock)  /* restore to previous state */
		xpgPQexec("end");
	    if (pgResult[0] != 'R')
		openView(NULL, name);
	}
	else   /* different form of retrieve command, or different command */
	    pgResult = execAndWarn(rel->top, cmdPtr, msg);

	/* check for execution errors */
	if (pgResult[0]=='R' && (errorCheck == ERR_ABORT))
	{
	    if (inTransactionBlock)
		execAndWarn(rel->top, "end", "END cmd failed! Things got bad");
	    fclose(in);
	    scriptFreeSelectionData(&info);
	    scriptCancel(w, rel, NULL);
	    setBusy(rel, NOBUSY);
	    return;
	}
    }
    status(rel, "Script execution done.");
    fclose(in);
    scriptFreeSelectionData(&info);
    scriptCancel(w, rel, NULL);
    setBusy(rel, NOBUSY);
}
