/*	Copyright (C) 1990 Free Software Foundation, Inc.

This file is part of Oleo, the GNU Spreadsheet.

Oleo 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 1, or (at your option)
any later version.

Oleo 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 Oleo; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "funcdef.h"
#include "sysdef.h"

#include "global.h"
#include "eval.h"
#include "cell.h"

struct pr_node {
	int tightness;
	int len;
	char string[1];
};

char *backslash_a_string EXT2(char *, int);

static VOIDSTAR save_decomp;
static CELLREF decomp_row;
static CELLREF decomp_col;

/* These #defines are so that we don't have to duplicate code below */
#ifdef A0_REFS
#define F0	"@%s()"
#define F1	"@%s(%s)"
#define F2	"@%s(%s,%s)"
#define F3	"@%s(%s,%s,%s)"
#define F4	"@%s(%s,%s,%s,%s)"
#define FN1	"@%s(%s"
#else
#define F0	"%s()"
#define F1	"%s(%s)"
#define F2	"%s(%s,%s)"
#define F3	"%s(%s,%s,%s)"
#define F4	"%s(%s,%s,%s,%s)"
#define FN1	"%s(%s"
#endif

/* We decompile things with these wierd node-things.  It's ugly, but it works.
 */
static struct pr_node *
n_alloc FUN3N(int, size, int, tightness, char *, fmt)
{
	struct pr_node *ret;
	va_list args;

	ret=ck_malloc(sizeof(struct pr_node)+size+1);
	ret->len=size;
	ret->tightness=tightness;
	var_start(args,fmt);
	vsprintf(ret->string,fmt,args);
	va_end(args);
	return ret;
}

#define n_free(x)	free(x)

