/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

#include <stdlib.h>
#include "m_pd.h"
#include <stdio.h>
#ifdef UNIX
#include <unistd.h>
#endif
#ifdef NT
#include <io.h>
#endif
#include <fcntl.h>
#include <string.h>
#include <stdarg.h>

struct _binbuf
{
    int b_n;
    t_atom *b_vec;
};

t_binbuf *binbuf_new(void)
{
    t_binbuf *x = (t_binbuf *)t_getbytes(sizeof(*x));
    x->b_n = 0;
    x->b_vec = t_getbytes(0);
    return (x);
}

void binbuf_free(t_binbuf *x)
{
    t_freebytes(x->b_vec, x->b_n * sizeof(*x->b_vec));
    t_freebytes(x,  sizeof(*x));
}

void binbuf_clear(t_binbuf *x)
{
    x->b_vec = t_resizebytes(x->b_vec, x->b_n * sizeof(*x->b_vec), 0);
    x->b_n = 0;
}

void binbuf_text(t_binbuf *x, char *text, size_t size)
{
    char buf[MAXPDSTRING+1], *bufp, *ebuf = buf+MAXPDSTRING;
    const char *textp = text, *etext = text+size;
    t_atom *ap;
    int nalloc = 16, natom = 0;
    t_freebytes(x->b_vec, x->b_n * sizeof(*x->b_vec));
    x->b_vec = t_getbytes(nalloc * sizeof(*x->b_vec));
    ap = x->b_vec;
    x->b_n = 0;
    while (1)
    {
	int type;
	while ((textp != etext) && (*textp == ' ' || *textp == '\n'
	    || *textp == '\r' || *textp == '\t')) textp++;
	if (textp == etext) break;
	if (*textp == ';') SETSEMI(ap), textp++;
	else if (*textp == ',') SETCOMMA(ap), textp++;
	else
	{
	    char c;
	    int floatstate = 0, slash = 0, lastslash = 0,
	    	firstslash = (*textp == '\\');
	    bufp = buf;
	    do
	    {
		c = *bufp = *textp++;
		lastslash = slash;
		slash = (c == '\\');

		if (floatstate >= 0)
		{
		    int digit = (c >= '0' && c <= '9'),
			dot = (c == '.'), minus = (c == '-'),
			plusminus = (minus || (c == '+')),
			expon = (c == 'e' || c == 'E');
		    if (floatstate == 0)    /* beginning */
		    {
			if (minus) floatstate = 1;
			else if (digit) floatstate = 2;
			else if (dot) floatstate = 3;
			else floatstate = -1;
		    }
		    else if (floatstate == 1)	/* got minus */
		    {
			if (digit) floatstate = 2;
			else if (dot) floatstate = 3;
			else floatstate = -1;
		    }
		    else if (floatstate == 2)	/* got digits */
		    {
			if (dot) floatstate = 4;
			else if (expon) floatstate = 6;
			else if (!digit) floatstate = -1;
		    }
		    else if (floatstate == 3)	/* got '.' without digits */
		    {
			if (digit) floatstate = 5;
			else floatstate = -1;
		    }
		    else if (floatstate == 4)	/* got '.' after digits */
		    {
			if (digit) floatstate = 5;
			else if (expon) floatstate = 6;
			else floatstate = -1;
		    }
		    else if (floatstate == 5)	/* got digits after . */
		    {
			if (expon) floatstate = 6;
			else if (!digit) floatstate = -1;
		    }
		    else if (floatstate == 6)	/* got 'e' */
		    {
			if (plusminus) floatstate = 7;
			else if (digit) floatstate = 8;
			else floatstate = -1;
		    }
		    else if (floatstate == 7)	/* got plus or minus */
		    {
			if (digit) floatstate = 8;
			else floatstate = -1;
		    }
		    else if (floatstate == 8)	/* got digits */
		    {
			if (!digit) floatstate = -1;
		    }
		}
		if (!slash) bufp++;
	    }
	    while (textp != etext && bufp != ebuf && *textp != ' ' &&
		*textp != '\n' && *textp != '\r' && *textp != '\t' &&
		(slash || (*textp != ',' && *textp != ';')));
	    *bufp = 0;
#if 0
	    post("buf %s", buf);
#endif
	    if (*buf == '$' && buf[1] >= '0' && buf[1] <= '9' && !firstslash)
	    {
		for (bufp = buf+2; *bufp; bufp++)
		    if (*bufp < '0' || *bufp > '9')
		{
		    SETDOLLSYM(ap, gensym(buf+1));
		    goto didit;
		}
		SETDOLLAR(ap, atoi(buf+1));
	    didit: ;
	    }
	    else
	    {
		if (floatstate == 2 || floatstate == 4 || floatstate == 5 ||
		    floatstate == 8)
		    	SETFLOAT(ap, atof(buf));
		else SETSYMBOL(ap, gensym(buf));
	    }
	}
	ap++;
	natom++;
	if (natom == nalloc)
	{
	    x->b_vec = t_resizebytes(x->b_vec, nalloc * sizeof(*x->b_vec),
		nalloc * (2*sizeof(*x->b_vec)));
	    nalloc = nalloc * 2;
	    ap = x->b_vec + natom;
	}
	if (textp == etext) break;
    }
    /* reallocate the vector to exactly the right size */
    x->b_vec = t_resizebytes(x->b_vec, nalloc * sizeof(*x->b_vec),
	natom * sizeof(*x->b_vec));
    x->b_n = natom;
}

