/********************************************
libmawk (C) 2009-2014, Tibor 'Igor2' Palinkas;

This is a source file for libmawk, an implementation of
the AWK programming language, fork of mawk.

Libmawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include "libmawk.h"
#include "debug.h"
#include "memory.h"
#include "sizes.h"
#include "array.h"
#include "fin.h"
#include "num.h"
#include "vio.h"
#include "vars.h"
#include "vio.h"
#include "vio_fifo.h"
#include "vio_orig.h"

mawk_state_t *libmawk_initialize_stage1(void)
{
	mawk_state_t *m;

	m = mawk_initialize_alloc();

	m->separate_begin = 1;
	m->suppress_undefined_function_warning = 1;
	m->no_program_ok = 1;
	return m;
}

mawk_state_t *libmawk_initialize_stage2(mawk_state_t *m, int argc, char *argv[])
{
	mawk_state_t *m2;
	char *argv_dummy[] = {
		"(null)",
		"-f",
		"/dev/null",
		NULL
	};

	if (argv == NULL) {
		argc = 3;
		argv = argv_dummy;
	}

	/* load and parse */
	m2 = mawk_initialize_argv(m, argc, argv);

	if (m2 == NULL) {
TODO(": cleanup m")
		return NULL;
	}
	m = m2;

	mawk_code_init(m);

	/* if the app failed to load scripts from argv[], add a dummy
	   empty BEGIN - if later the app feeds in some script, it will
	   be appended to the empty BEGIN, but there won't be any side
	   effect of this move. */
	if (*m->ps.buffp == '\0')
		strcpy((char *)m->ps.buffp, "BEGIN {}\n");

	mawk_parse(m);

	return m;
}

mawk_state_t *libmawk_initialize_stage3(mawk_state_t *m)
{

	if (m->execution_start == NULL)
		return NULL;

	m->mawk_state = EXECUTION;

	if (m->debug_symbols)
		mawk_debug_callstack_push(m, &mawk_debug_begin);

	mawk_execute(m, m->execution_start, m->eval_stack - 1, 0) ;

	if (m->debug_symbols)
		mawk_debug_callstack_pop(m);

	return m;
}

int libmawk_initialize_stdio(mawk_state_t *m, int stdin_apps, int stdout_apps, int stderr_apps)
{
	mawk_vio_t *vf;

	mawk_vio_orig_setup_stdio(m, stdin_apps, stdout_apps, stderr_apps);

	/* stdin is a fifo */
	if (!stdin_apps) {
		vf = mawk_vio_fifo_open(m, NULL, MAWK_VIO_I);
		mawk_file_register(m, "/dev/stdin", F_IN, vf);
	}

	if (!stdout_apps) {
		vf = mawk_vio_fifo_open(m, NULL, MAWK_VIO_O_APPEND);
		mawk_file_register(m, "/dev/stdout", F_APPEND, vf);
	}

	if (!stderr_apps) {
		vf = mawk_vio_fifo_open(m, NULL, MAWK_VIO_O_APPEND);
		mawk_file_register(m, "/dev/stderr", F_APPEND, vf);
	}

	/* file operation is handled by the orig vio */
	m->vio_init = mawk_vio_orig_init;
	return 0;
}

mawk_state_t *libmawk_initialize(int argc, char *argv[])
{
	mawk_state_t *m;

	m = libmawk_initialize_stage1();

	if (m == NULL)
		return NULL;

	/* stdout and stderr are bound to the process' stdout and stderr */
	libmawk_initialize_stdio(m, 0, 1, 1);

	m = libmawk_initialize_stage2(m, argc, argv);
	if (m != NULL)
		m = libmawk_initialize_stage3(m);

	return m;
}


void libmawk_run_main(mawk_state_t *m)
{
	/* don't run empty main */
	if (m->main_start == NULL)
		return;

	if (m->debug_symbols)
		mawk_debug_callstack_push(m, &mawk_debug_main);

	mawk_execute(m, m->main_start, m->eval_stack - 1, 0);

	if (m->debug_symbols)
		mawk_debug_callstack_pop(m);
}

void libmawk_uninitialize_stage1(mawk_state_t *m)
{
	INST exit0 = {_EXIT0};
	mawk_execute(m, &exit0, m->eval_stack - 1, 0);
}