static struct pr_node *
byte_decompile FUN1(unsigned char *, expr)
{
	unsigned char byte;
	double tmp_flt;
	long tmp_lng;
	char *tmp_str;
	struct var *v;
	unsigned long_skp;
	unsigned char save_val;
	unsigned jumpto;

	struct pr_node *new = 0;
	int pri;
	int aso;
	char *chr;

	static struct pr_node **the_line;
	static int line_alloc;

	static struct pr_node **c_node;
	struct function *f;

	if(!the_line) {
		the_line=(struct pr_node **)ck_malloc(20*sizeof(struct pr_node *));
		line_alloc=20;
		c_node=the_line;
	}

#ifdef TEST
	if(!expr)
		panic("No expression to decompile");
#endif
 next_byte:
 	byte= *expr++;
	if(byte<USR1)
	 	f= &the_funs[byte];
	else if(byte<SKIP) {
		tmp_lng= *expr++;
		f= &usr_funs[byte-USR1][tmp_lng];
	} else
		f= &skip_funs[byte-SKIP];

	if(f->fn_argn&X_J)
		jumpto= *expr++;
	else if(f->fn_argn&X_JL) {
		jumpto=expr[0]+((unsigned)(expr[1])<<8);
		expr+=2;
	}
#ifdef TEST
	else
		jumpto=0;
#endif
	switch(GET_COMP(f->fn_comptype)) {
	case C_IF:
		if(expr[jumpto-2]!=SKIP) {
			long_skp=1;
			save_val=expr[jumpto-3];
			expr[jumpto-3]=0;
		} else {
			long_skp=0;
			save_val=expr[jumpto-2];
			expr[jumpto-2]=0;
		}
		c_node[0]=byte_decompile(expr);
		c_node++;

		if(long_skp) {
			expr[jumpto-3]=save_val;
			expr+=jumpto;
			jumpto=expr[-2]+((unsigned)(expr[-1])<<8);
		} else {
			expr[jumpto-2]=save_val;
			expr+=jumpto;
			jumpto=expr[-1];
		}
		save_val=expr[jumpto];
		expr[jumpto]=0;
		c_node[0]=byte_decompile(expr);
		c_node-=2;
		expr[jumpto]=save_val;
		expr+=jumpto;
		if(byte==IF || byte==IF_L) {
			if(c_node[0]->tightness<=1)
				new=n_alloc(8+c_node[0]->len+c_node[1]->len+c_node[2]->len,
					1,
					"(%s) ? %s : %s",c_node[0]->string,c_node[1]->string,c_node[2]->string);
			else 
				new=n_alloc(6+c_node[0]->len+c_node[1]->len+c_node[2]->len,
					1,
					"%s ? %s : %s",c_node[0]->string,c_node[1]->string,c_node[2]->string);
		} else

			new=n_alloc(6+c_node[0]->len+c_node[1]->len+c_node[2]->len+strlen(f->fn_str),
				1000,
				F3,f->fn_str,c_node[0]->string,c_node[1]->string,c_node[2]->string);
		n_free(c_node[0]);
		n_free(c_node[1]);
		n_free(c_node[2]);
		break;

	case C_ANDOR:
		save_val=expr[jumpto];
		expr[jumpto]=0;
		c_node[0]=byte_decompile(expr);
		expr[jumpto]=save_val;
		expr+=jumpto;
		c_node++;
		if(ISINFIX(f->fn_comptype)) {
			pri= GET_IN(f->fn_comptype);
			aso= GET_ASO(f->fn_comptype);
			chr= f->fn_str;
			goto do_infix;
		} else
			goto do_fn2;

	case C_STR:
		tmp_str=backslash_a_string((char *)expr+jumpto,1);
		new=n_alloc(strlen(tmp_str)+1,
			1000,
			tmp_str);
		break;

	case C_CELL:
	{
#ifndef A0_REFS
		int num1,num2;
		char *str;
#endif
		CELLREF row,col;

		row=GET_ROW(expr);
		col=GET_COL(expr);
		expr+=EXP_ADD;

#ifdef A0_REFS
		new=n_alloc(30,1000,f->fn_str,col_to_str(col),row);
#else
		if(byte&ROWREL) {
			num1=row-decomp_row;
			if(byte&COLREL) {
				num2=col-decomp_col;
				if(row==decomp_row && col==decomp_col)
					str="rc";
				else if(row==decomp_row) {
					str="rc[%+d]";
					num1=num2;
				} else if(col==decomp_col)
					str="r[%+d]c";
				else
					str=f->fn_str;
			} else if(row==decomp_row) {
				str="rc%u";
				num1=num2=col;
			} else {
				str=f->fn_str;
				num2=col;
			}
		} else if(byte&COLREL) {
			num1=row;
			num2=col-decomp_col;
			if(col==decomp_col)
				str="r%uc";
			else
				str=f->fn_str;
		} else {
			str=f->fn_str;
			num1=row;
			num2=col;
		}
		new=n_alloc(30,1000,str,num1,num2);
#endif
		new->len=strlen(new->string);
	}
		break;

	case C_RANGE:
	{
#ifndef A0_REFS
		char tmprbuf[40];
		char tmpcbuf[40];
#endif
		struct rng rng;

		GET_RNG(expr,&rng);
		expr+=EXP_ADD_RNG;

#ifdef A0_REFS
		new=n_alloc(40,1000,f->fn_str,col_to_str(rng.lc),rng.lr,col_to_str(rng.hc),rng.hr);
#else
		/* Check for special cases */
		if(rng.lr==rng.hr && ((byte&LRREL)?1:0)==((byte&HRREL)?1:0)) {
			if(byte&LRREL) {
				if(rng.lr==decomp_row) {
					tmprbuf[0]='r';
					tmprbuf[1]='\0';
				} else
					(void)sprintf(tmprbuf,"r[%+d]",rng.lr-decomp_row);
			} else
				sprintf(tmprbuf,"r%u",rng.lr);
		} else if((byte&LRREL) && (byte&HRREL)) {
			int r1,r2,rtmp;

			r1=rng.lr-decomp_row;
			r2=rng.hr-decomp_row;
			if(r1<r2)
				rtmp=r1,r1=r2,r2=rtmp;
			(void)sprintf(tmprbuf,"r[%+d:%+d]",r1,r2);
		} else if((byte&LRREL))
			(void)sprintf(tmprbuf,"r[%+d]:%u",rng.lr-decomp_row,rng.hr);
		else if(byte&HRREL)
			(void)sprintf(tmprbuf,"r%u:[%+d]",rng.lr,rng.hr-decomp_row);
		else if(rng.lr<rng.hr)
			(void)sprintf(tmprbuf,"r%u:%u",rng.lr,rng.hr);
		else
			(void)sprintf(tmprbuf,"r%u:%u",rng.hr,rng.lr);

		if(rng.lc==rng.hc && ((byte&LCREL)?1:0)==((byte&HCREL)?1:0)) {
			if(byte&LCREL) {
				if(rng.lc==decomp_col) {
					tmpcbuf[0]='c';
					tmpcbuf[1]='\0';
				} else
					sprintf(tmpcbuf,"c[%+d]",rng.lc-decomp_col);
			} else
				sprintf(tmpcbuf,"c%u",rng.lc);
		} else if((byte&LCREL) && (byte&HCREL)) {
			int c1,c2,ctmp;

			c1=rng.lc-decomp_col;
			c2=rng.hc-decomp_col;
			if(c1<c2)
				ctmp=c1,c1=c2,c2=ctmp;
			(void)sprintf(tmpcbuf,"c[%+d:%+d]",c1,c2);
		} else if((byte&LCREL))
			(void)sprintf(tmpcbuf,"c[%+d]:%u",rng.lc-decomp_col,rng.hc);
		else if(byte&HCREL)
			(void)sprintf(tmpcbuf,"c%u:[%+d]",rng.lc,rng.hc-decomp_col);
		else if(rng.lc<rng.hc)
			(void)sprintf(tmpcbuf,"c%u:%u",rng.lc,rng.hc);
		else
			(void)sprintf(tmpcbuf,"c%u:%u",rng.hc,rng.lc);

		new=n_alloc(40,1000,"%s%s",tmprbuf,tmpcbuf);
#endif
		new->len=strlen(new->string);
	}
		break;

	case C_CONST:
		new=n_alloc(strlen(f->fn_str)+1,1000,f->fn_str);
		break;

	case C_FN0:
	case C_FN0X:
	case C_FN0|C_T:
		new=n_alloc(strlen(f->fn_str)+3,
			1000,
			F0,
			f->fn_str);
		break;

	case C_FN1:
		--c_node;
		new=n_alloc(c_node[0]->len+strlen(f->fn_str)+3,
			1000,
			F1, f->fn_str, c_node[0]->string);
		n_free(*c_node);
		break;

	case C_UNA:
		--c_node;
		if(c_node[0]->tightness<9) {
			new=n_alloc(3+c_node[0]->len,
				9,
				"%s(%s)",f->fn_str,c_node[0]->string);
		} else {
			new=n_alloc(1+c_node[0]->len,
				9,
				"%s%s",f->fn_str,c_node[0]->string);
		}
		n_free(*c_node);
		break;

	case C_INF:
		pri=GET_IN(f->fn_comptype);
		aso=GET_ASO(f->fn_comptype);
		chr=f->fn_str;

	do_infix:
		c_node-=2;
		if(c_node[0]->tightness<pri || (c_node[0]->tightness==pri && aso!=1)) {
			if(c_node[1]->tightness<pri || (c_node[1]->tightness==pri && aso!=-1))
				new=n_alloc(7+c_node[0]->len+c_node[1]->len,
					pri,
					"(%s) %s (%s)",c_node[0]->string,chr,c_node[1]->string);
			else
				new=n_alloc(5+c_node[0]->len+c_node[1]->len,
					pri,
					"(%s) %s %s",c_node[0]->string,chr,c_node[1]->string);
		} else if(c_node[1]->tightness<pri || (c_node[1]->tightness==pri && aso!=-1))
			new=n_alloc(5+c_node[0]->len+c_node[1]->len,
				pri,
				"%s %s (%s)",c_node[0]->string,chr,c_node[1]->string);
		else
			new=n_alloc(3+c_node[0]->len+c_node[1]->len,
				pri,
				"%s %s %s",c_node[0]->string,chr,c_node[1]->string);
		
		n_free(c_node[0]);
		n_free(c_node[1]);
		break;

	case C_FN2:
	do_fn2:
		c_node-=2;
		new=n_alloc(c_node[0]->len+c_node[1]->len+strlen(f->fn_str)+5,
			1000,
			F2,f->fn_str,c_node[0]->string,c_node[1]->string);
		n_free(c_node[0]);
		n_free(c_node[1]);
		break;

	case C_FN3:
		c_node-=3;
		new=n_alloc(c_node[0]->len+c_node[1]->len+c_node[2]->len+strlen(f->fn_str)+7,
			1000,
			F3,
				f->fn_str,
				c_node[0]->string,
				c_node[1]->string,
 				c_node[2]->string);
		n_free(c_node[0]);
		n_free(c_node[1]);
		n_free(c_node[2]);
		break;

	case C_FNN:
		aso= *expr++;
		c_node-=aso;

		if(aso==1)
			new=n_alloc(3+c_node[0]->len+strlen(f->fn_str),
				1000,
				F1,f->fn_str,c_node[0]->string);
		else {
			new=n_alloc(2+c_node[0]->len+strlen(f->fn_str),
				1000,
				FN1,f->fn_str,c_node[0]->string);
			--aso;
			for(pri=1;pri<aso;pri++) {
				n_free(c_node[0]);
				c_node[0]=new;
				new=n_alloc(2+new->len+c_node[pri]->len,
					1000,
					"%s, %s",new->string,c_node[pri]->string);
			}
			n_free(c_node[0]);
			c_node[0]=new;
			new=n_alloc(3+new->len+c_node[aso]->len,
				1000,
				"%s, %s)",new->string,c_node[aso]->string);
		}
		n_free(c_node[0]);
		break;

	case C_FN4:
		c_node-=4;
		new=n_alloc(c_node[0]->len+c_node[1]->len+c_node[2]->len+c_node[3]->len+strlen(f->fn_str)+6,
			1000,
			F4,
				f->fn_str,
				c_node[0]->string,
				c_node[1]->string,
 				c_node[2]->string,
 				c_node[3]->string);
		n_free(c_node[0]);
		n_free(c_node[1]);
		n_free(c_node[2]);
		n_free(c_node[3]);
		break;

	case C_ERR:
		tmp_str=(char *)expr+jumpto;
		expr++;
		new=n_alloc(strlen(tmp_str)+1,1000,tmp_str);
		/* bcopy((VOIDSTAR)expr,(VOIDSTAR)&tmp_str,sizeof(char *));
		expr+=sizeof(char *);
		new=n_alloc(strlen(tmp_str)+1,1000,f->fn_str,tmp_str); */
		break;

	case C_FLT:
		bcopy((VOIDSTAR)expr,(VOIDSTAR)&tmp_flt,sizeof(double));
		expr+=sizeof(double);
		new=n_alloc(20,1000,f->fn_str,tmp_flt);
		new->len=strlen(new->string);
		break;

	case C_INT:
		bcopy((VOIDSTAR)expr,(VOIDSTAR)&tmp_lng,sizeof(long));
		expr+=sizeof(long);
		new=n_alloc(20,1000,f->fn_str,tmp_lng);
		new->len=strlen(new->string);
		break;

	case C_VAR:
		bcopy((VOIDSTAR)expr,(VOIDSTAR)&v,sizeof(struct var *));
		expr+=sizeof(struct var *);
		new=n_alloc(strlen(v->var_name)+1,
			1000,
			f->fn_str, v->var_name);
		break;


	default:
		panic("Bad decompile %d",f->fn_comptype);
	}
	*c_node++=new;
	if(c_node== &the_line[line_alloc]) {
		line_alloc*=2;

		the_line=ck_realloc(the_line,line_alloc*sizeof(struct pr_node *));
		c_node= &the_line[line_alloc/2];
	}

	if(*expr)
		goto next_byte;

	/* if(c_node != &the_line[1]) {
		error_msg("%d values on decompile stack!",c_node - the_line);
		return the_line[0];
	} */
	new= *--c_node;
	/* free(the_line); */
	return new;
}

