#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sial_api.h>
#include <lcrash.h>

/*
	for the moment I hardcode some variables here 
	This needs to be changed when we have a way to grab this
	from the image. I have'nt done it yet. 
	- get the size of type caddr_t
	- get the cpuinfo cpu model to set the ABI.
*/
stab_type_t *find_type(uint64_t);
st_sym_t *find_st_sym(char *, int, uint64_t);

char * kl_getproducer(void) { return ""; }

/*
	This is the glue between the sial interpreter and icrash.
*/

static int
apigetmem(ull iaddr, void *p, int nbytes)
{
	if(get_block(iaddr, nbytes, p, 0)) return 0;
	return 1;
}

static int
apiputmem(ull iaddr, void *p, int nbytes)
{
	return 1;
}

/* extract a complex type (struct, union and enum) */
static int
apigetctype(int ctype, char *name, TYPE_S *tout)
{
int kltype, stype;
st_sym_t *st;

	if(sial_is_struct(ctype)) { stype=ST_TYPE; kltype=KLT_STRUCT; }
	else if(sial_is_union(ctype))  { stype=ST_TYPE; kltype=KLT_UNION; }
	else if(sial_is_enum(ctype))   { stype=ST_TYPE; kltype=KLT_ENUMERATION; }
	else if(sial_is_typedef(ctype)){ stype=ST_TYPEDEF; kltype=KLT_TYPEDEF; }
	else sial_error("Oops getctype");

	if((st = find_st_sym(name, stype, 0))) {

		stab_type_t *sp=(stab_type_t *)st->sym_kltype;

		if(sp->st_type == kltype) {

			/* populate */
			sial_type_settype(tout, ctype);
			sial_type_setsize(tout, sp->st_size);
			sial_type_setidx(tout, st->sym_typenum);
			sial_pushref(tout, 0);
			return 1;
		}
	}
	return 0;
}

/* set idx value to actual array indexes from specified size */
static void
sial_setupidx(TYPE_S*t, int ref, int nidx, int *idxlst)
{
        /* put the idxlst in index size format */
        if(nidx) {

                int i;

                for(i=0;i<nidx-1;i++) idxlst[i]=idxlst[i]/idxlst[i+1];

                /* divide by element size for last element bound */
                if(ref) idxlst[i] /= sial_defbsize();
                else idxlst[i] /= sial_type_getsize(t);
                sial_type_setidxlst(t, idxlst);
        }
}
/*
	This function needs to drill down a typedef and
	return the corresponding type.
	If the typedef is from a basetype sial_parsetype() will be
	called back to build the type 
*/
static char *
drilldowntype(kltype_t *kltp, TYPE_S *t)
{
char *tstr=0;
int fctflg=0, ref=0;
int *idxlst=0;
int nidx=0;

	while(kltp)
	{

		switch(kltp->kl_type) {

		case KLT_MEMBER:

			kltp=kltp->kl_realtype;
		break;

		/* typedef inserts a level of reference to the actual type */
		case KLT_POINTER:
	
			ref++;
			/* this could be a void*, in which case the drill down stops here */
			if(!kltp->kl_realtype) {

				/* make it a char* */
				sial_parsetype("char", t, ref);
				return 0;

			}
			kltp=kltp->kl_realtype;
		break;

		/* handle pointer to functions */
		case KLT_FUNCTION:

			fctflg=1;
			kltp=kltp->kl_realtype;
		break;

		/* Is this an array ? if so then just skip this type info and
		   we only need information on the elements themselves */
		case KLT_ARRAY:

                        if(!idxlst) idxlst=sial_calloc(sizeof(int)*(MAXIDX+1));
                        if(nidx >= MAXIDX) sial_error("Too many indexes! max=%d\n", MAXIDX);
                        idxlst[nidx++]=kltp->kl_high_bounds;
			kltp=kltp->kl_elementtype;
		break;

		/* typedef points to a typedef itself */
		case KLT_TYPEDEF:

			kltp=kltp->kl_realtype;
		break;

		case KLT_BASE:

			sial_parsetype(kltp->kl_name, t, 0);
			tstr=kltp->kl_typestr;
			kltp=0;
		break;

		case KLT_UNION: 
			sial_type_mkunion(t);
			goto label;

		case KLT_ENUMERATION:
			sial_type_mkenum(t);
			goto label;

		case KLT_STRUCT:
		{
		stab_type_t *sp=(stab_type_t *)kltp;

			sial_type_mkstruct(t);

label:
			if(kltp->kl_typestr) sial_parsetype(kltp->kl_typestr, t, 0);
			else {
				sial_type_setsize(t, kl_type_size(kltp));
				sial_type_setidx(t, sp->st_type_num);
				tstr=kltp->kl_typestr;
			}
			kltp=0;
		}
		break;

		/* we don;t have all the info about it */
		case KLT_INCOMPLETE:
		case KLT_UNKNOWN:
		case 100:
			sial_parsetype("int", t, 0);
			kltp=0;
		break;


		default: 
			sial_error("Oops drilldowntype");
		break;
		}


	}
	sial_setupidx(t, ref, nidx, idxlst);
	if(fctflg) sial_type_setfct(t, 1);
	sial_pushref(t, ref+(nidx?1:0));
	if(tstr) return sial_strdup(tstr);
	return sial_strdup("");
}