void binbuf_gettext(t_binbuf *x, char **bufp, int *lengthp)
{
    char *buf = getbytes(0), *newbuf;
    int length = 0;
    char string[MAXPDSTRING];
    t_atom *ap;
    int index;

    for (ap = x->b_vec, index = x->b_n; index--; ap++)
    {
    	int newlength;
    	char *newbuf;
    	if ((ap->a_type == A_SEMI || ap->a_type == A_COMMA) &&
    	    	length && buf[length-1] == ' ') length--;
    	atom_string(ap, string, MAXPDSTRING);
    	newlength = length + strlen(string) + 1;
    	if (!(newbuf = resizebytes(buf, length, newlength))) break;
    	buf = newbuf;
    	strcpy(buf + length, string);
    	length = newlength;
    	if (ap->a_type == A_SEMI) buf[length-1] = '\n';
    	else buf[length-1] = ' ';
    }
    if (length && buf[length-1] == ' ')
    {
    	if (newbuf = t_resizebytes(buf, length, length-1))
    	{
    	    buf = newbuf;
    	    length--;
    	}
    }
    *bufp = buf;
    *lengthp = length;
}

/* LATER improve the out-of-space behavior below.  Also fix this so that
writing to file doesn't buffer everything together. */

void binbuf_add(t_binbuf *x, int argc, t_atom *argv)
{
    int newsize = x->b_n + argc, i;
    t_atom *ap;
    if (ap = t_resizebytes(x->b_vec, x->b_n * sizeof(*x->b_vec),
	newsize * sizeof(*x->b_vec)))
	    x->b_vec = ap;
    else
    {
    	error("binbuf_addmessage: out of space");
    	return;
    }
#if 0
    startpost("binbuf_add: ");
    postatom(argc, argv);
    endpost();
#endif
    for (ap = x->b_vec + x->b_n, i = argc; i--; ap++)
    	*ap = *(argv++);
    x->b_n = newsize;
}

#define MAXADDMESSV 100
void binbuf_addv(t_binbuf *x, char *fmt, ...)
{
    va_list ap;
    t_atom arg[MAXADDMESSV], *at =arg;
    int nargs = 0;
    char *fp = fmt;

    va_start(ap, fmt);
    while (1)
    {
    	if (nargs >= MAXADDMESSV)
    	{
    	    error("binbuf_addmessv: only %d allowed", MAXADDMESSV);
    	    break;
    	}
    	switch(*fp++)
    	{
    	case 'i': SETFLOAT(at, va_arg(ap, t_int)); break;
    	case 'f': SETFLOAT(at, va_arg(ap, double)); break;
    	case 's': SETSYMBOL(at, va_arg(ap, t_symbol *)); break;
    	case ';': SETSEMI(at); break;
    	case ',': SETCOMMA(at); break;
    	default: goto done;
    	}
    	at++;
    	nargs++;
    }
done:
    va_end(ap);
    binbuf_add(x, nargs, arg);
}

/* add a binbuf to another one for saving.  Semicolons and commas go to
symbols ";", "'",; the symbol ";" goes to "\;", etc. */

void binbuf_addbinbuf(t_binbuf *x, t_binbuf *y)
{
    t_binbuf *z = binbuf_new();
    int i;
    t_atom *ap;
    binbuf_add(z, y->b_n, y->b_vec);
    for (i = 0, ap = z->b_vec; i < z->b_n; i++, ap++)
    {
    	char tbuf[30];
    	switch (ap->a_type)
    	{
    	case A_FLOAT:
    	    break;
    	case A_SEMI:
    	    SETSYMBOL(ap, gensym(";"));
    	    break;
    	case A_COMMA:
    	    SETSYMBOL(ap, gensym(","));
    	    break;
    	case A_DOLLAR:
    	    sprintf(tbuf, "$%d", ap->a_w.w_index);
    	    SETSYMBOL(ap, gensym(tbuf));
    	    break;
    	case A_SYMBOL:
    	    	/* FIXME make this general */
    	    if (!strcmp(ap->a_w.w_symbol->s_name, ";"))
    	    	SETSYMBOL(ap, gensym(";"));
    	    else if (!strcmp(ap->a_w.w_symbol->s_name, ","))
    	    	SETSYMBOL(ap, gensym(","));
    	    break;
    	default:
    	    bug("binbuf_addbinbuf");
    	}
    }
    
    binbuf_add(x, z->b_n, z->b_vec);
}