/* Actual entry points to this file */
/* decomp(row, col, cell) returns a string that can be byte_compiled to create
   cell->formula  decomp_free() frees up the allocated string */
char *
decomp FUN3(CELLREF, r, CELLREF, c, CELL *, cell)
{
	struct pr_node *ret;
	char *str;
	extern char *bname[];
	extern char *strdup EXT1(const char *);
	extern char *flt_to_str EXT1(double);

	decomp_row=r;
	decomp_col=c;
	if(cell->cell_formula==0) {
		switch(GET_TYP(cell)) {
		case 0:
			str=ck_malloc(1);
			str[0]='\0';
			break;
		case TYP_FLT:
			str=strdup(flt_to_str(cell->cell_flt));
			break;
		case TYP_INT:
			str=ck_malloc(20);
			sprintf(str,"%ld",cell->cell_int);
			break;
		case TYP_STR:
			str=strdup(backslash_a_string(cell->cell_str,1));
			break;
		case TYP_BOL:
			str=strdup(bname[cell->cell_bol]);
			break;
		case TYP_ERR:
			str=strdup(ename[cell->cell_bol]);
			break;
#ifdef TEST
		default:
			str=0;
			panic("Unknown type %d in decomp",GET_TYP(cell));
#endif
		}
		save_decomp=(VOIDSTAR)str;
		return str;
	} else
		ret=byte_decompile(cell->cell_formula);
		save_decomp=(VOIDSTAR)ret;
	return &(ret->string[0]);
}

