/*
 * Maintaining a stack of files / scripts.
 * 
 * The Hugs 98 system is Copyright (c) Mark P Jones, Alastair Reid, the
 * Yale Haskell Group, and the OGI School of Science & Engineering at OHSU,
 * 1994-2003, All rights reserved.  It is distributed as free software under
 * the license in the file "License", which is included in the distribution.
 *
 */
#include "prelude.h"
#include "storage.h"
#include "connect.h"
#include "errors.h"
#include "machdep.h"
#include "opts.h"
#include "strutil.h"
#include "script.h"

/* --------------------------------------------------------------------------
 * Local script state:
 * ------------------------------------------------------------------------*/

/*
 * The scripts that either have been loaded or will be later are all
 * stored in fixed-size stacks. (Moving to growable tables will also 
 * require making the module table expandable.)
 */
struct strScript {
    String fileName;			/* Script file name                */
    String realName;			/* Full path to canonical name     */
    String directory;			/* Directory module was found in   */
    Time lastChange;			/* Time of last change to script   */
    Bool postponed;			/* Indicates postponed load        */
    Bool chased;			/* Added by import chasing?        */
};

static struct strScript scriptTable[NUM_SCRIPTS];

static Int    numScripts;               /* Number of scripts loaded        */
static Int    namesUpto;                /* Number of script names set      */
static Int    scriptsStable;            /* Number of (Prelude) scripts     */
                                        /* considered 'stable'             */
                                        /* (=> won't be nuked when clearing */
                                        /* the script stack / reloading.)   */

static Bool   needsImports;             /* set to TRUE if imports required */
       String scriptFile;               /* Name of current script (if any) */

/* --------------------------------------------------------------------------
 * Local function prototypes:
 * ------------------------------------------------------------------------*/
static Bool local addScript     Args((String,Long));
static Void local freeScript    Args((Int));

/* --------------------------------------------------------------------------
 * Initialising / freeing script stacks:
 * ------------------------------------------------------------------------*/
Void initScripts() {
  scriptFile    = 0;
  numScripts    = 0;
  namesUpto     = 0;
  scriptsStable = 0;
}

Void stopScripts() {
  int i;

  for (i=0; i < numScripts ; i++)
    freeScript(i);
}

static Void local freeScript(i)
Int i; {
  if (scriptTable[i].fileName)
    free(scriptTable[i].fileName);
  if (scriptTable[i].realName)
    free(scriptTable[i].realName);
  if (scriptTable[i].directory)
    free(scriptTable[i].directory);
}

/* We record the number of scripts that loading the Prelude
 * brought about, so that when the user comes to clear the module
 * stack (e.g., ":l<ENTER>"), only modules later than the Prelude
 * ones are scratched.
 */
Void setScriptStableMark() {
  scriptsStable = namesUpto;
}

String getScriptName(s)  /* access the script name at index 's' */
Script s; {
  if ( s >=0 && s <= numScripts ) {
    return scriptTable[s].fileName;
  } else {
    ERRMSG(0) "getScriptName: Illegal script index %d (max: %d)", s, numScripts
    EEND;
  }
  return NULL;
}

String getScriptRealName(s)  /* access the path of script at index 's' */
Script s; {
  if ( s >=0 && s <= numScripts ) {
    return scriptTable[s].realName;
  } else {
    ERRMSG(0) "getScriptRealName: Illegal script index %d (max: %d)", s, numScripts
    EEND;
  }
  return NULL;
}

Int getScriptHwMark() { /* return number of on the stack, loaded or not. */
  return namesUpto;
}

Int numLoadedScripts() { /* return number of currently loaded scripts */
  return numScripts;
}

/* --------------------------------------------------------------------------
 * Loading script files:
 * ------------------------------------------------------------------------*/

Void addScriptName(s,sch)  /* Add script to list of scripts   */
String s;                  /* to be read in ...               */
Bool   sch; {              /* TRUE => requires pathname search*/
    if (namesUpto>=NUM_SCRIPTS) {
	ERRMSG(0) "Too many module files (maximum of %d allowed)",
		  NUM_SCRIPTS
	EEND;
	return;
    }
    if (sch) {
	if (isModuleId(s)) {
	    String location = findMPathname(s);
	    if (!location) {
		ERRMSG(0) "Can't find module \"%s\"", s
		EEND;
	    }
	    scriptTable[namesUpto].fileName  = strCopy(location);
	    scriptTable[namesUpto].directory = NULL;
	} else {
	    scriptTable[namesUpto].fileName  = strCopy(findPathname(s));
	    scriptTable[namesUpto].directory = dirname(scriptTable[namesUpto].fileName);
	}
    } else {
	scriptTable[namesUpto].fileName  = strCopy(s);
	scriptTable[namesUpto].directory = NULL;
    }
    scriptTable[namesUpto].realName   = strCopy(RealPath(scriptTable[namesUpto].fileName));
    scriptTable[namesUpto].chased     = !sch;
    namesUpto++;
}

static Bool local addScript(fname,len)  /* read single script file */
String fname;                           /* name of script file     */
Long   len; {                           /* length of script file   */
    if (!quiet) {
	Printf("Reading file \"%s\":\n",fname);  FlushStdout();
    }
    setLastEdit(fname,0);

    needsImports = FALSE;
    scriptFile = 0;
    if (!parseScript(fname,len)) {   /* process script file */
	/* file or parse error, drop the script */ 
	forgetAScript(numScripts);
	errFail();
    }
    if (needsImports) return FALSE;
    checkDefns();
    typeCheckDefns();
    compileDefns();
    scriptFile    = 0;
    preludeLoaded = TRUE;
    return TRUE;
}

