#include	<stdio.h>
#include	<ctype.h>
#include	<sys/types.h>
#include	<sys/stat.h>


#include	"ubermud.h"
#include	"io.h"
#include	"externs.h"


/*
Copyright(C) 1990, Marcus J. Ranum, All Rights Reserved.
This software may be freely used, modified, and redistributed,
as long as this copyright message is left intact, and this
software is not used to develop any commercial product, or used
in any product that is provided on a pay-for-use basis.
*/


/*
#define	MONITORDEBUG
*/

/* our operating environment. */
static	MemOp	maincore[MACHBUFSIZ];
static	Frame	mainframes[MACHFRASIZ];
static	Machine	maincpu = {
	mainframes, maincore, 0, MACHFRASIZ, 0, MACHBUFSIZ
};


static	FILE	*progf = (FILE *)0;
static	long	proguid = (long)0;

/* strings */
static	char	*badwrt = "write failed:";
static	char	*badread = "open for read failed:";
static	char	*badstack = "stack over/underflow!!!\n";
static	char	*badstring = "string table out of space!!!\n";
static	char	*huhmsg = "Huh(tm)?  (Type \"help\" for help.)\n";
static	char	*oocsmsg = "out of code space. compilation ignored\n";
static	char	*oossmsg = "out of string space. compilation ignored\n";
static	char	*sffmsg = "function write to system table failed: ";
static	char	*dffmsg = "function write to object failed: ";

/* bad run messages */
static	char	*runfmsg = "function calls nested too deep (aborted)\n";
static	char	*runsmsg = "function requires too much stack (aborted)\n";
static	char	*runimsg = "function contains illegal operand (aborted)\n";
#ifdef	LIMITRUNCPU
static	char	*runcmsg = "function used too many CPU cycles (aborted)\n";
#endif

/* for yyerror - mild kludge */
static	Iob	*curriob;	


/*
scratch the block of data given to the temporary file named after
the user id owning the code. the file pointer is juggled around
here, as needed.
*/
program_in(b,iob)
char	*b;
Iob	*iob;
{

	/* close and re-open output file */
	if(proguid != iob->uid || progf == (FILE *)NULL) {
		char	f[500];

		(void)sprintf(f,"%ld.code",iob->uid);
		if(progf != (FILE *)NULL)
			(void)fclose(progf);
		if((progf = fopen(f,"a")) == (FILE *)NULL) {
			iobput(iob,badwrt,sys_errlist[errno],"\n",0);
			return(0);
		}
		proguid = iob->uid;
	}

	if(fprintf(progf,"%s",b) == EOF)
		iobput(iob,badwrt,sys_errlist[errno],"\n",0);
	return(0);
}


/*
print the size of the program scratch buffer.
*/
void
program_siz(iob)
Iob	*iob;
{

	char	f[500];
	struct	stat	stbuf;

	(void)sprintf(f,"%ld.code",iob->uid);
	if(stat(f,&stbuf))
		return;
	if(stbuf.st_size > 0)
		iobput(iob,"temporary file ",f," currently contains ",
		itoa(stbuf.st_size)," bytes\n",0);
}



/*
in the event of a change of the user's monitor state, make
sure their temp file is closed, written, and ready to go.
*/
void
program_stop(iob)
Iob	*iob;
{
	/* close output file if it is opened to us */
	if(proguid == iob->uid && progf != (FILE *)NULL) {
		(void)fclose(progf);
		progf = (FILE *)NULL;
		proguid = (long)0;
	}
}



/*
unlink the program buffer. >poof<
*/
void
program_flush(iob)
Iob	*iob;
{
	char	f[500];

	/* close output file if it is opened to us */
	if(proguid == iob->uid && progf != (FILE *)NULL) {
		(void)fclose(progf);
		progf = (FILE *)NULL;
		proguid = (long)0;
	}

	(void)sprintf(f,"%ld.code",iob->uid);
	(void)unlink(f);
}



