/*	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 <stdio.h>

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

#ifdef SYSV
#include <dirent.h>
#define direct dirent
#else
#ifdef __TURBOC__
typedef int ino_t;
#include "msd_dir.h"
#else
#ifdef __STDC__
#include <sys/types.h>
#endif
#include <sys/file.h>
#include <sys/dir.h>
#endif
#endif

#ifdef __STDC__
#ifndef VOIDSTAR
#define VOIDSTAR void *
#endif
#define CONST const
#undef NULL
#else
#ifndef VOIDSTAR
#define VOIDSTAR char *
#endif
#define CONST
#endif

#include <ctype.h>

#ifndef F_OK
#define F_OK 0
#endif
#ifndef _IOSTRG
#define _IOSTRG 0
#endif

extern VOIDSTAR malloc();

VOIDSTAR ck_malloc EXT1(size_t);
VOIDSTAR ck_realloc EXT2(VOIDSTAR,size_t);
char *strdup EXT1(CONST char *);

struct id {
	int flag;
	FILE *fp;
	char *name;
};

struct id *__id_s;
int __id_n;
int __id_f;

int __make_backups;
int __backup_by_copying;

/* Stash argv[0] here so panic will know what the program is called */
char *myname = 0;

/* Blow chunks! */
void
panic FUN1N(char *, s)
{
	va_list iggy;
	extern void abort EXT0();

	var_start(iggy,s);
	if(myname)
		fprintf(stderr,"%s:",myname);
	vfprintf(stderr,s,iggy);
	putc('\n',stderr);
	va_end(iggy);
	abort();	/* Should be changed to exit(EXIT_FATAL) for production */
}

/* Given a file name, come up with a backup file name. . . */
char *
backup_file_name FUN1(char *,file_name)
{
	char    *dir_name,*dir_end;

	DIR     *dir;
	register struct direct *dp;
	int     len;
	int     max_fnum;
	int     cur_fnum;

	char *tmp_ptr;

	char *return_value;

	dir_end=rindex(file_name,'/');
	if(dir_end) {
		dir_name=file_name;
		file_name=dir_end+1;
		*dir_end='\0';
	} else {
		dir_name=".";
	}
	len=strlen(file_name);

	dir=opendir(dir_name);
	if(dir==0) {
		if(dir_end)
			*dir_end='/';
		return (char *)0;
	}

	max_fnum=0;
	while(dp=readdir(dir)) {
		if(   !dp->d_ino
#ifndef SYSV
 		   || dp->d_namlen<=len
#endif
		   || strncmp(dp->d_name,file_name,len)
		   || dp->d_name[len]!='.'
		   || dp->d_name[len+1]!='~'
#ifndef SYSV
		   || dp->d_name[dp->d_namlen-1]!='~')
#else
		   || dp->d_name[strlen(dp->d_name)-1]!='~')
#endif
			continue;

		tmp_ptr= &(dp->d_name[len+2]);
		for(cur_fnum=0;isdigit(*tmp_ptr);tmp_ptr++)
			cur_fnum=cur_fnum*10 + *tmp_ptr-'0';
#ifndef SYSV
		if(tmp_ptr!= &(dp->d_name[dp->d_namlen-1]) || cur_fnum<max_fnum)
#else
		if(tmp_ptr[2]!='\0' || cur_fnum<max_fnum)
#endif
			continue;
		max_fnum=cur_fnum;
	}
	closedir(dir);
	max_fnum++;
	return_value=(char *)malloc(strlen(dir_name)+len+12);
	if(!return_value)
		return (char *)0;
	sprintf(return_value,"%s/%s.~%d~",dir_name,file_name,max_fnum);
	if(dir_end)
		*dir_end='/';
	return return_value;
}


char *
__fp_name FUN1(FILE *, fp)
{
	int n;

	for(n=0;n<__id_n;n++) {
		if(__id_s[n].fp==fp)
			return __id_s[n].name;
	}
	return "{Unknown file pointer}";
}