void libmawk_uninitialize_stage2(mawk_state_t *m)
{
	libmawk_close_input(m);

	if (m->main_input != NULL)
		mawk_file_close_(m, m->main_input);

	mawk_uninitialize(m);
}


void libmawk_uninitialize(mawk_state_t *m)
{
	libmawk_uninitialize_stage1(m);
	libmawk_uninitialize_stage2(m);
}

/* callback from mawk */
void mawk_exit_(mawk_state_t *MAWK, int x)
{
	/* we do not really exit */
	MAWK->final_exit_code = MAWK->rt_exit_code != 0 ? MAWK->rt_exit_code : x;
	MAWK->wants_to_exit = 1;
}

int libmawk_append_ninput(mawk_state_t *m, const char *input, int len)
{
	mawk_vio_t *vf;
	vf = m->fnode_stdin->vf;
	if (vf == NULL)
		return -1;
	return mawk_vio_fifo_write_app(m, vf, input, len);
}

int libmawk_append_input(mawk_state_t *m, const char *input_str)
{
	return libmawk_append_ninput(m, input_str, strlen(input_str));
}

int libmawk_close_input(mawk_state_t *m)
{
	mawk_vio_t *vf;
	if (m->fnode_stdin == NULL)
		return -1;
	vf = m->fnode_stdin->vf;
	if (vf == NULL)
		return -1;
	if (vf->imp->vclose == mawk_vio_fifo_imp.vclose)
		mawk_vio_fifo_eof_from_app(m, vf);
	return 0;
}

mawk_cell_t *libmawk_set_cellv(mawk_state_t *MAWK, mawk_cell_t *cell, const char argtype, va_list *ap)
{
	char *s;
	int i;
	mawk_num_t d;

	switch(argtype) {
		case 's':
			s = va_arg((*ap), char *);
			cell->type = C_STRING ;
			cell->ptr = mawk_new_STRING(MAWK, s);
			break;
		case 'd':
			i = va_arg((*ap), int);
			cell->type = C_NUM ;
			cell->d.dval = i;
			break;
#ifndef MAWK_NO_FLOAT
		case 'f':
			d = va_arg((*ap), double);
			cell->type = C_NUM;
			cell->d.dval = d;
			break;
#endif
		default:
			return NULL;
	}
	return cell;
}

mawk_cell_t *libmawk_set_cellp(mawk_state_t *MAWK, mawk_cell_t *cell, const char argtype, void *argp)
{
	char *s;
	int i;
	mawk_num_t d;

	switch(argtype) {
		case 's':
			s = (char *)argp;
			cell->type = C_STRING ;
			cell->ptr = mawk_new_STRING(MAWK, s);
			break;
		case 'd':
			i = *(int *)argp;
			cell->type = C_NUM ;
			cell->d.dval = i;
			break;
#ifndef MAWK_NO_FLOAT
		case 'f':
			d = *(double *)argp;
			cell->type = C_NUM ;
			cell->d.dval = d;
			break;
#endif
		default:
			return NULL;
	}
	return cell;
}

mawk_cell_t *libmawk_set_cell(mawk_state_t *MAWK, mawk_cell_t *cell, const char argtype, ...)
{
	va_list ap;
	mawk_cell_t *ret;

	va_start(ap, argtype);
	ret = libmawk_set_cellv(MAWK, cell, argtype, &ap);
	va_end(ap);
	return ret;
}


mawk_exec_result_t libmawk_call_function(mawk_state_t *MAWK, const char *fname, mawk_cell_t *retc, const char *argtypes, ...)
{
	va_list ap;
	int numargs;
	SYMTAB *fs;
	mawk_cell_t *ret, *tmp;
	mawk_cell_t *orig_sp;
	FBLOCK *fbp;
	FBLOCK fbp_c;

	if (retc != NULL)
		mawk_cell_destroy(MAWK, retc);

	fs = mawk_find(MAWK, fname, 0);
	if ((fs == NULL) || ((fs->type != ST_FUNCT) && (fs->type != ST_C_FUNCTION))) {
		/* does not exist or not a function */
		return -1;
	}

	if (fs->type == ST_C_FUNCTION) {
		fbp = &fbp_c;
		fbp_c.name = fs->name;
		fbp_c.code = NULL;
	}
	else
		fbp = fs->stval.fbp;

TODO(": check if we need to grow the stack")

	orig_sp = MAWK->sp;
	va_start(ap, argtypes);
	for(numargs = 0;*argtypes != '\0';argtypes++,numargs++) {
		inc_mawksp();

		if (libmawk_set_cellv(MAWK, MAWK->sp, *argtypes, &ap) == NULL)
			goto err_cleanup;
	}
	va_end(ap);
	return mawk_call(MAWK, fbp, numargs, retc);

	err_cleanup:;
	va_end(ap);
	for(MAWK->sp--; MAWK->sp > orig_sp; MAWK->sp--)
		mawk_cell_destroy(MAWK, MAWK->sp);

	MAWK->sp = orig_sp;
	return -1;
}