static char *
apigetrtype(ull idx, TYPE_S *t)
{
stab_type_t *stp;
kltype_t *kltp;

	if(!(stp = find_type(idx))) return 0;
	kltp=stp->st_realtype;
	kltp=kltp->kl_realtype;
	return drilldowntype(kltp, t);
}


/* 
	Get the type, size and position information for a member of a structure.
*/
static char*
apimember(char *mname,  ull tnum, TYPE_S *tm, MEMBER_S *m, ull *lnum)
{
stab_type_t *sp, *mp;
#define LASTNUM (*lnum)

	/* if we're being asked the next member in a getfirst/getnext sequence */
	if(mname && !mname[0] && LASTNUM) {

		sp = (stab_type_t *)(unsigned long)LASTNUM;

	} else {

		sp = find_type(tnum);
		if (sp->st_type == KLT_TYPEDEF) {
			if (!(sp = (stab_type_t *)sp->st_realtype)) {
				return 0;
			}
		}
		if ((sp->st_type != KLT_STRUCT) && (sp->st_type != KLT_UNION)) {
			return 0;
		}
	}
	if (!sp) return 0;
	mp = (stab_type_t *)sp->st_member;
	while(mp) {

		if (!mname || !mname[0] || !strcmp(mp->st_name, mname)) {

			sial_member_soffset(m, mp->st_offset);
			sial_member_ssize(m, mp->st_size);

			if(mp->st_size*8 == mp->st_bit_size) 

				sial_member_snbits(m, 0);
			else
				sial_member_snbits(m, mp->st_bit_size);

			sial_member_sfbit(m, mp->st_klt.kl_bit_offset);
			sial_member_sname(m, mp->st_klt.kl_name?mp->st_klt.kl_name:"");
if(!strcmp(mp->st_klt.kl_name?mp->st_klt.kl_name:"", "comm")) {

	LASTNUM=LASTNUM;
}
			LASTNUM=(unsigned long)mp;
			return drilldowntype(&mp->st_klt, tm);
		}
		mp = (stab_type_t *)mp->st_member;
	}
	return 0;
}

/*
	This function gets the proper allignment value for a type.
*/
static int
apialignment(ull idx)
{
stab_type_t *sp;
kltype_t *kltp;

	if(!(sp=find_type(idx))) {

		sial_error("apialignment: type not found %lld", idx);
	}

	kltp=&sp->st_klt;

	while(1)
	{
		switch(kltp->kl_type) {

		/* Is this an array ? if so then just skip this type info and
		   we only need information on the elements themselves */
		/* typedef points to a typedef itself */
		case KLT_TYPEDEF:
		case KLT_ARRAY:
		case KLT_MEMBER:

			kltp=kltp->kl_realtype;
		break;

		case KLT_STRUCT:
		case KLT_UNION:
		{
			int max=0, cur;
			stab_type_t *mp;

			mp = (stab_type_t *)sp->st_member;
			while(mp) {
				cur=apialignment(mp->st_type_num);
				if(cur > max) max=cur;
				mp = (stab_type_t *)mp->st_member;
			}
			return max;
		}

		case KLT_POINTER:

			/* size should have been already set by us */
			return sial_defbsize();

		case KLT_ENUMERATION:
		case KLT_BASE:

			return kltp->kl_size;

		default:

			sial_error("Oops apialignment");
		}
	}
}

/* get the value of a symbol */
static int
apigetval(char *name, ull *val)
{
syment_t *sym;

	sym=kl_lkup_symname(name);
	if(sym) {

		*val=(ull)sym->s_addr;
		return 1;
	}
	return 0;
}

