/* MIT pancake <pancake@nopcode.org> (C) 2009-2021 */

#include <limits.h>
#include "spp.h"
#include "r_api.h"
#include "config.h"

#if !USE_R2
#include "r_api.c"
#endif

S_API int spp_run(char *buf, Output *out) {
	SppProc *p = out->proc? out->proc: proc;
	out->proc = p;
	SppTag *tags = (SppTag *)p->tags;
	size_t i;
	int ret = 0;
	char *tok;

	D fprintf (stderr, "SPP_RUN(%s)\n", buf);
	if (p->chop) {
		for (; IS_SPACE (*buf); buf++);
		int buflen = strlen (buf);
		for (tok = buf + (buflen? buflen - 1: 0); IS_SPACE (*tok); tok--) {
			*tok = '\0';
		}
	}

	if (p->token) {
		tok = strstr (buf, p->token);
		if (tok) {
			*tok = '\0';
			tok = tok + 1;
		} else {
			tok = buf;
		}
	} else {
		tok = buf;
	}
	for (i = 0; tags[i].callback; i++) {
		D fprintf (stderr, "NAME=(%s)\n", tok);
		if ((!tags[i].name) || (!strcmp (buf, tags[i].name))) {
			if (out->fout) {
				fflush (out->fout);
			}
			ret = tags[i].callback (&p->state, out, tok);
			p->state.ifl += ret;
			if (ret == -1) {
				break;
			}
			if (ret) {
				if (p->state.ifl < 0 || p->state.ifl >= MAXIFL) {
					fprintf (stderr, "Nested conditionals parsing error.\n");
					break;
				}
			}
			break;
		}
	}
	return ret;
}

static char *spp_run_str(SppProc *p, char *buf, int *rv) {
	char *b;
	Output tmp = { .proc = p };
	tmp.fout = NULL;
	tmp.cout = r_strbuf_new ("");
	int rc = spp_run (buf, &tmp);
	b = strdup (r_strbuf_get (tmp.cout));
	r_strbuf_free (tmp.cout);
	if (rv) {
		*rv = rc;
	}
	return b;
}

S_API void lbuf_strcat(SppBuf *dst, char *src) {
	size_t len = strlen (src);
	size_t need = (size_t)dst->lbuf_n + len + 1;
	if (need > INT_MAX) {
		fprintf (stderr, "Out of memory.\n");
		return;
	}
	if (!dst->lbuf || need > (size_t)dst->lbuf_s) {
		size_t nsize = (size_t)dst->lbuf_s * 2;
		if (nsize < need || nsize > INT_MAX) {
			nsize = need;
		}
		char *nbuf = realloc (dst->lbuf, nsize);
		if (!nbuf) {
			fprintf (stderr, "Out of memory.\n");
			return;
		}
		dst->lbuf = nbuf;
		dst->lbuf_s = (int)nsize;
	}
	memcpy (dst->lbuf + dst->lbuf_n, src, len + 1);
	dst->lbuf_n += (int)len;
}

S_API int do_fputs(Output *out, char *str) {
	SppProc *p = out->proc? out->proc: proc;
	out->proc = p;
	int i;
	int printed = 0;
	for (i = 0; i <= p->state.ifl; i++) {
		if (!p->state.echo[i]) {
			return printed;
		}
	}
	if (str[0]) {
		printed = 1;
	}
	if (p->fputs) {
		p->fputs (out, str);
	} else {
		if (out->fout) {
			fprintf (out->fout, "%s", str);
		}
	}
	return printed;
}

S_API void spp_eval(char *buf, Output *out) {
	spp_proc_eval (proc, buf, out);
}

