/*
 *	Euler - a numerical lab
 *
 *	platform : all
 *
 *	file : command.c -- builtin command handling
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <stdarg.h>

#include "command.h"
#include "builtin.h"
#include "express.h"
#include "input.h"
#include "meta.h"
#include "metaps.h"
#include "graphics.h"
#include "output.h"
#include "help.h"
#include "udf.h"
#include "mainloop.h"

#define EXTENSION ".e"
#define BOOKEXTENSION ".en"

int promptnotebook=1,booktype=0;
FILE *infile=0,*outfile=0;
char* path[32];
int npath=0;
static int printcomments=1;

static void load_file (void)
/***** load_file
	interpret a file.
*****/
{
	char filename[256];
	char oldline[1024],fn[256],*oldnext;
	int oldbooktype=booktype,pn;
	header *hd;
	FILE *oldinfile;
	
	if (udfon)
	{	output("Cannot load a file in a function!\n");
		error=221; return;
	}
	scan_space();
	if (*next=='(')
	{   hd=scan_value();
		if (error) return;
		if (hd->type!=s_string)
		{	output("String value expected!\n");
			error=1; return;
		}
		strcpy(filename,stringof(hd));
	}
	else
	{	scan_namemax(filename,256);
	}
	if (error) return;
	oldinfile=infile;
	pn=-1;
retry :
	if (pn>=0)
	{	strcpy(fn,path[pn]);
		strcat(fn,PATH_DELIM_STR);
		strcat(fn,filename);
	}
	else strcpy(fn,filename);
	infile=fopen(fn,"r");
	if (!infile)
	{   strcat(fn,EXTENSION);
		infile=fopen(fn,"r");
		pn++;
		if (!infile)
		{	if (pn>=npath)
			{	output1("Could not open %s!\n",filename);
				error=53; infile=oldinfile; return;
			}
			else goto retry;
		}
	}
	strcpy(oldline,input_line); oldnext=next;
	*input_line=0; next=input_line;
	booktype=0;
	while (!error && infile && !quit) command();
	booktype=oldbooktype;
	if (infile) fclose(infile);
	infile=oldinfile;
	strcpy(input_line,oldline); next=oldnext;
}

static void load_book (void)
/***** load_book
	interpret a notebook file.
*****/
{	header *hd;
	char name[256];
	char oldline[1024],fn[256],*oldnext;
	int oldbooktype=booktype;
	FILE *oldinfile;
	if (udfon)
	{	output("Cannot load a notebook in a function!\n");
		error=221; return;
	}
	 scan_space();
	if (*next=='(')
	{   hd=scan_value();
		if (error) return;
		if (hd->type!=s_string)
		{	output("String value expected!\n");
			error=1; return;
		}
		strcpy(name,stringof(hd));
	}
	else
	{	scan_namemax(name,256);
	}
	if (error) return;
	oldinfile=infile;
	infile=fopen(name,"r");
	if (!infile)
	{	strcpy(fn,name);
		strcat(fn,BOOKEXTENSION);
		infile=fopen(fn,"r");
		if (!infile)
		{	output1("Could not open %s!\n",stringof(name));
			error=53; infile=oldinfile; return;
		}
	}
	strcpy(oldline,input_line); oldnext=next;
	*input_line=0; next=input_line;
	booktype=1;
	while (!error && infile && !quit)
	{	startglobal=startlocal; endglobal=endlocal;
		command();
	}
	booktype=oldbooktype;
	if (infile) fclose(infile);
	infile=oldinfile;
	strcpy(input_line,oldline); next=oldnext;
}


static void do_clg (void)
{	graphic_mode(); gclear(); gflush();
}

static void do_cls (void)
{	text_mode(); clear_screen();
}

void clear (void)
/***** clear
	clears the stack and remove all variables and functions.
*****/
{	char name[32];
	scan_space();
	if (*next==';' || *next==',' || *next==0)
	{	endlocal=startlocal;
	}
	else
	while(1)
	{	scan_name(name); if (error) return;
		kill_local(name);
		scan_space();
		if (*next==',') { next++; continue; }
		else break;
	}
}

static void do_clear (void)
{	if (udfon)
	{	output("Cannot clear in a function!\n");
		error=120; return;
	}
	clear();
}

static void do_quit (void)
{	quit=1;
}