/*
	Get the list of enum symbols.
*/
ENUM_S*
apigetenum(char *name)
{
st_sym_t *st;

	if((st=find_st_sym(name, ST_TYPE, 0))) {

		stab_type_t *sp=(stab_type_t *)st->sym_kltype;

		if(sp->st_type == KLT_ENUMERATION) {

			ENUM_S *et=0;

			while(sp->st_member) {

				et=sial_add_enum(et, sial_strdup(sp->st_name), sp->st_value);
				sp = (stab_type_t *)sp->st_member;
			}
        		return et;
		}
        }

	return 0;
}

/*
	Return the list of preprocessor defines.
	For Irix we have to get the die for a startup.c file.
	Of dwarf type DW_TAG_compile_unit.
	the DW_AT_producer will contain the compile line.

	We then need to parse that line to get all the -Dname[=value]
*/
DEF_S *
apigetdefs(void)
{
DEF_S *dt=0;
char *prod, *p, rbuf[40];
int i;
struct linuxdefs_s {

	char *name;
	char *value;

} linuxdefs[] = {

	{"linux",		"1"},
	{"__linux",		"1"},
	{"__linux__",		"1"},
	{"unix",		"1"},
	{"__unix",		"1"},
	{"__unix__",		"1"},
	// helper macros
	{"LINUX_2_2_16",	"(LINUX_RELEASE==0x020210)"},
	{"LINUX_2_2_17",	"(LINUX_RELEASE==0x020211)"},
	{"LINUX_2_4_0",		"(LINUX_RELEASE==0x020400)"},
	{"LINUX_2_2_X",		"(((LINUX_RELEASE) & 0xffff00) == 0x020200)"},
	{"LINUX_2_4_X",		"(((LINUX_RELEASE) & 0xffff00) == 0x020400)"},
};

struct archdefs_s {

	int arch;
	struct linuxdefs_s def;

} archdefs[] = {

	{ARCH_I386, {"i386",		"1"}},
	{ARCH_I386, {"__i386",		"1"}},
	{ARCH_I386, {"__i386__",	"1"}},

	{ARCH_IA64, {"ia64",		"1"}},
	{ARCH_IA64, {"__ia64",		"1"}},
	{ARCH_IA64, {"__ia64__",	"1"}},
	{ARCH_IA64, {"__LP64__",	"1"}},
	{ARCH_IA64, {"_LONGLONG",	"1"}},
	{ARCH_IA64, {"__LONG_MAX__",	"9223372036854775807L"}},
};
	
	prod=sial_strdup(kl_getproducer());
	for(p=prod; *p; p++) {

		if(*p=='-' && *(p+1)=='D') {

			char *def=p+2;

			while(*p && *p != '=' && *p != ' ') p++;

			if(!*p || *p == ' ') {

				*p='\0';
				dt=sial_add_def(dt, sial_strdup(def), sial_strdup("1"));

			} else {

				char *val=p+1;

				*p++='\0';
				while(*p && *p != ' ') p++;
				*p='\0';

				dt=sial_add_def(dt, sial_strdup(def), sial_strdup(val));
			}
		}
	}
	
	/* insert constant defines from list above */
	for(i=0;i<sizeof(linuxdefs)/sizeof(linuxdefs[0]);i++) {

		dt=sial_add_def(dt, sial_strdup(linuxdefs[i].name), 
			sial_strdup(linuxdefs[i].value));
	}

	sprintf(rbuf, "0x%08x", KL_LINUX_RELEASE);
	dt=sial_add_def(dt, sial_strdup("LINUX_RELEASE"), sial_strdup(rbuf));

	/* push arch specific macros */
	for(i=0;i<(sizeof(archdefs)/sizeof(archdefs[0])); i++) {

		if(archdefs[i].arch==KL_ARCH) {

			dt=sial_add_def(dt, sial_strdup(archdefs[i].def.name), 
				 sial_strdup(archdefs[i].def.value));
		}
	}

	return dt;
}

apiops icops= {
	apigetmem, 
	apiputmem, 
	apimember, 
	apigetctype, 
	apigetrtype, 
	apialignment, 
	apigetval, 
	apigetenum, 
	apigetdefs
};

void
sial_version(void)
{
	sial_msg("< Sial interpreter version %d.%d [%s, %s] >\n"
		, S_MAJOR, S_MINOR, __DATE__, __TIME__);
}