void
__set_fp FUN3(FILE *,fp, CONST char *,name, int,flag)
{
	if(__id_s==0) {
		__id_s=ck_malloc(20*sizeof(struct id));
		__id_n=0;
		__id_f=20;
	} else {
		int n;

		for(n=0;n<__id_n;n++)
			if(__id_s[n].fp==fp) {
				free(__id_s[n].name);
				__id_s[n]=__id_s[--__id_n];
				__id_f++;
				break;
			}
	}
	if(__id_f==0) {
		__id_f=20;

		__id_s=ck_realloc(__id_s,(__id_f+__id_n)*sizeof(struct id));
	}
	__id_s[__id_n].flag=flag;
	__id_s[__id_n].name=strdup(name);
	__id_s[__id_n].fp=fp;
	__id_n++;
	__id_f--;
}

/* Open a file or a pipe */
FILE *
xopen FUN2(CONST char *,name, CONST char *,mode)
{
	int flag = 0;
	FILE *ret;
	extern FILE *popen EXT2(CONST char *,CONST char *);

	while(*name==' ')
		name++;
	if(*name=='!') {
		name++;
		ret=popen(name,mode);
		flag=1;
	} else
		ret=fopen(name,mode);
	if(ret==0)
		return ret;
	__set_fp(ret,name,flag);
	return ret;
}

/* Open a file, creating a backup file if needed. . . */
FILE *
fopen_with_backup FUN2(char *,name, CONST char *,mode)
{
	char *newname;

	if(__make_backups && *mode=='w' && access(name,F_OK)==0) {
		newname=backup_file_name(name);
		if(!newname)
			return (FILE *)0;
		if(__backup_by_copying) {
			FILE *c_in,*c_out;
			int n_read;
#ifdef __TURBOC__
			char buf[512];
#else
			char buf[4096];
#endif
			extern FILE *ck_fopen();

			c_in=fopen(name,"r");
			c_out=fopen(name,"w");
			if(!c_in || !c_out)
				return (FILE *)0;
			while((n_read=fread(buf,sizeof(buf),1,c_in))>0)
				if(fwrite(buf,n_read,1,c_out)!=n_read)
					return (FILE *)0;
			if(fclose(c_in)==EOF ||fclose(c_out)==EOF)
				return (FILE *)0;
		} else
#ifdef NO_RENAME
			if(link(name,newname) || unlink(name))
#else
			if(rename(name,newname)<0)
#endif
				return (FILE *)0;
		free(newname);
	}
	return fopen(name,mode);
}

/* Open a file or a pipe, creating a backup file if it's a file */
FILE *
xopen_with_backup FUN2(char *,name, CONST char *,mode)
{
	int flag;
	FILE *ret;

	while(*name==' ')
		name++;
	if(*name=='|') {
		ret=popen(name+1,mode);
		flag=1;
	} else {
		ret=fopen_with_backup(name,mode);
		flag=0;
	}
	if(ret==0)
		return ret;
	__set_fp(ret,name,flag);
	return ret;
}

/* Close something opened with xopen. . . */
int
xclose FUN1(FILE *,fp)
{
	int ret;
	int n;
	extern int pclose();
	extern int fclose();

	for(n=0;n<__id_n;n++) {
		if(__id_s[n].fp==fp)
			break;
	}
	if(n==__id_n)
		panic("Unknown file pointer %p given to xclose",fp);
	if(__id_s[n].flag)
		ret=pclose(fp);
	else
		ret=fclose(fp);
	return ret;
}

/* Fclose or panic */
void
ck_fclose FUN1(FILE *, stream)
{
extern int fclose();

	if(fclose(stream)==EOF)
		panic("Couldn't close %s",__fp_name(stream));
}

/* fopen or panic */
VOIDSTAR
ck_malloc FUN1(size_t, size)
{
	VOIDSTAR ret;

	ret=malloc(size);
	if(ret==(VOIDSTAR)0)
		panic("Couldn't allocate %u bytes",size);
	return ret;
}

/* Realloc or panic */
VOIDSTAR
ck_realloc FUN2(VOIDSTAR, ptr,size_t, size)
{
	VOIDSTAR ret;
	VOIDSTAR realloc EXT2(VOIDSTAR,size_t);

	ret=realloc(ptr,size);
	if(ret==(VOIDSTAR)0)
		panic("Couldn't re-allocate %u bytes from %p",size,ptr);
	return ret;
}