static void do_exec (void)
{	header *name;
	char *s;
	name=scan_value(); if (error) return;
	if (name->type!=s_string)
	{	output("Cannot execute a number or matrix!\n");
		error=130; return;
	}
	s=stringof(name);
	while (*s && !isspace(*s)) s++;
	if (*s) *s++=0;
	if (execute(stringof(name),s))
	{	output("Execution failed or program returned a failure!\n");
		error=131;
	}
}

static void do_forget (void)
{	char name[16];
	header *hd;
	int r;
	if (udfon)
	{	output("Cannot forget functions in a function!\n");
		error=720; return;
	}
	while (1)
	{	scan_space();
		scan_name(name);
		r=xor(name);
		hd=(header *)ramstart;
		while ((char *)hd<udfend)
		{	if (r==hd->xor && !strcmp(hd->name,name)) break;
			hd=nextof(hd);
		}
		if ((char *)hd>=udfend)
		{	output1("Function %s not found!\n",name);
			error=160; return;
		}
		kill_udf(name);
		scan_space();
		if (*next!=',') break;
		else next++;
	}
}


extern int builtin_count;
extern builtintyp builtin_list[];
extern int command_count;
extern commandtyp command_list[];

static void do_list (void)
{
	header *hd;
	int i, c, cend, lw=linelength/16;
	
	output("  *** Builtin functions:\n");
	for (i=0; i<builtin_count; i+=lw) {
		cend = i+lw;
		if (cend>=builtin_count) cend=builtin_count;
		for (c=i; c<cend ; c++) {
			output1("%-16s",builtin_list[c].name);
			if (test_key()==escape) return;
		}
		output("\n");
	}
	output("\n  *** Commands:\n");
	for (i=0; i<command_count; i+=lw) {
		cend = i+lw;
		if (cend>=command_count) cend=command_count;
		for (c=i; c<cend ; c++) {
			output1("%-16s",command_list[c].name);
			if (test_key()==escape) return;
		}
		output("\n");
	}
	output("\n  *** Your functions:\n");
	hd=(header *)ramstart;i=0;
	while ((char *)hd<udfend)
	{	if (hd->type!=s_udf) break;
		if (i>=lw) {
			i=0;
			output("\n");
		}
		output1("%-16s",hd->name);
		if (test_key()==escape) return;
		hd=nextof(hd);
		i++;
	}
	output("\n");
}

static void listvar1 (char *s, header *hd)
{	output1("%-20sType: %s\n",hd->name,s);
}

static void listvar2 (char *s, header *hd)
{	output1("%-20sType: %s (%dx%d)\n",hd->name,s,dimsof(hd)->r,dimsof(hd)->c);
}

static void listvar3 (char *s, header *hd)
{	output1("%-20sType: %s (%dx%0d)",hd->name,s,
		submdimsof(hd)->r,submdimsof(hd)->c);
}

static void do_listvar (void)
{	header *hd=(header *)startlocal;
	while (hd<(header *)endlocal)
	{	switch (hd->type)
		{	case s_real : listvar1("Real",hd); break;
			case s_interval : listvar1("Interval",hd); break;
			case s_complex : listvar1("Complex",hd); break;
			case s_string : listvar1("String",hd); break;
			case s_matrix : listvar2("Real Matrix",hd); break;
			case s_cmatrix : listvar2("Complex Matrix",hd); break;
			case s_imatrix : listvar2("Interval Matrix",hd); break;
			case s_reference : listvar1("Reference",hd); break;
			case s_submatrix : listvar3("Real Submatrix",hd); break;
			case s_isubmatrix : listvar3("Interval Submatrix",hd); break;
			case s_csubmatrix : listvar3("Complex Submatrix",hd); break;
			default: listvar1("Unknown Type",hd); break;
		}
		hd=nextof(hd);
		if (test_key()==escape) break;
	}
}

static void do_dump (void)
{	header *file;
	if (outfile)
	{	if (fclose(outfile))
		{	output("Error while closing dumpfile.\n");
		}
		outfile=0;
	}
	scan_space();
	if (*next==';' || *next==',' || *next==0)
	{	if (*next) next++; return; }
	file=scan_value();
	if (error || file->type!=s_string)
	{	output("Dump needs a filename!\n");
		error=201; return;
	}
	outfile=fopen(stringof(file),"a");
	if (!outfile)
	{	output1("Could not open %s.\n",stringof(file));
	}
}