/* Supply atoms to a binbuf from a message, making the opposite changes
from binbuf_addbinbuf.  The symbol ";" goes to a semicolon, etc. */

void binbuf_restore(t_binbuf *x, int argc, t_atom *argv)
{
    int newsize = x->b_n + argc, i;
    t_atom *ap;
    if (ap = t_resizebytes(x->b_vec, x->b_n * sizeof(*x->b_vec),
	newsize * sizeof(*x->b_vec)))
	    x->b_vec = ap;
    else
    {
    	error("binbuf_addmessage: out of space");
    	return;
    }

    for (ap = x->b_vec + x->b_n, i = argc; i--; ap++)
    {
    	if (argv->a_type == A_SYMBOL)
    	{
    	    if (!strcmp(argv->a_w.w_symbol->s_name, ";")) SETSEMI(ap);
    	    else if (!strcmp(argv->a_w.w_symbol->s_name, ",")) SETCOMMA(ap);
    	    else if (argv->a_w.w_symbol->s_name[0] == '$')
    	    {
    	    	int dollar = 0;
    	    	sscanf(argv->a_w.w_symbol->s_name + 1, "%d", &dollar);
    	    	SETDOLLAR(ap, dollar);
    	    }
    	    else *ap = *argv;
    	    argv++;
    	}
    	else *ap = *(argv++);
    }
    x->b_n = newsize;
}


#define MSTACKSIZE 2048

static int realizedollsym(t_atom *at, char *name, int argc, t_atom *argv)
{
    error("dollsym: not implemented yet");
    return(1);
}

void binbuf_print(t_binbuf *x)
{
    startpost("binbuf_print:");
    postatom(x->b_n, x->b_vec);
    endpost();
}

int binbuf_getnatom(t_binbuf *x)
{
    return (x->b_n);
}

void binbuf_eval(t_binbuf *x, t_pd *target, int argc, t_atom *argv)
{
    static t_atom mstack[MSTACKSIZE], *msp = mstack, *ems = mstack+MSTACKSIZE;
    t_atom *stackwas = msp;
    t_atom *at = x->b_vec;
    int ac = x->b_n;
    int nargs;
    
    while (1)
    {
    	t_pd *nexttarget;
	    /* get a target. */
	while (!target)
	{
	    t_symbol *s;
	    while (ac && (at->a_type == A_SEMI || at->a_type == A_COMMA))
		ac--,  at++;
	    if (!ac) break;
	    s = atom_getsymbol(at);
	    if (!(target = s->s_thing))
	    {
		error("%s: no such object", s->s_name);
		do at++, ac--;
		while (ac && at->a_type != A_SEMI);
		    /* LATER eat args until semicolon and continue */
		continue;
	    }
	    else
	    {
	    	at++, ac--;
	    	break;
	    }
	}
	if (!ac) break;
	nargs = 0;
	nexttarget = target;
	while (1)
	{
	    if (!ac) goto gotmess;
	    if (msp >= ems)
	    {
		error("message stack overflow");
		goto broken;
	    }
	    switch (at->a_type)
	    {
	    case A_SEMI:
	    	nexttarget = 0;     /* falls through */
	    case A_COMMA:
		goto gotmess;
	    case A_FLOAT:
	    case A_SYMBOL:
		*msp = *at;
		break;
	    case A_DOLLAR:
		if (at->a_w.w_index <= 0 || at->a_w.w_index > argc)
		{
		    if (target == s__N.s_thing)
		    	SETFLOAT(msp, 0);
		    else
		    {
		    	error("$%d: not enough arguments supplied",
			    at->a_w.w_index);
		    	goto broken; 
		    }
		}
		else *msp = argv[at->a_w.w_index-1];
		break;
	    case A_DOLLSYM:
		if (realizedollsym(msp, at->a_w.w_symbol->s_name, argc, argv))
		    goto broken;
		break;
	    default:
		bug("bad item in binbuf");
		goto broken;
	    }
	    msp++;
	    ac--;
	    at++;
	    nargs++;
	}
    gotmess:
	if (nargs)
	{
	    switch (stackwas->a_type)
	    {
	    case A_SYMBOL:
		typedmess(target, stackwas->a_w.w_symbol, nargs-1, stackwas+1);
		break;
	    case A_FLOAT:
		if (nargs == 1) pd_float(target, stackwas->a_w.w_float);
		else pd_list(target, 0, nargs, stackwas);
		break;
	    }
	}
	msp = stackwas;
	if (!ac) break;
	target = nexttarget;
	at++;
	ac--;
    }

    return;
broken:
    msp = stackwas;
}