static	void
compile_FILE(fdes,iob,uid,euid)
FILE	*fdes;
Iob	*iob;
long	uid;
long	euid;
{
	int	rv;
	int	runval;
	int	done = 0;
	Oper	op;
	char	*ep;
	long	tmpu = uid;
	long	tmpeu = euid;

	setyyinput(fdes);
	yylineno = 1;

	while(!done) {
		resetmachine(&maincpu);
		cache_reset();
		resetparser();

		rv = yyparse();
		switch(rv) {
			case	COMPILE_OOS:
				if(iob != (Iob *)0)
					iobput(iob,oossmsg,0);
				else
					logf(oossmsg,0);
				break;

			case	COMPILE_OOE:
				if(iob != (Iob *)0)
					iobput(iob,oocsmsg,0);
				else
					logf(oocsmsg,0);
				break;

			case	COMPILE_OK:
				runval = run(&maincpu,yaccprog,0,&tmpu,&tmpeu);
				if(runval != RUN_OK) {
					switch(runval) {
					case RUN_STACK:
						if(iob != (Iob *)0)
							iobput(iob,runsmsg,0);
						else
							logf(runsmsg,0);
						break;
					case RUN_FRAME:
						if(iob != (Iob *)0)
							iobput(iob,runfmsg,0);
						else
							logf(runfmsg,0);
						break;
					case RUN_ILLEGAL:
						if(iob != (Iob *)0)
							iobput(iob,runimsg,0);
						else
							logf(runimsg,0);
						break;
#ifdef	LIMITRUNCPU
					case RUN_CPU:
						if(iob != (Iob *)0)
							iobput(iob,runcmsg,0);
						else
							logf(runcmsg,0);
						break;
#endif
					}
				}
				if(runval == RUN_OK &&
				(maincpu.m_pc != 0 || maincpu.m_fp != 0))
					logf(runsmsg,0);
				break;

			case	COMPILE_FUNC:
				op.p = yaccprog;
				ep = tmpalloc(strlen(funcname())+1+(int)sizeof(long));
				if(ep == (char *)0) {
					fatal("memory allocation failed!\n",0);
					break;
				}
				ELENUM(ep) = funcnum();
				(void)strcpy(ELENAM(ep),funcname());
				if(ELENUM(ep) == 0) {
					int	fv;
					if((fv = sys_freeze(ep,TYP_FUNC,op,uid,euid)) != 0) {
						if(iob != (Iob *)0)
							iobput(iob,sffmsg,errmsg(fv),"\n",0);
						else
							logf(sffmsg,errmsg(fv),"\n",0);
					} else {
						iobput(iob,"compiled func #",
						ltoa(funcnum()),".",
						funcname()," (",
						itoa((int)(PROGSIZE(yaccprog))),
						" bytes)\n",0);
					}
				} else {
					int	fv;
					if((fv = disk_freeze(ep,TYP_FUNC,op,funcop(),uid,euid)) != 0) {
						if(iob != (Iob *)0)
							iobput(iob,dffmsg,errmsg(fv),"\n",0);
						else
							logf(dffmsg,errmsg(fv),"\n",0);
					} else {
						iobput(iob,"compiled func #",
						ltoa(funcnum()),".",
						funcname()," (",
						itoa((int)(PROGSIZE(yaccprog))),
						" bytes)\n",0);
					}
				}
				break;

			case	1:
			case	COMPILE_BAD:
				done++;
				if(iob != (Iob *)0)
					iobput(iob,"(remaining input discarded)\n",0);
				break;

			default:
				done++;
				break;
		}
		tmpfree();
	}
}




void
program_compile(iob)
Iob	*iob;
{

	curriob = iob;

	if(proguid != iob->uid || progf == (FILE *)NULL) {
		char	f[500];

		(void)sprintf(f,"%ld.code",iob->uid);
		if(progf != (FILE *)NULL)
			(void)fclose(progf);
		if((progf = fopen(f,"r")) == (FILE *)NULL) {
			iobput(iob,badread,sys_errlist[errno],"\n",0);
			return;
		}
		proguid = iob->uid;
	}
	compile_FILE(progf,iob,iob->uid,iob->euid);
	(void)fclose(progf);
	progf = (FILE *)NULL;
	curriob = (Iob *)0;
}