/* Do a sprintf into an allocated buffer. */
char *
#ifdef __STDC__
mk_sprintf(char *str,...)
{
	va_list iggy;
#ifdef __TURBOC__
	static
#endif
	char tmpbuf[1024*8];
	char *ret;
#ifdef NEED_VPRINTF
	auto FILE f;
#endif

	va_start(iggy,str);
#ifdef NEED_VPRINTF
	f._cnt = 1024*8-1;
	f._ptr = (unsigned char *)tmpbuf;
	f._flag = _IOWRT+_IOSTRG;
	_doprnt(str, iggy, &f);
	*f._ptr = 0;
#else
	vsprintf(tmpbuf,str,iggy);
#endif
	va_end(iggy);
	ret=(char *)ck_malloc(strlen(tmpbuf)+1);
	strcpy(ret,tmpbuf);
	return ret;
}
#else
mk_sprintf(str,va_alist)
char *str;
va_dcl
{
	va_list iggy;
#ifdef __TURBOC__
	static
#endif
	char tmpbuf[1024*8];
	char *ret;
#ifdef NEED_VPRINTF
	auto FILE f;
#endif

	va_start(iggy);
#ifdef NEED_VPRINTF
	f._cnt = 1024*8-1;
	f._ptr = (unsigned char *)tmpbuf;
	f._flag = _IOWRT+_IOSTRG;
	_doprnt(str, iggy, &f);
	*f._ptr = 0;
#else
	vsprintf(tmpbuf,str,iggy);
#endif
	va_end(iggy);

	ret=(char *)ck_malloc(strlen(tmpbuf)+1);
	strcpy(ret,tmpbuf);
	return ret;
}
#endif

/* Implement a variable sized LIFO stack of pointers to void */

struct stack {
	int allocated;
	int used;
	VOIDSTAR *buf;
};

#define MIN_STACK 20

VOIDSTAR
init_stack FUN0()
{
	struct stack *b;

	b=(struct stack *)ck_malloc(sizeof(struct stack));
	b->allocated=MIN_STACK;
	b->used=0;
	b->buf=(VOIDSTAR *)ck_malloc(MIN_STACK*sizeof(VOIDSTAR));
	return (VOIDSTAR)b;
}

void
flush_stack FUN1(VOIDSTAR, bb)
{
	struct stack *b;

	b=(struct stack *)bb;
	free(b->buf);
	b->buf=0;
	b->allocated=0;
	b->used=0;
	free(b);
}

void
push_stack FUN2(VOIDSTAR,bb, VOIDSTAR,add)
{
	struct stack *b;

	b=(struct stack *)bb;
	if(b->allocated==b->used) {
		b->allocated*=2;

		b->buf=(VOIDSTAR *)ck_realloc(b->buf,b->allocated*sizeof(VOIDSTAR));
	}
	b->buf[(b->used)++]=add;
}

VOIDSTAR
pop_stack FUN1(VOIDSTAR, bb)
{
	struct stack *b;

	b=(struct stack *)bb;
	if(b->used==0)
		return (VOIDSTAR)0;
	return b->buf[--(b->used)];
}

int
size_stack FUN1(VOIDSTAR, bb)
{
	struct stack *b;

	b=(struct stack *)bb;
	return b->used;
}


/* For systems that don't have them */
#ifdef NEED_VPRINTF
int
vfprintf FUN3(FILE *, fp, CONST char *, s, va_list, ap)
{
	int len;

	len = _doprnt(s,ap,fp);
	return (ferror(fp) ? EOF : len);
}

int
vsprintf FUN3(char *, into, CONST char *, s, va_list, ap)
{
	int ret;
	auto FILE f;

	f._cnt = 32767;
	f._ptr = into;
	f._flag = _IOWRT+_IOSTRG;
	ret = _doprnt(s, ap, &f);
	*f._ptr = 0;
	return (ret);
}
#endif