static int binbuf_doopen(char *s, int mode)
{
    char namebuf[MAXPDSTRING];
#ifdef NT
    mode |= O_BINARY;
#endif
    sys_bashfilename(s, namebuf);
    return (open(namebuf, mode));
}

static FILE *binbuf_dofopen(char *s, char *mode)
{
    char namebuf[MAXPDSTRING];
    sys_bashfilename(s, namebuf);
    return (fopen(namebuf, mode));
}

int binbuf_read(t_binbuf *b, char *filename, char *dirname)
{
    long length;
    int fd;
    int readret;
    char *buf;
    char namebuf[MAXPDSTRING];
    strcpy(namebuf, dirname);
    strcat(namebuf, "/");
    strcat(namebuf, filename);
    
    if ((fd = binbuf_doopen(namebuf, 0)) < 0)
    {
    	fprintf(stderr, "open: ");
    	perror(namebuf);
    	return (1);
    }
    if ((length = lseek(fd, 0, SEEK_END)) < 0 || lseek(fd, 0, SEEK_SET) < 0 
    	|| !(buf = t_getbytes(length)))
    {
    	fprintf(stderr, "lseek: ");
    	perror(namebuf);
    	close(fd);
    	return(1);
    }
    if ((readret= read(fd, buf, length)) < length)
    {
    	fprintf(stderr, "read (%d %ld) -> %d\n", fd, length, readret);
    	perror(namebuf);
    	close(fd);
    	t_freebytes(buf, length);
    	return(1);
    }
    binbuf_text(b, buf, length);

#if 0
    startpost("binbuf_read "); postatom(b->b_n, b->b_vec); endpost();
#endif

    t_freebytes(buf, length);
    close(fd);
    return (0);
}

#define WBUFSIZE 4096

int binbuf_write(t_binbuf *x, char *filename, char *dir)
{
    FILE *f;
    char sbuf[WBUFSIZE], fbuf[MAXPDSTRING], *bp = sbuf, *ep = sbuf + WBUFSIZE;
    t_atom *ap;
    int index;

    strcpy(fbuf, dir);
    strcat(fbuf, "/");
    strcat(fbuf, filename);
    if (!(f = binbuf_dofopen(fbuf, "w")))
    {
    	fprintf(stderr, "open: ");
    	sys_unixerror(fbuf);
    	return (1);
    }
    for (ap = x->b_vec, index = x->b_n; index--; ap++)
    {
    	int length;
    	if (ap->a_type == A_SYMBOL || ap->a_type == A_DOLLSYM)
    	    length = 3 + strlen(ap->a_w.w_symbol->s_name);
    	else length = 40;
    	if (ep - bp < length)
    	{
    	    if (fwrite(sbuf, bp-sbuf, 1, f) < 1)
    	    {
    	    	sys_unixerror(fbuf);
    	    	fclose(f);
    	    	return(1);
    	    }
    	    bp = sbuf;
    	}
    	if ((ap->a_type == A_SEMI || ap->a_type == A_COMMA) &&
    	    bp > sbuf && bp[-1] == ' ') bp--;
    	atom_string(ap, bp, (ep-bp)-2);
    	bp += strlen(bp);
    	if (ap->a_type == A_SEMI) *bp++ = '\n';
    	else *bp++ = ' ';
    }
    if (fwrite(sbuf, bp-sbuf, 1, f) < 1)
    {
    	sys_unixerror(fbuf);
    	fclose(f);
    	return(1);
    }
    fclose(f);
    return (0);
}

extern t_pd *newest;
void pd_doloadbang(void);

/* LATER make this evaluate the file on-the-fly. */
/* LATER figure out how to log errors */
void binbuf_evalfile(t_symbol *name, t_symbol *dir)
{
    t_binbuf *b = binbuf_new();
    	/* set filename so that new canvases can pick them up */
    glob_setfilename(0, name, dir);
    if (binbuf_read(b, name->s_name, dir->s_name)) perror(name->s_name);
    binbuf_eval(b, 0, 0, 0);
    binbuf_free(b);
}

void glob_evalfile(t_pd *ignore, t_symbol *name, t_symbol *dir)
{
    t_pd *x = 0;
    int dspstate = canvas_suspend_dsp();
    binbuf_evalfile(name, dir);
    while ((x != s__X.s_thing) && (x = s__X.s_thing))
    	vmess(x, gensym("pop"), "i", 1);
    canvas_resume_dsp(dspstate);
    pd_doloadbang();
}