static char *
get_ustr(command_t *cmd)
{
char *ustr;
char fname[MAX_SYMNAMELEN+sizeof("_usage")+1];

	snprintf(fname, sizeof(fname), "%s_usage", cmd->command);
	ustr=(char*)(unsigned long)sial_exefunc(fname, 0);
	return ustr;
}

static void
help_callback(command_t *cmd)
{
char fname[MAX_SYMNAMELEN+sizeof("_help")+1];
char *hstr, *ustr=get_ustr(cmd);

	snprintf(fname, sizeof(fname), "%s_help", cmd->command);
	hstr=(char*)(unsigned long)sial_exefunc(fname, 0);
	if(!hstr) sial_msg("NULL help string from function '%s'\n", fname);
	else { CMD_HELP(cmd, ustr, hstr); }
}

static void
usage_callback(command_t *cmd)
{
char *ustr=get_ustr(cmd);

	if(!ustr) sial_msg("NULL usage string for function '%s'\n", cmd->command);
	else CMD_USAGE(cmd, ustr);
}

static int
parse_callback(command_t *cmd)
{
	if (set_cmd_flags(cmd, (C_NO_OPCHECK + C_WRITE), 0)) {
		return(1);
	}
	return 0;
}

static int
run_callback(command_t *cmd)
{
char **myargv=sial_alloc(sizeof(char*)*(cmd->nargs+1));
int i, ret;
void *f;
	
	// we most create a argv[0] and argc most be at least 1
	// argv[0] becomes the command name

	myargv[0]=sial_strdup(cmd->command);
	for(i=0;i< cmd->nargs;i++) myargv[i+1]=cmd->args[i];

	// switch to lcrash output stream
	f=sial_getofile();
	sial_setofile(cmd->ofp);

	ret=sial_cmd(cmd->command, myargv, cmd->nargs+1);

	// go back to previous stream
	sial_setofile(f);

	return ret;
}

/*
	This function is called for every new function
	generated by a load command. This enables us to
	register new commands.

	We check here is the functions:

	fname_help()
	fname_opt()
	and
	fname_usage()

	exist, and if so then we have a new command.
	Then we associated (register) a function with
	the standard sial callbacks.
*/
void
reg_callback(char *name)
{
char fname[MAX_SYMNAMELEN+sizeof("_usage")+1];
_command_t cmds[2];

	snprintf(fname, sizeof(fname), "%s_help", name);
	if(!sial_chkfname(fname, 0)) return;
	snprintf(fname, sizeof(fname), "%s_usage", name);
	if(!sial_chkfname(fname, 0)) return;

	cmds[0].cmd=strdup(name);
	cmds[0].real_cmd=0;
	cmds[0].cmdfunc=run_callback;
	cmds[0].cmdparse=parse_callback;
	cmds[0].cmdusage=usage_callback;
	cmds[0].cmdhelp=help_callback;
	cmds[1].cmd=0;
	unregister_cmd(cmds[0].cmd);
	(void)register_cmds(cmds);
}

#define _VI_USAGE "function_name | -f sial_file_name"

/*
 * vi_usage() -- vi sial macros
 */
void
vi_usage(command_t *cmd)
{
	CMD_USAGE(cmd, _VI_USAGE);
}

/*
 * vi_cmd()
 */
int
vi_cmd(command_t *cmd)
{
	if(cmd->flags&C_LIST) sial_vilast();
	else {

		if(cmd->args[0]) sial_vi(cmd->args[0], cmd->flags&C_FULL);
		else vi_usage(cmd);
	}
	return(0);
}

/*
 * vi_help() -- Print the help information for the 'vi' command.
 */
void
vi_help(command_t *cmd)
{
	CMD_HELP(cmd, _VI_USAGE,
		"Start a vi session of a sial file or a sial function in particular.");
}

/*
 * vi_parse() -- Parse the command line arguments for 'vi'.
 */
int
vi_parse(command_t *cmd)
{
	if (set_cmd_flags(cmd, (C_FULL|C_LIST), 0)) {
		return(1);
	}
	return 0;
}

#define _LOAD_USAGE "filename|directory"

/*
 * load_usage() -- load sial macros
 */
void
load_usage(command_t *cmd)
{
	CMD_USAGE(cmd, _LOAD_USAGE);
}

/*
 * load_cmd()
 */
int
load_cmd(command_t *cmd)
{
	if(lcrash_debug) {

		sialdebug=1;
		sialppdebug=1;

	} else {

		sialdebug=0;
		sialppdebug=0;
	}
	if(!cmd->args[0]) load_usage(cmd);
	else sial_loadunload(1, cmd->args[0], 0);
	return(0);
}