mawk_exec_result_t libmawk_call_functionp(mawk_state_t *MAWK, const char *fname, mawk_cell_t *retc, const char *argtypes, void **args)
{
	int numargs;
	SYMTAB *fs;
	mawk_cell_t  *ret, *tmp;
	mawk_cell_t *orig_sp;
	FBLOCK *fbp;
	FBLOCK fbp_c;

	if (retc != NULL)
		mawk_cell_destroy(MAWK, retc);

	fs = mawk_find(MAWK, fname, 0);
	if ((fs == NULL) || ((fs->type != ST_FUNCT) && (fs->type != ST_C_FUNCTION))) {
		/* does not exist or not a function */
		return -1;
	}

	if (fs->type == ST_C_FUNCTION) {
		fbp = &fbp_c;
		fbp_c.name = fs->name;
		fbp_c.code = NULL;
	}
	else
		fbp = fs->stval.fbp;

TODO(": check if we need to grow the stack")

	orig_sp = MAWK->sp;
	for(numargs = 0;*argtypes != '\0';argtypes++,numargs++,args++) {
		inc_mawksp();
		if (libmawk_set_cellp(MAWK, MAWK->sp, *argtypes, *args) == NULL)
			goto err_cleanup;
	}
	return mawk_call(MAWK, fs->stval.fbp, numargs, retc);

	err_cleanup:;
	for(MAWK->sp--; MAWK->sp > orig_sp; MAWK->sp--)
		mawk_cell_destroy(MAWK, MAWK->sp);

	MAWK->sp = orig_sp;
	return -1;

}

mawk_exec_result_t libmawk_call_functionc(mawk_state_t *MAWK, const char *fname, mawk_cell_t *retc, int argc, const mawk_cell_t *argv)
{
	int n;
	SYMTAB *fs;
	mawk_cell_t  *ret, *tmp;
	mawk_cell_t *orig_sp;
	FBLOCK *fbp;
	FBLOCK fbp_c;

	if (retc != NULL)
		mawk_cell_destroy(MAWK, retc);

	fs = mawk_find(MAWK, fname, 0);
	if ((fs == NULL) || ((fs->type != ST_FUNCT) && (fs->type != ST_C_FUNCTION))) {
		/* does not exist or not a function */
		return -1;
	}

	if (fs->type == ST_C_FUNCTION) {
		fbp = &fbp_c;
		fbp_c.name = fs->name;
		fbp_c.code = NULL;
	}
	else
		fbp = fs->stval.fbp;

TODO(": check if we need to grow the stack")

	for(n = 0; n< argc; n++) {
		inc_mawksp();
		mawk_cellcpy(MAWK, MAWK->sp, &argv[n]);
	}
	return mawk_call(MAWK, fs->stval.fbp, argc, retc);
}


void libmawk_cell_destroy(mawk_state_t *MAWK, mawk_cell_t *c)
{
	mawk_cell_destroy(MAWK, c);
	c->type = C_NOINIT;
}