S_API void spp_proc_eval(SppProc *proc, char *buf, Output *out) {
	out->proc = proc;
	char *ptr, *ptr2;
	char *ptrr = NULL;
	int delta;
	int printed = 0;
retry:
	/* per word */
	if (!proc->tag_pre && proc->token) {
		do {
			ptr = strstr (buf, proc->token);
			if (ptr) {
				*ptr = '\0';
			}
			delta = strlen (buf) - 1;
			if (buf[delta] == '\n') {
				buf[delta] = '\0';
			}
			if (*buf) {
				spp_run (buf, out);
			}
			buf = ptr + 1;
		} while (ptr);
		return;
	}

	if (!proc->tag_post) {
		/* handle per line here ? */
		return;
	}

	// TODO: do it in scope!
	delta = strlen (proc->tag_post);

	/* (pre) tag */
	ptr = proc->tag_pre? strstr (buf, proc->tag_pre): NULL;
	if (ptr) {
		D printf ("==> 0.0 (%s)\n", ptr);
		if (!proc->tag_begin || (proc->tag_begin && ptr == buf)) {
			*ptr = '\0';
			ptr = ptr + strlen (proc->tag_pre);
			if (do_fputs (out, buf)) {
				printed = 1;
			}
			D printf ("==> 0 (%s)\n", ptr);
		}
		ptrr = strstr (ptr + strlen (proc->tag_pre), proc->tag_pre);
	}

	/* (post) tag */
	if (!ptr) {
		if (do_fputs (out, buf)) {
			printed = 1;
		}
		return;
	}
	ptr2 = strstr (ptr, proc->tag_post);
	if (ptr2) {
		*ptr2 = '\0';
		if (ptrr) {
			if (ptrr < ptr2) {
				char *p = strdup (ptr2 + 2);
				char *s = spp_run_str (proc, ptrr + strlen (proc->tag_pre), NULL);
				if (s && p) {
					size_t prefix_len = ptrr - buf;
					size_t s_len = strlen (s);
					size_t p_len = strlen (p);
					if (s_len < SIZE_MAX - p_len - prefix_len - 1) {
						size_t newlen = prefix_len + s_len + p_len + 1;
						char *newbuf = malloc (newlen);
						if (newbuf) {
							memcpy (newbuf, buf, prefix_len);
							memcpy (newbuf + prefix_len, s, s_len);
							memcpy (newbuf + prefix_len + s_len, p, p_len + 1);
							free (s);
							free (p);
							buf = newbuf;
							ptrr = NULL;
							goto retry;
						}
					}
				}
				free (s);
				free (p);
			}
		}
		if (proc->buf.lbuf && proc->buf.lbuf[0]) {
			D printf("==> 1 (%s)\n", proc->buf.lbuf);
			if (ptr) {
				lbuf_strcat (&proc->buf, buf);
				if (do_fputs (out, buf)) {
					printed = 1;
				}
				spp_run (ptr, out);
			} else {
				lbuf_strcat (&proc->buf, buf);
				D printf ("=(1)=> spp_run(%s)\n", proc->buf.lbuf);
				spp_run (proc->buf.lbuf + delta, out);
				D printf ("=(1)=> spp_run(%s)\n", proc->buf.lbuf);
			}
			proc->buf.lbuf[0]='\0';
			proc->buf.lbuf_n = 0;
		} else {
			D printf ("==> 2 (%s)\n", ptr);
			if (ptr) {
				D printf (" ==> 2.1: run(%s)\n", ptr);
				spp_run (ptr, out);
				buf = ptr2 + delta;
				if (buf[0] == '\n' && printed) {
					buf++;
				}
				D printf (" ==> 2.1: continue(%s)\n", buf);
				goto retry;
			} else {
				if (do_fputs (out, buf)) {
					printed = 1;
				}
			}
		}
		if (do_fputs (out, buf)) {
			printed = 1;
		}
	} else {
		D printf ("==> 3\n");
		lbuf_strcat (&proc->buf, ptr);
	}
}

/* TODO: detect nesting */
S_API void spp_io(FILE *in, Output *out) {
	SppProc *p = out->proc? out->proc: proc;
	out->proc = p;
	char buf[4096];
	int lines;
	if (!p->buf.lbuf) {
		p->buf.lbuf = calloc (1, 4096);
		if (!p->buf.lbuf) {
			fprintf (stderr, "Out of memory.\n");
			return;
		}
		p->buf.lbuf_s = 4096;
	}
	p->buf.lbuf[0] = '\0';
	p->buf.lbuf_n = 0;
	while (!feof (in)) {
		buf[0] = '\0'; // ???
		if (!fgets (buf, sizeof (buf) - 1, in)) {
			break;
		}
		if (feof (in)) break;
		lines = 1;
		if (!memcmp (buf, "#!", 2)) {
			if (!fgets (buf, sizeof (buf) - 1, in) || feof (in)) {
				break;
			}
			lines++;
		}
		if (p->multiline) {
			while (1) {
				char *eol = buf + strlen (buf) - strlen (p->multiline);
				if (!strcmp (eol, p->multiline)) {
					D fprintf (stderr, "Multiline detected!\n");
					if (!fgets (eol, 1023, in)) {
						break;
					}
					if (feof (in)) {
						break;
					}
					lines++;
				} else {
					break;
				}
			}
		}
		spp_proc_eval (p, buf, out);
		p->state.lineno += lines;
	}
	(void)do_fputs (out, p->buf.lbuf);
}