void
decomp_free FUN0()
{
#ifdef TEST
	if(!save_decomp)
		panic("No save decomp");
	n_free(save_decomp);
	save_decomp= (VOIDSTAR)0;
#else
	n_free(save_decomp);
#endif
}

/* This takes a string and returns a backslashed form suitable for printing.
   Iff add_quote is true, it'll add "s at the beginning and end.
   Note that this returns a pointer to a static area that is overwritten with
   each call. . .
 */
char *
backslash_a_string FUN2(char *,string, int,add_quote)
{
	char *pf;
	char *pt;
	int ch;
	int size;
	int len;

	static char *cbuf;
	static int s_cbuf;

#define ALLOC_PT()				\
	len=strlen(pf);				\
	size=pf-string;				\
	if(s_cbuf<3+size+4*len) {		\
		s_cbuf=3+size+4*len;		\
		cbuf= (cbuf) ? ck_realloc(cbuf,s_cbuf) : ck_malloc(s_cbuf); \
	}					\
	if(size)				\
		bcopy(string,cbuf,size);	\
	pt=cbuf+size;				\


	pt=0;
	pf=string;
	if(add_quote) {
		ALLOC_PT()
		*pt++='"';
	}
	for(;*pf;pf++) {
		ch= *pf;
		if(ch>=' ' && ch<='~' && ch!='\\' && (ch!='"' || !add_quote)) {
			if(pt)
				*pt++=ch;
			continue;
		}

		if(!pt) {
			ALLOC_PT()
		}
		if(ch=='\\') {
			*pt++='\\';
			*pt++='\\';
		} else if(ch=='"') {
			*pt++='\\';
			*pt++=ch;
		} else {
			*pt++='\\';
			*pt++=((ch>>6)&0x3)+'0';
			*pt++=((ch>>3)&0x7)+'0';
			*pt++=( ch    &0x7)+'0';
		}
	}
	if(add_quote)
		*pt++='"';
	if(pt) {
		*pt++='\0';
		return cbuf;
	}
	return string;
}