Bool chase(imps)                 /* Process list of import requests */
List imps; {
    Int    origPos = numScripts; /* keep track of original position */
    String origDir = scriptTable[origPos].directory;
    for (; nonNull(imps); imps=tl(imps)) {
	String modname = textToStr(textOf(hd(imps)));
	String iname = NULL;
	String rname;
	Bool   inOrigDir = FALSE;
	Int    i     = 0;

	if (origDir) {
	    iname = findMInDir(origDir,modname);
	    if (iname)
		inOrigDir = TRUE;
	}
	if (iname == NULL)
	    iname = findMPathname(modname);
	if (iname == NULL) {
	    ERRMSG(0) "Can't find imported module \"%s\"", modname
	    EEND;
	}

	rname = RealPath(iname);
	for (; i<namesUpto; i++)
	    if (filenamecmp(scriptTable[i].realName,rname)==0)
		break;
	if (i>=origPos) {           /* Neither loaded or queued        */
	    struct strScript tmpScript;

	    scriptTable[origPos].postponed = TRUE;
	    needsImports           = TRUE;

	    if (i>=namesUpto) {     /* Name not found (i==namesUpto)   */
		addScriptName(iname,FALSE);
		if (inOrigDir)
		    scriptTable[i].directory = strCopy(origDir);
	    } else if (scriptTable[i].postponed) {/* imported by itself? */
		ERRMSG(0)
		  "Recursive import dependency between \"%s\" and \"%s\"",
		  scriptTable[origPos].fileName, iname
		EEND;
	    }
	    /* Right rotate section of tables between numScripts and i so
	     * that i ends up with other imports in front of orig. script
	     */
	    tmpScript = scriptTable[i];
	    for (; i>numScripts; i--)
		scriptTable[i] = scriptTable[i-1];
	    scriptTable[numScripts] = tmpScript;
	    origPos++;
	}
    }
    return needsImports;
}

/* --------------------------------------------------------------------------
 * Adding scripts found in an argument vector:
 * ------------------------------------------------------------------------*/
Void addScriptsFromArgs(argc,argv)
Int argc;
String argv[]; {
    Int i;

#if USE_PREFERENCES_FILE
    extern Int     iniArgc;
    extern String* iniArgv;

    if (iniArgc > 0) {
        /* load additional files found in the preferences file */
        for (i=0; i<iniArgc; i++) {
	    addScriptName(iniArgv[i],TRUE);
        }
    }
#endif
    for (i=1; i<argc; ++i) {
      if (argv[i] && argv[i][0] && !isOption(argv[i])) {
	    addScriptName(argv[i],TRUE);
      }
    }


}

/* --------------------------------------------------------------------------
 * Dropping script files:
 * ------------------------------------------------------------------------*/
Void forgetScriptsFrom(scno) /* remove scripts from system     */
Script scno; {
    Script i;
    for (i=scno; i<namesUpto; ++i)
	freeScript(i);
    dropScriptsFrom(scno-1); /* don't count prelude as script  */
    namesUpto = scno;
    if (numScripts>namesUpto)
	numScripts = scno;
}

Void forgetAllScripts() {
  /* Drop all but the stable scripts; i.e., the
   * Prelude and (possibly) its implementation module(s).
   */
  forgetScriptsFrom( scriptsStable ); 
}

Void forgetAScript(scno) /* remove a script from system */
Script scno; {
    Script i;
    
    if (scno > namesUpto)
	return;

    freeScript(scno);

    for (i=scno+1; i < namesUpto; i++)
	scriptTable[i-1] = scriptTable[i];
    dropAScript(scno);
    namesUpto--;
}

Void readScripts(n)        /* Reread current list of scripts, */
Int n; {                   /* loading everything after and    */
    Time timeStamp;        /* including the first script which*/
    Long fileSize;         /* has been either changed or added*/

    for (; n<numScripts; n++) {         /* Scan previously loaded scripts  */
	getFileInfo(scriptTable[n].fileName, &timeStamp, &fileSize);
	if (timeChanged(timeStamp,scriptTable[n].lastChange)) {
	    dropScriptsFrom(n-1);
	    numScripts = n;
	    break;
	}
    }
    for (; n<NUM_SCRIPTS; n++)          /* No scripts have been postponed  */
	scriptTable[n].postponed = FALSE;       /* at this stage                   */


    while (numScripts<namesUpto) {      /* Process any remaining scripts   */
	getFileInfo(scriptTable[numScripts].fileName, &timeStamp, &fileSize);
	timeSet(scriptTable[numScripts].lastChange,timeStamp);
	if (numScripts>0)               /* no new script for prelude       */
	    startNewScript(scriptTable[numScripts].fileName);
	if (addScript(scriptTable[numScripts].fileName,fileSize))
	    numScripts++;
	else
	    dropScriptsFrom(numScripts-1);
    }

    if (listScripts)
	whatScripts();
    if (numScripts<=1)
	setLastEdit((String)0, 0);
}

Void whatScripts() {       /* list scripts in current session */
    int i;
    Printf("\nHugs session for:");
    for (i=0; i<numScripts; ++i) {
	Printf("\n%s",scriptTable[i].fileName);
    }
    Putchar('\n');
}

Void script(what)
Int what; {

  /* not much to it..will be more when/if the local tables
   * become resizable.
   */
  switch(what) {
  case RESET:   return;
  case MARK:    return;
  case INSTALL: return;
  case EXIT:    stopScripts();
                return;
  case BREAK:   return;
  }
}
  