#ifndef LMALLOC
char *
strdup FUN1(CONST char *, str)
{
	char *ret;

	ret=(char *)ck_malloc(strlen(str)+2);
	strcpy(ret,str);
	return ret;
}
#endif

/*
 * stricmp - compare string s1 to s2, ignoring case
 */

int
stricmp FUN2(CONST char *,s1, CONST char *,s2)
{
	register CONST char *scan1;
	register CONST char *scan2;
	register char chr1,chr2;

	scan1 = s1;
	scan2 = s2;
	do {
 		chr1= isupper(*scan1) ? tolower(*scan1) : *scan1;
 		chr2= isupper(*scan2) ? tolower(*scan2) : *scan2;
		scan1++;
		scan2++;
	} while(chr1 && chr1==chr2);

	/*
	 * The following case analysis is necessary so that characters
	 * which look negative collate low against normal characters but
	 * high against the end-of-string NUL.
	 */
	if (chr1 == '\0' && chr2 == '\0')
		return 0;
	else if (chr1 == '\0')
		return -1;
	else if (chr2 == '\0')
		return 1;
	else
		return chr1 - chr2;
}

/* strincmp - compare first N chars of strings S1 and S2 */
int
strincmp FUN3(CONST char *,s1, CONST char *,s2, size_t,n)
{
	register CONST char *scan1;
	register CONST char *scan2;
	register size_t count;
	register char chr1,chr2;

	scan1 = s1;
	scan2 = s2;
	count = n;
	do {
 		chr1= isupper(*scan1) ? tolower(*scan1) : *scan1;
 		chr2= isupper(*scan2) ? tolower(*scan2) : *scan2;
		scan1++;
		scan2++;
	} while(--count!=0 && chr1 && chr1==chr2);

	/* if (count == (size_t)-1)
		return 0; */

	/*
	 * The following case analysis is necessary so that characters
	 * which look negative collate low against normal characters but
	 * high against the end-of-string NUL.
	 */
	if (chr1 == '\0' && chr2 == '\0')
		return 0;
	else if (chr1 == '\0')
		return -1;
	else if (chr2 == '\0')
		return 1;
	else
		return chr1 - chr2;
}

CONST char *
strstr FUN2(CONST char *,s, CONST char *,wanted)
{
	register CONST char *scan;
	register size_t len;
	register char firstc;

	/*
	 * The odd placement of the two tests is so "" is findable.
	 * Also, we inline the first char for speed.
	 * The ++ on scan has been moved down for optimization.
	 */
	firstc = *wanted;
	len = strlen(wanted);
	for (scan = s; *scan != firstc || strncmp(scan, wanted, len) != 0; )
		if (*scan++ == '\0')
			return (char *)0;
	return scan;
}

extern int sys_nerr;
extern char *sys_errlist[];
extern int errno;

char *
err_msg FUN0()
{
	int n;
	static char buf[80];

	n=errno;

	if(n<sys_nerr)
		return sys_errlist[n];
	sprintf(buf,"Unknown error code %d (%#x)",n,n);
	return buf;
}

#if !defined(LMALLOC) && !defined(__GNU_LIBRARY__)
CONST char *				/* found char, or NULL if none */
index FUN2(CONST char *,s, char, charwanted)
{
	register CONST char *scan;

	/*
	 * The odd placement of the two tests is so NUL is findable.
	 */
	for (scan = s; *scan != charwanted;)	/* ++ moved down for opt. */
		if (*scan++ == '\0')
			return (char *)0;
	return scan;
}

void
bcopy FUN3(CONST VOIDSTAR,src, VOIDSTAR,dst, size_t,size)
{
	register char *d;
	register CONST char *s;
	register size_t n;

	if (size <= 0)
		return;

	s = src;
	d = dst;
	if (s <= d && s + (size-1) >= d) {
		/* Overlap, must copy right-to-left. */
		s += size-1;
		d += size-1;
		for (n = size; n > 0; n--)
			*d-- = *s--;
	} else
		for (n = size; n > 0; n--)
			*d++ = *s++;

}