#ifdef TEST
void
dbg_print_formula FUN1(unsigned char *,expr)
{
	unsigned char byte;
	struct function *f;
	double tmp_flt;
	char *tmp_str;
	long tmp_int;
	struct var *v;
	struct rng rng;
	char *buf;
	unsigned jumpto;
	extern char print_buf[];
	extern char *strcpy();

	if(!expr)
		return;
	strcpy(print_buf,"Formula: ");
	buf=print_buf+9;
	while(*expr) {
		byte= *expr++;

		if(byte<USR1)
			f= &the_funs[byte];
		else if(byte<SKIP) {
			tmp_int= *expr++;
			f= &usr_funs[byte-USR1][tmp_int];
		} else
			f= &skip_funs[byte-SKIP];

		if(f->fn_argn&X_J)
			jumpto= *expr++;
		else if(f->fn_argn&X_JL) {
			jumpto= expr[0]+((unsigned)(expr[1])<<8);
			expr+=2;
		} else
			jumpto=0;

		switch(GET_COMP(f->fn_comptype)) {
		case C_IF:
			sprintf(buf," if%d.%u",byte,jumpto);
			break;
		case C_ANDOR:
			sprintf(buf," andor%d.%u",byte,jumpto);
			break;
		case C_ERR:
			tmp_str=(char *)(expr+jumpto);
			byte= *expr++;
			sprintf(buf," err%d.%p(%s)",byte,tmp_str,tmp_str);
			break;
		case C_FLT:
			bcopy((VOIDSTAR)expr,(VOIDSTAR)&tmp_flt,sizeof(double));
			expr+=sizeof(double);
			sprintf(buf," flt.%.15g",tmp_flt);
			break;
		case C_INT:
			bcopy((VOIDSTAR)expr,(VOIDSTAR)&tmp_int,sizeof(long));
			expr+=sizeof(long);
			sprintf(buf," int.%ld",tmp_int);
			break;
		case C_STR:
			tmp_str=(char *)(expr+jumpto);
			sprintf(buf," str.%p.%s",tmp_str,tmp_str);
			break;
		case C_VAR:
			bcopy((VOIDSTAR)expr,(VOIDSTAR)&v,sizeof(struct var *));
			expr+=sizeof(struct var *);
			sprintf(buf," var.%p.%s",v,v->var_name);
			break;
		case C_CELL:
			sprintf(buf," ref%d.%u.%u",byte,GET_ROW(expr-1),GET_COL(expr-1));
			expr+=EXP_ADD;
			break;
		case C_RANGE:
			GET_RNG(expr,&rng);
			sprintf(buf," rng%d.%u-%u,%u-%u",byte,rng.lr,rng.hr,rng.lc,rng.hc);
			expr+=EXP_ADD_RNG;
			break;
		case C_FN0:
		case C_FN0X:
		case C_FN1:
		case C_FN2:
		case C_FN3:
		case C_FN4:
		case C_UNA:
		case C_INF:
			sprintf(buf," fun%d.%s.%p",byte,f->fn_str,f->fn_fun);
			break;

		case C_FNN:
			sprintf(buf," funn%d.%s.%p.%d",byte,f->fn_str,f->fn_fun,*expr++);
			break;
		case C_CONST:
			sprintf(buf," const%s",f->fn_str);
			break;
		case C_SKIP:
			sprintf(buf," skip.%d",jumpto);
			break;
		case 0:
			sprintf(buf," ???%d",byte);
			break;
		default:
			error_msg("Unknown decompile type %d",f->fn_comptype);
		}
		buf+=strlen(buf);
	}
	text_line(print_buf);
}

#endif