/*
 * load_help() -- Print the help information for the 'load' command.
 */
void
load_help(command_t *cmd)
{
	CMD_HELP(cmd, _LOAD_USAGE,
		"Load a sial macro from a file or a directory. In the case of a directory, "
		"all files in that directory will be loaded.");
}

/*
 * load_parse() -- Parse the command line arguments for 'load'.
 */
int
load_parse(command_t *cmd)
{
	return(0);
}

#define _UNLOAD_USAGE "filename|directory"

/*
 * unload_usage() -- unload sial macros
 */
void
unload_usage(command_t *cmd)
{
	CMD_USAGE(cmd, _UNLOAD_USAGE);
}

/*
 * unload_cmd()
 */
int
unload_cmd(command_t *cmd)
{
	if(!cmd->args[0]) unload_usage(cmd);
	else sial_loadunload(0, cmd->args[0], 0);
	return(0);
}

/*
 * unload_help() -- Print the help information for the 'unload' command.
 */
void
unload_help(command_t *cmd)
{
	CMD_HELP(cmd, _UNLOAD_USAGE,
		"Unload a file or a directory. In the case of a directory, "
		"all files in that directory will be unloaded.");
}

/*
 * unload_parse() -- Parse the command line arguments for 'unload'.
 */
int
unload_parse(command_t *cmd)
{
	return 0;
}

static  _command_t  sial_cmds[] = {

	{"vi", 0, vi_cmd, vi_parse, vi_help, vi_usage},
	{"load", 0, load_cmd, load_parse, load_help, load_usage},
	{"unload", 0, unload_cmd, unload_parse, unload_help, unload_usage},
	{(char *)0 }
};

// some builtins
VALUE_S*
settask(VALUE_S* vtask, ...)
{
	return sial_makebtype(
		kl_set_deftask((kaddr_t)sial_getval(vtask))
	   );
}

void
init_sial(char *M_val, char *I_val)
{
#define LCDIR "/usr/share/sial/lcrash"
#define LCIDIR "include"
#define LCUDIR ".lcrash"


	if(sial_open() >= 0) {

		char *path, *ipath;
		char *homepath=0;
               	char *home=getenv("HOME");

		/* set api, default size, and default sign for types */
		sial_apiset(&icops, KL_ARCH == ARCH_I386 ? ABI_INTEL_X86 : ABI_INTEL_IA, KL_NBPW, 0);

		/* send a message to the user
		sial_msg("\n\n");
		sial_version(); */

        	/* set the macro search path */
        	if(!(path=M_val) && !(path=getenv("LC_MPATH"))) {

                	if(home) {

                        	path=sial_alloc(strlen(home)+sizeof(LCUDIR)+sizeof(LCDIR)+4);
				homepath=sial_alloc(strlen(home)+sizeof(LCUDIR)+2);

				/* build a path for call to sial_load() */
				strcpy(homepath, home);
				strcat(homepath, "/");
				strcat(homepath, LCUDIR);

				/* built the official path */
                        	strcpy(path, LCDIR);
                        	strcat(path, ":");
                        	strcat(path, home);
                        	strcat(path, "/");
				strcat(path, LCUDIR);
                	}
                	else path=LCDIR;
		}
		sial_setmpath(path);

		/* include path */
		if(!(ipath=I_val) && !(ipath=getenv("LC_IPATH"))) {

                	if(home) {

                        	ipath=sial_alloc(strlen(home)+sizeof(LCDIR)+sizeof(LCUDIR)+(sizeof(LCIDIR)*2)+(sizeof("/usr/include")+2)+6);

				/* built the official path */
                        	strcpy(ipath, LCDIR);
                        	strcat(ipath, "/"LCIDIR":");
                        	strcat(ipath, home);
                        	strcat(ipath, "/");
				strcat(ipath, LCUDIR);
				strcat(ipath, "/"LCIDIR);
				strcat(ipath, ":/usr/include");
                	}
                	else ipath=LCDIR"/"LCIDIR;
		}
		sial_setipath(ipath);

		/* set the new function callback */
		sial_setcallback(reg_callback);

		/* load the default macros */
		sial_loadall();

		/* our builtins */
        	sial_builtin("int settask(struct task_struct*)", settask);

		/* load some sial specific commands */
		register_cmds(sial_cmds);
	}
}