char *libmawk_print_cell(mawk_state_t *MAWK, const mawk_cell_t *c, char *buff, int size)
{
	char tmp[128];
	int len;

	if ((buff == NULL) || (size < 1))
		return NULL;
	*buff = '\0';

	switch(c->type) {
		case C_NUM:
#ifdef MAWK_NO_FLOAT
			len = sprintf(tmp, "%d", (int)c->d.dval);
#else
			if (c->d.dval == (int)c->d.dval)
				len = sprintf(tmp, "%d", (int)c->d.dval);
			else
				len = sprintf(tmp, "%f", c->d.dval);
#endif
			if (len > size-1)
				len = size-1;
			goto copy_tmp;
		case C_STRING:
			strncpy(buff, ((mawk_string_t *)(c->ptr))->str, size);
			buff[size-1] = '\0';
			break;
		
		case C_NOINIT:
			/* should be empty string just as in awk */
			break;
		case C_STRNUM:
		case C_MBSTRN:
TODO(": we should be able to convert the above two")
		case C_RE:
		case C_SPACE:
		case C_SNULL:
		case C_REPL:
		case C_REPLV:
			len = sprintf(buff, "Can't convert celltype %d\n", c->type);
			break;
		default:
			len = sprintf(buff, "Invalid celltype %d\n", c->type);
			break;
			
	}
	return buff;

	copy_tmp:;
	memcpy(buff, tmp, len);
	buff[len] = '\0';
	return buff;
}


const mawk_cell_t *libmawk_get_var(mawk_state_t *MAWK, const char *vname)
{
	return mawk_get_var(MAWK, vname);
}

static mawk_array_t array_prep_idx(mawk_state_t *MAWK, const char *arr_name, const char *idx, mawk_cell_t *idxc)
{
	SYMTAB *fs;
	fs = mawk_find(MAWK, arr_name, 0);

	/* does symbol exist at all? */
	if (fs == NULL)
		return NULL;

	/* exit if not an array */
	if (fs->type != ST_ARRAY)
		return NULL;

	idxc->type = C_STRING;
	idxc->ptr  = (PTR) mawk_new_STRING(MAWK, idx);

	return (mawk_array_t)fs->stval.cp;
}

int libmawk_get_array_at(mawk_state_t *MAWK, const char *arr_name, const char *idx, mawk_cell_t *result, int create)
{
	mawk_array_t arr;
	mawk_cell_t idxc;
	int res;


	if (result != NULL) {
		mawk_cell_destroy(MAWK, result);
		result->type = C_NOINIT;
	}

	arr = array_prep_idx(MAWK, arr_name, idx, &idxc);
	if (arr == NULL)
		return -1;

	res = mawk_array_find(MAWK, arr, &idxc, result, create);
	mawk_cell_destroy(MAWK, &idxc);
	return res;
}

int libmawk_set_array_atv(mawk_state_t *MAWK, const char *arr_name, const char *idx, const char valtype, va_list *ap)
{
	mawk_array_t arr;
	mawk_cell_t idxc, valc = libmawk_empty_cell;

	arr = array_prep_idx(MAWK, arr_name, idx, &idxc);
	if (arr == NULL)
		return -1;

	libmawk_set_cellv(MAWK, &valc, valtype, ap);

	mawk_array_set(MAWK, arr, &idxc, &valc);
	mawk_cell_destroy(MAWK, &idxc);
	mawk_cell_destroy(MAWK, &valc);
	return 0;
}

int libmawk_set_array_atp(mawk_state_t *MAWK, const char *arr_name, const char *idx, const char valtype, void *val)
{
	mawk_array_t arr;
	mawk_cell_t idxc, valc = libmawk_empty_cell;

	arr = array_prep_idx(MAWK, arr_name, idx, &idxc);
	if (arr == NULL)
		return -1;

	libmawk_set_cellp(MAWK, &valc, valtype, val);

	mawk_array_set(MAWK, arr, &idxc, &valc);
	mawk_cell_destroy(MAWK, &idxc);
	mawk_cell_destroy(MAWK, &valc);
	return 0;
}

int libmawk_set_array_at(mawk_state_t *MAWK, const char *arr_name, const char *idx, const char valtype, ...)
{
	va_list ap;
	int ret;

	va_start(ap, valtype);
	ret = libmawk_set_array_atv(MAWK, arr_name, idx, valtype, &ap);
	va_end(ap);
	return ret;
}

int libmawk_set_scalarv(mawk_state_t *MAWK, const char *var_name, const char valtype, va_list *ap)
{
	mawk_cell_t *c;
	c = mawk_get_var(MAWK, var_name);
	if (c == NULL)
		return -1;
	libmawk_set_cellv(MAWK, c, valtype, ap);
	return 0;
}

int libmawk_set_scalarp(mawk_state_t *MAWK, const char *var_name, const char valtype, void *val)
{
	mawk_cell_t *c;
	c = mawk_get_var(MAWK, var_name);
	if (c == NULL)
		return -1;
	libmawk_set_cellp(MAWK, c, valtype, val);
	return 0;
}