S_API int spp_file(const char *file, Output *out) {
	FILE *in = r_sandbox_fopen (file, "r");
	D fprintf (stderr, "SPP-FILE(%s)\n", file);
	if (in) {
		spp_io (in, out);
		fclose (in);
		return 1;
	}
	fprintf (stderr, "Cannot find '%s'\n", file);
	return 0;
}

S_API void spp_proc_list_kw() {
	int i;
	for (i = 0; tags[i].name; i++) {
		printf ("%s\n", tags[i].name);
	}
}

S_API void spp_proc_list() {
	size_t i;
	for (i = 0; procs[i]; i++) {
		printf ("%s\n", procs[i]->name);
	}
}

S_API void spp_proc_set(SppProc *p, const char *arg, int fail) {
	size_t i;
	bool found = false;
	if (arg) {
		for (i = 0; procs[i]; i++) {
			if (!strcmp (procs[i]->name, arg)) {
				proc = procs[i];
				found = true;
				D printf ("SET PROC:(%s)(%s)\n", arg, proc->name);
				break;
			}
		}
	}
	if (arg && *arg && !procs[i] && fail) {
		fprintf (stderr, "Invalid preprocessor name '%s'\n", arg);
		return;
	}
	if (!found || !proc) {
		proc = p;
	}
	if (proc) {
		proc->state.lineno = 1;
		proc->state.ifl = 0;
		for (i = 0; i < MAXIFL; i++) {
			proc->state.echo[i] = proc->default_echo;
		}
		//args = (struct Arg*)proc->args;
		tags = (SppTag*)proc->tags;
	}
}

S_API const SppProc *spp_default_proc(void) {
	return &spp_proc;
}

S_API void out_printf(Output *out, char *str, ...) {
	va_list ap;
	va_start (ap, str);
	if (out->fout) {
		vfprintf (out->fout, str, ap);
	} else {
#if USE_R2
		r_strbuf_vappendf (out->cout, str, ap);
#else
		char tmp[4096];
		vsnprintf (tmp, sizeof (tmp), str, ap);
		tmp[sizeof (tmp) - 1] = 0;
		r_strbuf_append (out->cout, tmp);
#endif
	}
	va_end (ap);
}

static void spp_proc_init(SppProc *p) {
	p->state.lineno = 1;
	p->state.ifl = 0;
	size_t i;
	for (i = 0; i < MAXIFL; i++) {
		p->state.echo[i] = p->default_echo;
	}
}

S_API char *spp_eval_str(SppProc *p, const char *code) {
	SppProc local;
	if (!p) {
		local = *spp_default_proc ();
		local.buf = (SppBuf) {0};
		local.state.pipe_fd = NULL;
		local.state.switch_str = NULL;
		p = &local;
	}
	spp_proc_init (p);
	Output out = { .proc = p };
	out.fout = NULL;
	out.cout = r_strbuf_new (NULL);
	r_strbuf_init (out.cout);
	char *c = strdup (code);
	if (c) {
		spp_proc_eval (p, c, &out);
		free (c);
	}
	char *result = r_strbuf_drain (out.cout);
	free (p->state.switch_str);
	p->state.switch_str = NULL;
#if SPP_HAVE_SYSTEM
	if (p->state.pipe_fd) {
		pclose (p->state.pipe_fd);
		p->state.pipe_fd = NULL;
	}
#endif
	if (p == &local) {
		free (local.buf.lbuf);
	}
	return result;
}
