/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer - low level gerber parser
 *  Copyright (C) 2019 Tibor 'Igor2' Palinkas
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include <librnd/config.h>

#include "gexpr.h"

#define STACK_MAX 128

#define PUSH(num, is_prm) \
do { \
	if (sp == sizeof(stack)) \
		return GEEE_STACK_OVERFLOW; \
	stack[sp] = (num); \
	is_param[sp] = is_prm; \
	sp++; \
} while(0)

#define POP_(res) \
do { \
	if (sp == 0) \
		return GEEE_STACK_UNDERFLOW; \
	sp--; \
	res = stack[sp]; \
} while(0)

#define POP(res) \
do { \
	if (sp == 0) \
		return GEEE_STACK_UNDERFLOW; \
	sp--; \
	res = stack[sp]; \
	if (is_param[sp]) { \
		double *__d__; \
		__d__ = vtd0_get(params, res-1, 0); \
		if (__d__ == NULL) \
			res = 0; \
		else \
			res = *__d__; \
	} \
} while(0)

#define POP_REQ(res, req_prm) \
do { \
	POP_(res); \
	if (is_param[sp] != req_prm) \
		return GEEE_INTERNAL; \
} while(0)

ge_expr_err_t gex_eval(ge_expr_prg_t *prg, vtd0_t *params, double *res)
{
	char is_param[STACK_MAX];
	double stack[STACK_MAX];
	double o1, o2;
	int sp = 0; /* next stack item to use, length of the stack */
	double *d;

	for(;prg != NULL; prg = prg->next) {
		switch(prg->inst) {
			case PUSH_NUM:
				PUSH(prg->payload, 0);
				break;
			case PUSH_PARAM:
				PUSH(prg->payload, 1);
				break;
			case SET:
				d = vtd0_get(params, prg->payload-1, 1);
				POP_REQ(*d, 0);
				break;
			case ADD:
				POP(o1); POP(o2);
				PUSH(o2+o1, 0);
				break;
			case SUB:
				POP(o1); POP(o2);
				PUSH(o2-o1, 0);
				break;
			case MUL:
				POP(o1); POP(o2);
				PUSH(o2*o1, 0);
				break;
			case DIV:
				POP(o1); POP(o2);
				if (o1 == 0)
					return GEEE_DIV0;
				PUSH(o2/o1, 0);
				break;
		}
	}
	POP(o1);
	*res = o1;
	return GEEE_SUCCESS;
}

static ge_expr_prg_t *gex_append_(ge_expr_prglist_t *ctx)
{
	ge_expr_prg_t *ex = malloc(sizeof(ge_expr_prg_t));
	if (ctx->last != NULL) {
		ctx->last->next = ex;
		ctx->last = ex;
	}
	else
		ctx->first = ctx->last = ex;

	ex->next = NULL;
	return ex;
}

void gex_append(ge_expr_prglist_t *ctx, gexi_t inst, double payload)
{
	ge_expr_prg_t *ex = gex_append_(ctx);
	ex->inst = inst;
	ex->payload = payload;
}

void gex_append_idx(ge_expr_prglist_t *ctx, gexi_t inst, int payload)
{
	ge_expr_prg_t *ex = gex_append_(ctx);
	ex->inst = inst;
	ex->payload = payload;
}

void gex_free_prg(ge_expr_prg_t *prg)
{
	ge_expr_prg_t *next;
	for(; prg != NULL; prg = next) {
		next = prg->next;
		free(prg);
	}
}