int libmawk_set_scalar(mawk_state_t *MAWK, const char *var_name, const char valtype, ...)
{
	va_list ap;
	int ret;

	va_start(ap, valtype);
	ret = libmawk_set_scalarv(MAWK, var_name, valtype, &ap);
	va_end(ap);
	return ret;
}


int libmawk_register_function(mawk_state_t *MAWK, const char *fname, libmawk_c_function *callback)
{
	SYMTAB *sym;
	sym = mawk_find(MAWK, fname, 0);

	if (sym != NULL) {
		/* special case: we already know symbol is a function but body is empty (typical for c calls) */
		if ((sym->type == ST_FUNCT) && (sym->stval.fbp->code == NULL))
			sym->type = ST_NONE;

		/* if symbol is already defined as something else, return error */
		if (sym->type != ST_NONE)
			return 1;
	}
	else
		sym = mawk_find(MAWK, mawk_strdup(MAWK, fname), 1);

	sym->type = ST_C_FUNCTION;
	sym->stval.c_function.callback = callback;
	sym->stval.c_function.func_userdata = MAWK->func_userdata;
	return 0;
}

mawk_cell_t *libmawk_cfunc_arg(mawk_cell_t *sp, int num_args, int n)
{
	if ((n >= 0) && (n < num_args))
		return sp - (num_args - n - 1);
	return NULL;
}

mawk_cell_t *libmawk_cfunc_ret(mawk_cell_t *sp, int num_args)
{
	return sp - num_args + 1;
}

SYMTAB *libmawk_register_array(mawk_state_t *MAWK, const char *name, array_imp_t *arr_imp)
{
	SYMTAB *s;

	/* register a variable only if it's not already in the hash */
	s = mawk_find(MAWK, name, 0);
	if (s != NULL)
		return NULL;

	s = mawk_insert(MAWK, mawk_strdup(MAWK, name));
	memset(&(s->stval), 0, sizeof(s->stval));
	s->type = ST_ARRAY;
	s->offset = 0;
	s->stval.array = mawk_array_new(MAWK, arr_imp);
	return s;
}

SYMTAB *libmawk_register_scalar(mawk_state_t *MAWK, const char *name, mawk_celltype_t type, void *val)
{
	SYMTAB *s;

	switch(type) {
		case C_NUM:
		case C_STRING:
			break;
		default:
			return NULL;
	}

	/* register a variable only if it's not already in the hash */
	s = mawk_find(MAWK, name, 0);
	if (s != NULL)
		return NULL;

	s = mawk_insert(MAWK, mawk_strdup(MAWK, name));
	memset(&(s->stval), 0, sizeof(s->stval));
	s->type = ST_VAR;
	s->offset = 0;
	s->stval.cp = MAWK_ZMALLOC(MAWK, mawk_cell_t);
	s->stval.cp->type = type;
	switch(type) {
		case C_NUM:
			if (val != NULL)
				s->stval.cp->d.dval = *(mawk_num_t *)val;
			else
				s->stval.cp->d.dval = MAWK_NUM_ZERO;
			break;
		case C_STRING:
			if (val != NULL)
				s->stval.cp->ptr = (void *)mawk_new_STRING(MAWK, (char *)val);
			else
				s->stval.cp->ptr = (void *)mawk_new_STRING(MAWK, "");
			break;
		default:
			mawk_bozo(MAWK, "libmawk_register_scalar: unsupported type");
	}
	return s;
}

mawk_num_t libmawk_cell2num(mawk_state_t *MAWK, const mawk_cell_t *cp)
{
	mawk_cell_t tmp;
	mawk_cellcpy(MAWK, &tmp, cp);
	mawk_cast1_to_num(MAWK, &tmp);
	/* NOTE: no need to destroy tmp: it's a number for sure, numbers are not allocated */
	return tmp.d.dval;
}


int libmawk_cell2int(mawk_state_t *MAWK, const mawk_cell_t *cp)
{
	return (int)libmawk_cell2num(MAWK, cp);
}

double libmawk_cell2double(mawk_state_t *MAWK, const mawk_cell_t *cp)
{
	return (double)libmawk_cell2num(MAWK, cp);
}