#ifdef	USECPP
/*
run a file through the pre-processor and return 0 or the resulting file
perforce, this is system specific!
*/
static	char	*
preprocess_file(f)
char	*f;
{
	extern	char	*mktemp();
	static	char	*tmpf = "/tmp/usubXXXXXX";
	int	sval;
	char	cbuf[600];

	if((tmpf = mktemp(tmpf)) == NULL)
		return(NULL);
	(void)strcpy(cbuf,DEFAULTCPP);
	(void)strcat(cbuf," ");
	(void)strcat(cbuf,f);
	(void)strcat(cbuf," ");
	(void)strcat(cbuf,tmpf);
	sval = system(cbuf);
	if(sval == 127) {
		logf("cannot invoke c-preprocessor!\n",0);
		return((char *)0);
	}
	if(sval != 0)
		return((char *)0);
	return(tmpf);
}
#endif



compile_file(name)
char	*name;
{
#ifdef	USECPP
	if((name = preprocess_file(name)) == (char *)0) {
		logf("preprocessor run failed\n",0);
		(void)unlink(name);
		return(1);
	}
#endif

	curriob = (Iob *)0;
	if((progf = fopen(name,"r")) == (FILE *)NULL) {
		logf("cannot open ",name,":",(char *)-1,"\n",0);
		return(1);
	}
	compile_FILE(progf,(Iob *)0,(long)0,(long)0);
	(void)fclose(progf);
	progf = (FILE *)NULL;
	curriob = (Iob *)0;

#ifdef	USECPP
	(void)unlink(name);
#endif

	return(0);
}



int
yyerror(s)
char	*s;
{
	if(curriob != (Iob *)0)
		iobput(curriob,s," line ",itoa(yylineno),"\n",0);
	else
		logf(s," line ",itoa(yylineno),"\n",0);
}



static	void
call_with_argv(iob,objno,ac,av,uid,euid)
Iob	*iob;
long	objno;
int	ac;
char	*av[];
long	uid;
long	euid;
{
	int	dt;
	int	strof;
	long	tmpu = uid;
	long	tmpeu = euid;

	/* REALLY important !*/
	resetparser();
	cache_reset();
	resetmachine(&maincpu);

#ifdef	MONITORDEBUG
	printf("calling #%d.%s with %d args\n",objno,av[0],ac - 1);
#endif

	(void)assemble(yaccprog,OP_RPUSH);
	(void)assemble(yaccprog,(int)objno);
	(void)assemble(yaccprog,OP_ELPUSH);

	if((strof = putinstringbuf(av[0])) == -1) {
		logf(badstring,0);
		return;
	}
	(void)assemble(yaccprog,strof);
	(void)assemble(yaccprog,OP_EEVAL);

	for(dt = 1; dt < ac; dt++) {
		(void)assemble(yaccprog,OP_SPUSH);
		if((strof = putinstringbuf(av[dt])) == -1) {
			logf(badstring,0);
			return;
		}
		(void)assemble(yaccprog,strof);
	}

	(void)assemble(yaccprog,OP_CALL);
	(void)assemble(yaccprog,ac - 1);
	(void)assemble(yaccprog,OP_POP);
	(void)assemble(yaccprog,OP_STOP);
	strof = run(&maincpu,yaccprog,0,&tmpu,&tmpeu);

	if(strof != RUN_OK) {
		switch(strof) {
		case RUN_STACK:
			if(iob != (Iob *)0)
				iobput(iob,runsmsg,0);
			logf(runsmsg,0);
			break;
		case RUN_FRAME:
			if(iob != (Iob *)0)
				iobput(iob,runfmsg,0);
			logf(runfmsg,0);
			break;
		case RUN_ILLEGAL:
			if(iob != (Iob *)0)
				iobput(iob,runimsg,0);
			logf(runimsg,0);
			break;
#ifdef	LIMITRUNCPU
		case RUN_CPU:
			if(iob != (Iob *)0)
				iobput(iob,runcmsg,0);
			logf(runcmsg,0);
			break;
#endif
		}
	}
	if(strof == RUN_OK && (maincpu.m_pc != 0 || maincpu.m_fp != 0))
		logf(badstack,0);
	tmpfree();
}