static void do_dir (void)
{
	header *file;
	int len, npl, i, j, k, imax;
	char **entries=NULL;
	int n_entries=0;
	
	scan_space();
	if (*next==';' || *next==',' || *next==0)
	{
		file=new_string("*",5,"");
	}
	else file=scan_value();
	if (error || file->type!=s_string)
	{	output("Dir needs a string!\n");
		error=201; return;
	}
	
	len = scan_dir(".",stringof(file),&entries,&n_entries);
	len +=2;
	npl = linelength/len;
	imax = n_entries/npl;
	if (n_entries % npl) imax++;
	for (i=0; i<imax;i++) {
		for (j=0; j<npl; j++) {
			if (outputing) {
				int l;
				if (npl*i+j>=n_entries) break;
				output1("%s", entries[npl*i+j]);
				l=strlen(entries[npl*i+j]);
				for (k=0;k<len-l;k++)
					output(" ");
				if (test_key()==escape) outputing=0;
			}
			free(entries[npl*i+j]);
		}
		output("\n");
	}
	free(entries);
	if (!outputing) {
		outputing=1;
		output("\n");
	}
	
	if (*next==',' || *next==';') next++;
}

static void do_path (void)
{
	header *ppath;
	char s[256],*p,*q;
	int i;
	
	scan_space();
	if (*next==';' || *next==',' || *next==0)
	{   out :
		for (i=0; i<npath; i++)
		{	output1("%s;",path[i]);
		}
		output("\n");
		return;
	}
	ppath=scan_value();
	if (error || ppath->type!=s_string)
	{	output("Path needs a string!\n");
		error=201; return;
	}
	p=stringof(ppath);
	for (i=2; i<npath; i++) free(path[i]);
	npath=2;
	while (*p)
	{	q=s;
		while (*p && *p!=';') *q++=*p++;
		if (q>s && *(q-1)==PATH_DELIM_CHAR) q--;
		*q=0;
		if (*p==';') p++;
		path[npath]=(char *)malloc(strlen(s)+1);
		strcpy(path[npath],s);
		npath++;
	}
	if (*next!=';') goto out;
}

static void do_cd (void)
{	header *hd;
	char name[256];
	char *s;
	scan_space();
	if (*next==';' || *next==',' || *next==0)
	{	s=cd("");
		output1("%s\n",s);
		return;
	}
	if (*next=='(')
	{   hd=scan_value();
		if (error) return;
		if (hd->type!=s_string)
		{	output("String value expected!\n");
			error=1; return;
		}
		strcpy(name,stringof(hd));
	}
	else
	{	scan_namemax(name,256);
	}
	if (error) return;
	s=cd(name);
	if (*next!=';') output1("%s\n",s);
	if (*next==',' || *next==';') next++;
}

static void do_meta (void)
{	header *file;

	scan_space();
	file=scan_value();
	if (error || file->type!=s_string)
	{	output("Meta needs a filename!\n");
		error=201; return;
	}
	if (!dump_meta(stringof(file)))
	{	output1("Could not open %s.\n",stringof(file));
	}
}

static void do_postscript (void)
{
	header *file;

	scan_space();
	file=scan_value();
	if (error || file->type!=s_string)
	{	output("postscript needs a filename!\n");
		error=201; return;
	}
	if (!dump_postscript(stringof(file)))
		output1("Could not open %s.\n",stringof(file));
}

static void do_remove (void)
{	header *hd;
	char name[256];
	if (*next=='(')
	{   hd=scan_value();
		if (error) return;
		if (hd->type!=s_string)
		{	output("String value expected!\n");
			error=1; return;
		}
		strcpy(name,stringof(hd));
	}
	else
	{	scan_namemax(name,256);
	}
	if (error) return;
	remove(name);
}

static void do_do (void)
{	int udfold;
	char name[16];
	char *oldnext=next,*udflineold;
	header *var;
	scan_space(); scan_name(name); if (error) return;
	var=searchudf(name);
	if (!var || var->type!=s_udf)
	{	output("Need a udf!\n"); error=220; return;
	}
	udflineold=udfline; udfline=next=udfof(var); udfold=udfon; udfon=1;
	while (!error && udfon==1)
	{	command();
		if (udfon==2) break;
		if (test_key()==escape) 
		{	output("User interrupted!\n"); error=58; break;
		}
	}
	if (error) output1("Error in function %s\n",var->name);
	if (udfon==0)
	{	output1("Return missing in %s!\n",var->name); error=55; }
	udfon=udfold; udfline=udflineold;
	if (udfon) next=oldnext;
	else { next=input_line; *next=0; }
}