int				/* == 0 or != 0 for equality and inequality */
bcmp FUN3(CONST VOIDSTAR,s1, CONST VOIDSTAR,s2, size_t,size)
{
	register CONST char *scan1;
	register CONST char *scan2;
	register size_t n;

	scan1 = s1;
	scan2 = s2;
	for (n = size; n > 0; n--)
		if (*scan1 == *scan2) {
			scan1++;
			scan2++;
		} else
			return *scan1 - *scan2;

	return 0;
}

void
bzero FUN2(VOIDSTAR,dst, size_t,length)
{
	register char *scan;
	register size_t n;

	scan = dst;
	for (n = length; n > 0; n--)
		*scan++ = 0;
}
#endif

/* Take a quoted string and return the character it represents */
int
string_to_char FUN1(char **,ptr)
{
	char *str;
	int i;
	char c1,c2;

	str= *ptr;
	if(str[0]=='\\') {
		switch(str[1]) {
		case '\\':	i='\\';		break;
		case 'a':	i='\a';		break;
		case 'b':	i='\b';		break;
		case 'f':	i='\f';		break;
		case 'n':	i='\n';		break;
		case 'r':	i='\r';		break;
		case 't':	i='\t';		break;
		case 'x':
			c1=str[2];
			c2=str[3];
			if(isxdigit(c1)) {
				if(isdigit(c1))
					c1-='0';
				else if(isupper(c1))
					c1-='A';
				else
					c1-='a';
				if(isxdigit(c2)) {
					if(isdigit(c2))
						c2-='0';
					else if(isupper(c2))
						c2-='A';
					else
						c2-='a';
					i=c1*0x10+c2;
					str++;
				} else
					i=c1;
			} else
				i='x';
			break;

		case '0':  case '1':  case '2':  case '3':
		case '4':  case '5':  case '6':  case '7':
			if(str[2]>='0' && str[2]<='7') {
				if(str[3]>='0' && str[3]<='7') {
					i=(str[1]-'0')*0100+(str[2]-'0')*010+(str[3]-'0');
					str+=2;
				} else {
					i=(str[1]-'0')*010+(str[2]-'0');
					str++;
				}
			} else
				i=str[1]-'0';
			break;
		default:	i=str[0];	--str;	break;
		}
		str+=2;
		*ptr=str;
		return i;
	}

	if(str[0]=='M' && str[1]=='-') {
		i=0x80;
		str+=2;
	} else
		i=0;

	if(str[0]=='^') {
 		if(str[1]=='?')
 			i+=0x7f;
		else if (str[1]>='@' && str[1]<='_')
			i|= str[1]-'@';
		else
			return -1;
		str+=2;
	} else {
		i|=str[0];
		str++;
	}
	*ptr=str;
	return i;
}

/* Take a char and turn it into a readable string */
char *
char_to_string FUN1(char,ch)
{
	static char buf[5] = "M-";

	if(ch>=' ' && ch<='~') {
		buf[3]=ch;
		return &buf[3];
	}
	if(ch&0x80) {
		ch&=0x7f;
		if(ch==0x7f || ch<' ') {
			buf[2]='^';
			buf[3]=(ch==0x7f ? '?' : ch+'@');
		} else {
			buf[2]=ch;
			buf[3]='\0';
		}
		return &buf[0];
	}
	if(ch==0x7f || ch<' ') {
		buf[2]='^';
		buf[3]=(ch==0x7f ? '?' : ch+'@');
		return &buf[2];
	}
	return "huh";
}

long
astol FUN1(char **,ptr)
{
	register long i = 0;
	register int c;
	int sign = 1;
	char *s;

	s= *ptr;
	/* Skip whitespace */
	while(isspace(*s))
		if(*s++ == '\0') {
			*ptr=s;
			return(0);
		}
	/* Check for - or + */
	if(*s == '-') {
		s++;
		sign = -1;
	} else if(*s=='+')
		s++;

	/* Read in the digits */
	for(; c = *s; s++) {
		if(!isdigit(c) || i>214748364 || (i==214748364 && c>(sign>0 ? '7' : '8')))
			break;
		i = i * 10 + c-'0';
	}
	*ptr=s;
	return i*sign;
}

