var astree = require('./ast.js');
var err = require('./error.js');
var edefs = require('./eval_defs.js');

function get_argtype(node, key) {
	/*
	*  Get the type of 'arg' as defined in edefs.ARGTYPES.
	*/
	let arg = node.data[key];

	// Inner text nodes are always of type rstr.
	if (node.name === '__inner__') {
		return edefs.ARGTYPES.rstr;
	}

	if (arg.match(/^[0-9]+\%$/)) {
		return edefs.ARGTYPES.percent;
	} else if (arg.match(/^[0-9]*$/)) {
		return edefs.ARGTYPES.int;
	} else if (arg.match(/^'.*'$/) || arg.match(/^".*"$/)) {
		return edefs.ARGTYPES.str;
	} else {
		return edefs.ARGTYPES.rstr;
	}
}

function chk_args(node) {
	/*
	*  Check for missing or extra node arguments and check
	*  accepted argument values.
	*/
	let expr = edefs.EXPRESSIONS[node.name];
	let atype = null;
	let typestr = '';

	for (let a in node.data) {
		if (!(a in expr.has)) {
			throw new err.MarkupSyntaxError(
				'EARG',
				node.lexeme,
				`Extra argument '${a}' in tag '${node.name}'.`
			);
		}
		atype = get_argtype(node, a);
		if (expr.has[a].code != atype.code) {
			throw new err.MarkupSyntaxError(
				'ETYPE',
				node.lexeme,
				`Invalid type '${atype.typestr}' for '${a}'. ` +
				`Expected '${expr.has[a].typestr}'.`
			);
		}

		// Check accepted values.
		if (expr.accept && !(expr.accept[a].includes(node.data[a]))) {
			throw new err.MarkupSyntaxError(
				'EVAL',
				node.lexeme,
				`Invalid value '${node.data[a]}' for '${a}'. ` +
				`Expected one of '${expr.accept[a].join(', ')}'.`
			);
		}
	}
	for (let a in expr.has) {
		if (!node.data || !(a in node.data)) {
			throw new err.MarkupSyntaxError(
				'EARG',
				node.lexeme,
				`Missing argument '${a}' in tag '${node.name}'.`
			);
		}
	}
}

function eval_node(node) {
	/*
	*  Recursively evaluate 'node' and return the
	*  resulting string.
	*/
	let buf = '';
	let expr = null;
	let out = null;

	if (!(node.name in edefs.EXPRESSIONS)) {
		throw new err.MarkupSyntaxError(
			'ETAG',
			node.lexeme,
			node.name
		);
	}
	expr = edefs.EXPRESSIONS[node.name];
	chk_args(node);

	out = expr.handle(node);
	buf += out[0].replace(/\t+/g, "\t");
	for (let n of node.nodes) {
		buf += eval_node(n);
	}
	buf += out[1].replace(/\t+/g, "\t");
	return buf;
}

module.exports.eval = function(ast) {
	/*
	*  Evaluate the Abstract Syntax Tree object 'ast'
	*  and return the resulting string.
	*/
	return eval_node(ast.nodes);
}