static void do_mdump (void)
{	header *hd;
#ifndef SPLIT_MEM
	output1("ramstart : 0\nstartlocal : %ld\n",startlocal-ramstart);
	output1("endlocal : %ld\n",endlocal-ramstart);
	output1("newram   : %ld\n",newram-ramstart);
	output1("ramend   : %ld\n",ramend-ramstart);
#else
	output1("ramstart : 0\nstartlocal : %ld\n",startlocal-varstart);
	output1("endlocal : %ld\n",endlocal-varstart);
	output1("newram   : %ld\n",newram-varstart);
	output1("ramend   : %ld\n",ramend-varstart);
#endif
	hd=(header *)ramstart;
#ifdef SPLIT_MEM
	while ((char *)hd<udfend)
	{
		output1("%6ld : %16s, ",(char *)hd-ramstart,hd->name);
		output1("size %6ld ",(long)hd->size);
		output1("type %d\n",hd->type);
		hd=nextof(hd);
	}
	hd=(header *)varstart;
#endif
	while ((char *)hd<newram)
	{
#ifndef SPLIT_MEM
		output1("%6ld : %16s, ",(char *)hd-ramstart,hd->name);
#else
		output1("%6ld : %16s, ",(char *)hd-varstart,hd->name);
#endif
		output1("size %6ld ",(long)hd->size);
		output1("type %d\n",hd->type);
		hd=nextof(hd);
	}
}

static void hex_out1 (int n)
{	if (n<10) output1("%c",n+'0');
	else output1("%c",n-10+'A');
}

static void hex_out (unsigned int n)
{	hex_out1(n/16);
	hex_out1(n%16);
	output(" ");
}

static void string_out (unsigned char *p)
{	int i;
	unsigned char a;
	for (i=0; i<16; i++) 
	{	a=*p++;
		output1("%c",(a<' ')?'_':a);
	}
}

static void do_hexdump (void)
{	char name[16];
	unsigned char *p,*end;
	int i=0,j;
	unsigned long count=0;
	header *hd;
	scan_space(); scan_name(name); if (error) return;
	hd=searchvar(name);
	if (!hd) hd=searchudf(name);
	if (error || hd==0) return;
	p=(unsigned char *)hd; end=p+hd->size;
	output1("\n%5lx ",count);
	while (p<end)
	{	hex_out(*p++); i++; count++;
		if (i>=16) 
		{	i=0; string_out(p-16);
			output1("\n%5lx ",count);
			if (test_key()==escape) break;
		}
	}
	for (j=i; j<16; j++) output("   ");
	string_out(p-i);
	output("\n");
}

void do_help (void)
{	char name[256];
	header *hd;
	int count,i,defaults;
	char *p,*end,*pnote;
	builtintyp *b;
	scan_space();
	p=name;
	while (*next!=0 && *next!=' ')
	{	*p++=*next++;
    	if (p-name>254) break;
	}
	*p=0;
	if (!*name) strcpy(name,"help");
	b=find_builtin(name);
	if (b)
	{   if (b->nargs>=0)
		{	output1(
				"%s is a builtin function with %d argument(s).\n"
				,name,b->nargs);
		}
		else
			output1(
				"%s is a builtin function.\n"
				,name);
	}
	hd=searchudf(name);
	if (hd && hd->type==s_udf)
	{   if (b) output1("%s is also a user defined function.\n",name);
		output1("function %s (",name);
		end=udfof(hd);
		p=helpof(hd);
		memmove(&count,p,sizeof(inttyp));
		p+=sizeof(inttyp);
		pnote=p;
		for (i=0; i<count; i++)
		{	memmove(&defaults,p,sizeof(inttyp)); p+=sizeof(inttyp);
			output1("%s",p);
			p+=16+sizeof(inttyp);
			if (defaults)
			{	output("=...");
				p=(char *)nextof((header *)p);
			}
			if (i!=count-1) output(",");
		}
		output(")\n");
		p=pnote;
		for (i=0; i<count; i++)
		{	memmove(&defaults,p,sizeof(inttyp)); p+=sizeof(inttyp);
			if (defaults) output1("## Default for %s :\n",p);
			p+=16+sizeof(inttyp);
			if (defaults)
			{	give_out((header *)p);
				p=(char *)nextof((header *)p);
			}
		}
		while (*p!=1 && p<end)
		{	output(p); output("\n");
			p+=strlen(p); p++;
		}
	}
    externhelp(name);
}