void
call_sysfunc(fname,uid,euid)
char	*fname;
long	uid;
long	euid;
{
	char	*av[2];

	av[0] = fname;
	av[1] = (char *)0;
	call_with_argv((Iob *)0,(long)0,1,av,uid,euid);
}




/*
read a buffer of stuff from the monkey at the other end of the keyboard
and try to resolve it into a function call.
*/
player_in(b,iob)
char	*b;
Iob	*iob;
{
	char	*p = b;
	static	char	*fnamb = 0;
	int	dt;
	Oper	d;
	int	ac;
	char	*av[MUDMAXARG];
	char	abuf[MUDBUFSIZ];
	long	ploc;
	int	fmode;


	if(fnamb == (char *)0) {
		fnamb = malloc(MAXIDENTLEN * 2);
		if(fnamb ==(char *)0)
			fatal("player_in: cannot allocate name buffer\n",0);
	}

	/* skip white-boy space */
	while(*b != '\0' && (isspace(*b) || !isprint(*b)))
		b++;

	/* handle this broken up into lines. */
	while(*b != '\0') {
		int	huh = 1;

		/* slide a pointer ahead and chop into lines */
		while(*p && *p != '\n' && *p != '\r')
			p++;

		while(*p == '\n' || *p == '\r')
			*p++ = '\0';


		/*
		this is a special case - we hot-wire the function call
		to emulate the tiny* say and emote syntax.
		we just hand-assemble our argv and argc, without a call
		to enargv().
		*/
		if(*b == '\"' || *b == ':' || *b == '>') {
			if(*b == '\"') {
				av[0] = "say";
#ifdef	MONITORDEBUG
				printf("hot-wired call to @.say();\n");
#endif
			} else
			if(*b == ':') {
				av[0] = "emote";
#ifdef	MONITORDEBUG
				printf("hot-wired call to @.emote();\n");
#endif
			} else
			if(*b == '>') {
				av[0] = "go";
#ifdef	MONITORDEBUG
				printf("hot-wired call to @.go();\n");
#endif
			}

			/* skip the hot-wire char */
			b++;

			/* skip white-boy space */
			while(*b != '\0' && (isspace(*b) || !isprint(*b)))
				b++;

			/* finish patching up the arg vector. */
			if(*b != '\0') {
				av[1] = b;
				ac = 2;
			} else {
				av[1] = (char *)0;
				ac = 1;
			}

		} else { 
			ac = enargv(b,av,MUDMAXARG,abuf,sizeof(abuf));
		}

		if(ac <= 0 || strlen(av[0]) >= MAXIDENTLEN)
				goto dropthrough;


		/*
		you CAN take this out, if you really want.
		it is where we prohibit calls to functions starting
		with a leading underscore. (except for the Wiz)
		*/
		/* HARDCODE */
		if(av[0][0] == '_' && iob->euid != (long)0)
			goto dropthrough;


#ifdef	MONITORDEBUG
		for(dt = 0; dt < ac; dt++)
			printf("argv[%d]= %s\n",dt,av[dt]);
#endif

		/*
		searching for and calling functions in order. I
		coded this section with gotos because it's that
		much easier to move the blocks of stuff around
		to change what gets called from where.
		*/


		/* SEARCH system table for matching function name */
		/*
		call it ONLY if it has BLOCK perm set. we search the
		system table again later for the same func, without
		BLOCK set.
		*/
		ELENUM(fnamb) = (long)0;
		(void)strcpy(ELENAM(fnamb),av[0]);
		if(sys_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 && dt == TYP_FUNC) {
			fmode = d.p->p_mode;
			if(fmode & PERM_BLOCK) {
#ifdef	MONITORDEBUG
				printf("call to block sys func\n");
#endif
				call_with_argv(iob,0,ac,av,iob->uid,iob->euid);

				huh = 0;
				if(!(fmode & PERM_CHAIN))
					goto	dropthrough;
			}
		}



		/* SEARCH in the room we are in for bound functions */
		ELENUM(fnamb) = iob->uid;
		/* HARDCODE */
		(void)strcpy(ELENAM(fnamb),"_loc");
#ifdef	MONITORDEBUG
		printf("determine player location...\n");
#endif
		if(disk_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 && dt == TYP_OBJ) {

			/* wherever you go... */
			ploc = d.l;
#ifdef	MONITORDEBUG
			printf("player is at #%d\n",ploc);
#endif

			/* scan here for func */
			ELENUM(fnamb) = ploc;
			(void)strcpy(ELENAM(fnamb),av[0]);
			if(disk_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 &&
				dt == TYP_FUNC) {
				fmode = d.p->p_mode;
#ifdef	MONITORDEBUG
				printf("call to #%d.%s();\n",ploc,av[0]);
#endif
				call_with_argv(iob,ploc,ac,av,iob->uid,iob->euid);
				huh = 0;

				if(!(fmode & PERM_CHAIN))
					goto	dropthrough;
			}
		}



		/* SEARCH what the player is using */
		/* this section here goes on the assumption that,
		for performance reasons, we only want players to
		be using one object at a time. If not, you will
		need to adapt the code to call match() with the
		list of objects, then probe the matched object
		for the function.
		*/
		ELENUM(fnamb) = iob->uid;
		/* HARDCODE */
		(void)strcpy(ELENAM(fnamb),"_use");
#ifdef	MONITORDEBUG
		printf("determine what player is using...\n");
#endif
		if(disk_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 && dt == TYP_OBJ) {
#ifdef	MONITORDEBUG
			printf("player is using %d...\n",d.l);
#endif
			ELENUM(fnamb) = d.l;
			(void)strcpy(ELENAM(fnamb),av[0]);
			if(disk_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 &&
				dt == TYP_FUNC) {
				fmode = d.p->p_mode;
#ifdef	MONITORDEBUG
				printf("call to #%d.%s();\n",ELENUM(fnamb),av[0]);
#endif

				call_with_argv(iob,ELENUM(fnamb),ac,av,iob->uid,iob->euid);
				huh = 0;

				if(!(fmode & PERM_CHAIN))
					goto	dropthrough;
			}
		}



		/* SEARCH player for matching function name */
		ELENUM(fnamb) = (long)iob->uid;
		(void)strcpy(ELENAM(fnamb),av[0]);
		if(ELENUM(fnamb) != (long)0 &&
			disk_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 &&
			dt == TYP_FUNC) {

			fmode = d.p->p_mode;
#ifdef	MONITORDEBUG
			printf("call to player: %d.%s();\n",ELENUM(fnamb),av[0]);
#endif
			call_with_argv(iob,iob->uid,ac,av,iob->uid,iob->euid);
			huh = 0;

			if(!(fmode & PERM_CHAIN))
				goto	dropthrough;
		}



		/* SEARCH system table for matching function name */
		ELENUM(fnamb) = (long)0;
		(void)strcpy(ELENAM(fnamb),av[0]);
		if(sys_thaw(fnamb,&dt,&d,iob->uid,iob->euid) == 0 && dt == TYP_FUNC) {
			fmode = d.p->p_mode;

			/*
			if block was set before, we called it before
			so we don't call it again.
			*/
			if(!(fmode & PERM_BLOCK)) {
				call_with_argv(iob,0,ac,av,iob->uid,iob->euid);

				huh = 0;
				if(!(fmode & PERM_CHAIN))
					goto	dropthrough;
			}
		}


dropthrough:
		if(huh)
			iobput(iob,huhmsg,0);

		/* next line if one. */
		b = p;

		/* skip white (again) */
		while(*b != '\0' && (isspace(*b) || !isprint(*b)))
			b++;
	}
	return(0);
}