/*
 *	astof - accept a number of the form:
 *		(and ignores leading white space)
 *
 *	null	::=
 *	digit	::= 0|1|2|3|4|5|6|7|8|9
 *	digits	::= <digit>*
 *	DIGITS	::= <digit>+
 *	sign	::= <null> | + | -
 *	-------------------------------
 *		accepted:
 *	-------------------------------
 *	integer	::= <sign><DIGITS>
 *	real	::= <integer> . <digits> | <null> . <DIGITS>
 *	epart	::= e <integer> | E <integer>
 *	float	::= <integer> <epart> | <real> <epart>
 *
 *	Always returned as a double
 *
 *	There is no particular attempt to reduce mpys/divs
 *	those machines that are still out there (eg. PDP11/Small)
 *	that shun floating point arithmetic might rethink this routine.
 */

static double exps0[10] = { 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9};
static double exps1[10] = { 1E00,1E10,1E20,1E30
#ifndef vax
	,1E40,1E50,1E60,1E70,1E80,1E90
#endif
};

#define REGISTER register

double
astof FUN1(char **,sp)
{
	REGISTER char *s;
	REGISTER char *cp;
	long ipart, epart;
	int neg = 0;
	double res;
	int n;

	s= *sp;
	while(isspace(*s)) {
		s++;
		if(*s == '\0') {
			*sp = s;
			return(0.0);
		}
	}
	/*
	 *	Need to handle sign here due to '-.3' or '-0.3'
	 */
	if(*s == '-') {
		++neg;
		++s;
	} else if(*s=='+')
		++s;
	cp = s;
	/*
	 *	get ipart handling '.n' case
	 */
	res= 0.0;
	while(isdigit(*s)) {
		for(n=0,ipart=0;n<6 && isdigit(*s);n++)
			ipart=ipart*10 + *s++ -'0';
		res=res*exps0[n]+(double)ipart;
	}
	if(s == cp) {
		if(*s == '.')
			ipart = 0;
		else {
			*sp = s;
			return(0.0);
		}
	}
	/*
	 *	either we find a '.' or e|E or done
	 */
	if(*s == '.')
	{
		int m;
		++s;

		m=0;
		while(isdigit(*s)) {
			for(n=0,ipart=0;n<6 && isdigit(*s);n++)
				ipart=ipart*10 + *s++ -'0';
			m+=n;
			if(m>=100)
				continue;
			if(m>=10)
				res+=((double)ipart)/(exps1[m/10]*exps0[m%10]);
			else
				res+= ((double)ipart)/exps0[m];
		}
	}
	/*
	 *	In either case (.) handle E part
	 */
	if(*s=='e' || *s=='E') {
		int eneg;

		++s;
		epart=0;
		eneg=0;
		if(*s=='-') {
			eneg++;
			s++;
		} else if(*s=='+')
			s++;
		while(isdigit(*s))
			epart=epart*10+*s++-'0';
		if(eneg) {
#ifndef vax
			while(epart>=100) {
				res/=1E100;
				epart-=100;
			}
#endif
			if(epart>9) {
				res/=exps1[epart/10];
				epart%=10;
			}
			if(epart)
				res/=exps0[epart];
		} else {
#ifndef vax
			while(epart>=100) {
				res *=1E100;
				epart-=100;
			}
#endif
			if(epart>9) {
				res*=exps1[epart/10];
				epart %= 10;
			}
			if(epart)
				res*=exps0[epart];
		}
	}
	/*
	 *	fix sign
	 */
	if(neg) res = -res;
	*sp=s;
	return(res);
}

#ifdef TEST_ASTOF
main()
{
	char buf[80];
	char *ptr;
	double at,ast;
	double atof();

	while(gets(buf)) {
		at=atof(buf);
		ptr=buf;
		ast=astof(&ptr);
		printf("%15.6f %15.6f %s ",at,ast,at==ast ? "eq" : "NEQ");
		if(*ptr) printf("%s->'%s'\n",buf,ptr);
		else printf("%s\n",buf);
	}
}

#endif