static void do_output (void)
/**** do_output
	toggles output.
****/
{	scan_space();
	if (!strncmp(next,"off",3))
	{	outputing=0; next+=3;
	}
	else if (!strncmp(next,"on",2))
	{	outputing=1; output("\n"); next+=2;
	}
	else outputing=!outputing;
}

static void do_prompt (void)
/**** do_prompt
	toggles notebook prompt.
****/
{	scan_space();
	if (!strncmp(next,"off",3))
	{   promptnotebook=0; next+=3;
	}
	else if (!strncmp(next,"on",2))
	{	promptnotebook=1; output("\n"); next+=2;
	}
	else promptnotebook=!promptnotebook;
}

void do_comments (void)
/**** do_comments
	toggles comments
****/
{	scan_space();
	if (!strncmp(next,"off",3))
	{	printcomments=0; next+=3;
	}
	else if (!strncmp(next,"on",2))
	{	printcomments=1; output("\n"); next+=2;
	}
	else printcomments=!printcomments;
}

static void do_comment (void)
{	FILE *fp=infile;
	if (!fp || udfon)
	{	output("comment illegal at this place\n");
		error=1001; return;
	}
	while (1)
	{	next_line();
		if (infile!=fp)
		{	output("endcomment missing!\n"); error=1002;
			return;
		}
		if (strncmp(next,"endcomment",10)!=0)
		{	if (printcomments)
			{	output(input_line); output("\n");
			}
		}
		else break;
	}
	next_line();
}


static int command_count;

commandtyp command_list[] = {
	{"quit",c_quit,do_quit},
	{"hold",c_hold,ghold},
	{"shg",c_shg,show_graphics},
	{"load",c_load,load_file},
	{"function",c_udf,get_udf},
	{"return",c_return,do_return},
	{"for",c_for,do_for},
	{"endif",c_endif,do_endif},
	{"end",c_end,do_end},
	{"break",c_break,do_break},
	{"loop",c_loop,do_loop},
	{"else",c_else,do_else},
	{"elseif",c_elseif,do_elseif},
	{"if",c_if,do_if},
	{"repeat",c_repeat,do_repeat},
	{"clear",c_clear,do_clear},
	{"clg",c_clg,do_clg},
	{"cls",c_cls,do_cls},
	{"exec",c_exec,do_exec},
	{"forget",c_forget,do_forget},
	{"global",c_global,do_global},
	{"useglobal",c_global,do_useglobal},
	{"list",c_global,do_list},
	{"listvar",c_global,do_listvar},
	{"type",c_global,do_type},
	{"dump",c_global,do_dump},
	{"remove",c_global,do_remove},
	{"help",c_global,do_help},
	{"do",c_global,do_do},
	{"memorydump",c_global,do_mdump},
	{"hexdump",c_global,do_hexdump},
	{"output",c_global,do_output},
	{"comments",c_global,do_comments},
	{"meta",c_global,do_meta},
	{"postscript",c_global,do_postscript},
	{"comment",c_global,do_comment},
	{"trace",c_global,do_trace},
	{"notebook",c_global,load_book},
	{"prompt",c_global,do_prompt},
	{"cd",c_global,do_cd},
	{"dir",c_global,do_dir},
	{"path",c_global,do_path},
	{0,c_none,0}
};

int command_compare (const commandtyp *p1, const commandtyp *p2)
{	return strcmp(p1->name,p2->name);
}

void sort_command (void)
{	command_count=0;
	while (command_list[command_count].name) command_count++;
	qsort(command_list,command_count,sizeof(commandtyp),
		(int (*)(const void *, const void *))command_compare);
}

commandtyp *preview_command (unsigned long *l)
{	commandtyp h;
	char name[16],*a,*n;
	*l=0;
	a=next; n=name;
	while (*l<15 && isalpha(*a)) { *n++=*a++; *l+=1; }
	*n=0; if (isalpha(*a)) return 0;
	h.name=name;
	return (commandtyp *)bsearch(&h,command_list,command_count,sizeof(commandtyp),
		(int (*)(const void *, const void *))command_compare);
}
