/******************************************************************************
 * Copyright (C) 2018-2021 Dibyendu Majumdar
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************/
#include <parser.h>

const char *raviX_get_type_name(ravitype_t tt)
{
	switch (tt) {
	case RAVI_TANY:
		return "any";
	case RAVI_TNIL:
		return "nil";
	case RAVI_TBOOLEAN:
		return "boolean";
	case RAVI_TNUMFLT:
		return "number";
	case RAVI_TNUMINT:
		return "integer";
	case RAVI_TTABLE:
		return "table";
	case RAVI_TSTRING:
		return "string";
	case RAVI_TARRAYINT:
		return "integer[]";
	case RAVI_TARRAYFLT:
		return "number[]";
	case RAVI_TFUNCTION:
		return "closure";
	case RAVI_TUSERDATA:
		return "userdata";
	default:
		return "?";
	}
}

static void printf_buf(TextBuffer *buf, const char *format, ...)
{
	static const char *PADDING = "                                                                                ";
	char tbuf[128] = {0};
	va_list ap;
	const char *cp;
	va_start(ap, format);
	for (cp = format; *cp; cp++) {
		if (cp[0] == '%' && cp[1] == 'p') { /* padding */
			int level = va_arg(ap, int);
			snprintf(tbuf, sizeof tbuf, "%.*s", level, PADDING);
			raviX_buffer_add_string(buf, tbuf);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 't') { /* string_object */
			const StringObject *s = va_arg(ap, const StringObject *);
			raviX_buffer_add_string(buf, s->str);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 'T') { /* VariableType */
			const VariableType *type;
			type = va_arg(ap, const VariableType *);
			if (type->type_code == RAVI_TUSERDATA) {
				const StringObject *s = type->type_name;
				raviX_buffer_add_string(buf, s->str);
			} else {
				raviX_buffer_add_string(buf, raviX_get_type_name(type->type_code));
			}
			cp++;
		} else if (cp[0] == '%' && cp[1] == 's') { /* const char * */
			const char *s;
			s = va_arg(ap, const char *);
			raviX_buffer_add_string(buf, s);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 'c') { /* comment */
			const char *s;
			s = va_arg(ap, const char *);
			raviX_buffer_add_fstring(buf, "--%s", s);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 'i') { /* integer */
			lua_Integer i;
			i = va_arg(ap, lua_Integer);
			raviX_buffer_add_longlong(buf, i);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 'f') { /* float */
			double d;
			d = va_arg(ap, double);
			raviX_buffer_add_fstring(buf, "%.16f", d);
			cp++;
		} else if (cp[0] == '%' && cp[1] == 'b') { /* boolean */
			lua_Integer i;
			i = va_arg(ap, lua_Integer);
			raviX_buffer_add_bool(buf, i != 0);
			cp++;
		} else {
			raviX_buffer_add_char(buf, *cp);
		}
	}
	va_end(ap);
}

static void print_ast_node_list(TextBuffer *buf, AstNodeList *list, int level, const char *delimiter)
{
	AstNode *node;
	bool is_first = true;
	FOR_EACH_PTR(list, node)
	{
		if (is_first)
			is_first = false;
		else if (delimiter)
			printf_buf(buf, "%p%s\n", level, delimiter);
		raviX_print_ast_node(buf, node, level + 1);
	}
	END_FOR_EACH_PTR(node);
}

static void print_statement_list(TextBuffer *buf, AstNodeList *statement_list, int level)
{
	print_ast_node_list(buf, statement_list, level + 1, NULL);
}

static inline const char *get_as_str(const StringObject *ts) { return ts ? ts->str : ""; }

static void print_symbol(TextBuffer *buf, LuaSymbol *sym, int level)
{
	switch (sym->symbol_type) {
	case SYM_ENV: {
		printf_buf(buf, "%p%t %c %s %s\n", level, sym->variable.var_name, "_ENV",
			   raviX_get_type_name(sym->variable.value_type.type_code), get_as_str(sym->variable.value_type.type_name));
		break;
	}
	case SYM_GLOBAL: {
		printf_buf(buf, "%p%t %c %s %s\n", level, sym->variable.var_name, "global symbol",
			   raviX_get_type_name(sym->variable.value_type.type_code), get_as_str(sym->variable.value_type.type_name));
		break;
	}
	case SYM_LOCAL: {
		printf_buf(buf, "%p%t %c %s %s\n", level, sym->variable.var_name, "local symbol",
			   raviX_get_type_name(sym->variable.value_type.type_code), get_as_str(sym->variable.value_type.type_name));
		break;
	}
	case SYM_UPVALUE: {
		printf_buf(buf, "%p%t %c %s %s\n", level, sym->upvalue.target_variable->variable.var_name, "upvalue",
			   raviX_get_type_name(sym->upvalue.target_variable->variable.value_type.type_code),
			   get_as_str(sym->upvalue.target_variable->variable.value_type.type_name));
		break;
	}
	default:
		assert(0);
	}
}

static void print_symbol_name(TextBuffer *buf, LuaSymbol *sym)
{
	switch (sym->symbol_type) {
	case SYM_LOCAL:
	case SYM_ENV:
	case SYM_GLOBAL: {
		printf_buf(buf, "%t", sym->variable.var_name);
		break;
	}
	case SYM_UPVALUE: {
		if (sym->upvalue.target_variable->symbol_type == SYM_ENV) {
			printf_buf(buf, "%t*", sym->upvalue.target_variable->variable.var_name);
		}
		else {
			printf_buf(buf, "%t", sym->upvalue.target_variable->variable.var_name);
		}
		break;
	}
	default:
		assert(0);
	}
}

static void print_symbol_list(TextBuffer *buf, LuaSymbolList *list, int level, const char *delimiter)
{
	LuaSymbol *node;
	bool is_first = true;
	FOR_EACH_PTR(list, node)
	{
		if (is_first)
			is_first = false;
		else if (delimiter)
			printf_buf(buf, "%p%s\n", level, delimiter);
		print_symbol(buf, node, level + 1);
	}
	END_FOR_EACH_PTR(node);
}

static void print_symbol_names(TextBuffer *buf, LuaSymbolList *list)
{
	LuaSymbol *node;
	bool is_first = true;
	FOR_EACH_PTR(list, node)
	{
		if (is_first)
			is_first = false;
		else
			printf_buf(buf, ", ");
		print_symbol_name(buf, node);
	}
	END_FOR_EACH_PTR(node);
}

const char *raviX_get_unary_opr_str(UnaryOperatorType op)
{
	switch (op) {
	case UNOPR_NOT:
		return "not";
	case UNOPR_MINUS:
		return "-";
	case UNOPR_BNOT:
		return "~";
	case UNOPR_LEN:
		return "#";
	case UNOPR_TO_INTEGER:
		return "@integer";
	case UNOPR_TO_NUMBER:
		return "@number";
	case UNOPR_TO_INTARRAY:
		return "@integer[]";
	case UNOPR_TO_NUMARRAY:
		return "@number[]";
	case UNOPR_TO_TABLE:
		return "@table";
	case UNOPR_TO_CLOSURE:
		return "@closure";
	case UNOPR_TO_STRING:
		return "@string";
	case UNOPR_TO_TYPE:
		return "@<usertype>";
	default:
		return "";
	}
}

const char *raviX_get_binary_opr_str(BinaryOperatorType op)
{
	switch (op) {
	case BINOPR_ADD:
		return "+";
	case BINOPR_SUB:
		return "-";
	case BINOPR_MUL:
		return "*";
	case BINOPR_MOD:
		return "%";
	case BINOPR_POW:
		return "^";
	case BINOPR_DIV:
		return "/";
	case BINOPR_IDIV:
		return "//";
	case BINOPR_BAND:
		return "&";
	case BINOPR_BOR:
		return "|";
	case BINOPR_BXOR:
		return "~";
	case BINOPR_SHL:
		return "<<";
	case BINOPR_SHR:
		return ">>";
	case BINOPR_CONCAT:
		return "..";
	case BINOPR_NE:
		return "~=";
	case BINOPR_EQ:
		return "==";
	case BINOPR_LT:
		return "<";
	case BINOPR_LE:
		return "<=";
	case BINOPR_GT:
		return ">";
	case BINOPR_GE:
		return ">=";
	case BINOPR_AND:
		return "and";
	case BINOPR_OR:
		return "or";
	default:
		return "";
	}
}

void raviX_print_ast_node(TextBuffer *buf, AstNode *node, int level)
{
	switch (node->type) {
	case EXPR_FUNCTION: {
		if (node->function_expr.args) {
			printf_buf(buf, "%pfunction(\n", level);
			print_symbol_list(buf, node->function_expr.args, level + 1, ",");
			printf_buf(buf, "%p)\n", level);
		} else {
			printf_buf(buf, "%pfunction()\n", level);
		}
		if (node->function_expr.locals) {
			printf_buf(buf, "%p%c ", level, "locals ");
			print_symbol_names(buf, node->function_expr.locals);
			printf_buf(buf, "\n");
		}
		if (node->function_expr.upvalues) {
			printf_buf(buf, "%p%c ", level, "upvalues ");
			print_symbol_names(buf, node->function_expr.upvalues);
			printf_buf(buf, "\n");
		}
		print_statement_list(buf, node->function_expr.function_statement_list, level);
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case AST_NONE:
		break;
	case STMT_RETURN: {
		printf_buf(buf, "%preturn\n", level);
		print_ast_node_list(buf, node->return_stmt.expr_list, level + 1, ",");
		break;
	}
	case STMT_LOCAL: {
		printf_buf(buf, "%plocal\n", level);
		printf_buf(buf, "%p%c\n", level, "[symbols]");
		print_symbol_list(buf, node->local_stmt.var_list, level + 1, ",");
		if (node->local_stmt.expr_list) {
			printf_buf(buf, "%p%c\n", level, "[expressions]");
			print_ast_node_list(buf, node->local_stmt.expr_list, level + 1, ",");
		}
		break;
	}
	case STMT_FUNCTION: {
		raviX_print_ast_node(buf, node->function_stmt.name, level);
		if (node->function_stmt.selectors) {
			printf_buf(buf, "%p%c\n", level + 1, "[selectors]");
			print_ast_node_list(buf, node->function_stmt.selectors, level + 2, NULL);
		}
		if (node->function_stmt.method_name) {
			printf_buf(buf, "%p%c\n", level + 1, "[method name]");
			raviX_print_ast_node(buf, node->function_stmt.method_name, level + 2);
		}
		printf_buf(buf, "%p=\n", level + 1);
		raviX_print_ast_node(buf, node->function_stmt.function_expr, level + 2);
		break;
	}
	case STMT_LABEL: {
		printf_buf(buf, "%p::%t::\n", level, node->label_stmt.symbol->label.label_name);
		break;
	}
	case STMT_GOTO: {
		printf_buf(buf, "%pgoto %t\n", level, node->goto_stmt.name);
		break;
	}
	case STMT_DO: {
		printf_buf(buf, "%pdo\n", level);
		print_ast_node_list(buf, node->do_stmt.do_statement_list, level + 1, NULL);
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case STMT_EXPR: {
		printf_buf(buf, "%p%c\n", level, "[expression statement start]");
		if (node->expression_stmt.var_expr_list) {
			printf_buf(buf, "%p%c\n", level + 1, "[var list start]");
			print_ast_node_list(buf, node->expression_stmt.var_expr_list, level + 2, ",");
			printf_buf(buf, "%p= %c\n", level + 1, "[var list end]");
		}
		printf_buf(buf, "%p%c\n", level + 1, "[expression list start]");
		print_ast_node_list(buf, node->expression_stmt.expr_list, level + 2, ",");
		printf_buf(buf, "%p%c\n", level + 1, "[expression list end]");
		printf_buf(buf, "%p%c\n", level, "[expression statement end]");
		break;
	}
	case STMT_IF: {
		AstNode *test_then_block;
		bool is_first = true;
		FOR_EACH_PTR(node->if_stmt.if_condition_list, test_then_block)
		{
			if (is_first) {
				is_first = false;
				printf_buf(buf, "%pif\n", level);
			} else
				printf_buf(buf, "%pelseif\n", level);
			raviX_print_ast_node(buf, test_then_block->test_then_block.condition, level + 1);
			printf_buf(buf, "%pthen\n", level);
			print_ast_node_list(buf, test_then_block->test_then_block.test_then_statement_list, level + 1,
					    NULL);
		}
		END_FOR_EACH_PTR(node);
		if (node->if_stmt.else_block) {
			printf_buf(buf, "%pelse\n", level);
			print_ast_node_list(buf, node->if_stmt.else_statement_list, level + 1, NULL);
		}
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case STMT_WHILE: {
		printf_buf(buf, "%pwhile\n", level);
		raviX_print_ast_node(buf, node->while_or_repeat_stmt.condition, level + 1);
		printf_buf(buf, "%pdo\n", level);
		print_ast_node_list(buf, node->while_or_repeat_stmt.loop_statement_list, level + 1, NULL);
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case STMT_REPEAT: {
		printf_buf(buf, "%prepeat\n", level);
		print_ast_node_list(buf, node->while_or_repeat_stmt.loop_statement_list, level + 1, NULL);
		printf_buf(buf, "%puntil\n", level);
		raviX_print_ast_node(buf, node->while_or_repeat_stmt.condition, level + 1);
		printf_buf(buf, "%p%c\n", level, "[repeat end]");
		break;
	}
	case STMT_FOR_IN: {
		printf_buf(buf, "%pfor\n", level);
		print_symbol_list(buf, node->for_stmt.symbols, level + 1, ",");
		printf_buf(buf, "%pin\n", level);
		print_ast_node_list(buf, node->for_stmt.expr_list, level + 1, ",");
		printf_buf(buf, "%pdo\n", level);
		print_statement_list(buf, node->for_stmt.for_statement_list, level + 1);
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case STMT_FOR_NUM: {
		printf_buf(buf, "%pfor\n", level);
		print_symbol_list(buf, node->for_stmt.symbols, level + 1, NULL);
		printf_buf(buf, "%p=\n", level);
		print_ast_node_list(buf, node->for_stmt.expr_list, level + 1, ",");
		printf_buf(buf, "%pdo\n", level);
		print_statement_list(buf, node->for_stmt.for_statement_list, level + 1);
		printf_buf(buf, "%pend\n", level);
		break;
	}
	case EXPR_SUFFIXED: {
		printf_buf(buf, "%p%c %T\n", level, "[suffixed expr start]", &node->suffixed_expr.type);
		printf_buf(buf, "%p%c %T\n", level + 1, "[primary start]",
			   &node->suffixed_expr.primary_expr->common_expr.type);
		raviX_print_ast_node(buf, node->suffixed_expr.primary_expr, level + 2);
		printf_buf(buf, "%p%c\n", level + 1, "[primary end]");
		if (node->suffixed_expr.suffix_list) {
			printf_buf(buf, "%p%c\n", level + 1, "[suffix list start]");
			print_ast_node_list(buf, node->suffixed_expr.suffix_list, level + 2, NULL);
			printf_buf(buf, "%p%c\n", level + 1, "[suffix list end]");
		}
		printf_buf(buf, "%p%c\n", level, "[suffixed expr end]");
		break;
	}
	case EXPR_FUNCTION_CALL: {
		printf_buf(buf, "%p%c %T\n", level, "[function call start]", &node->function_call_expr.type);
		if (node->function_call_expr.method_name) {
			printf_buf(buf, "%p: %t (\n", level + 1, node->function_call_expr.method_name);
		} else {
			printf_buf(buf, "%p(\n", level + 1);
		}
		print_ast_node_list(buf, node->function_call_expr.arg_list, level + 2, ",");
		printf_buf(buf, "%p)\n", level + 1);
		printf_buf(buf, "%p%c\n", level, "[function call end]");
		break;
	}
	case EXPR_SYMBOL: {
		print_symbol(buf, node->symbol_expr.var, level + 1);
		break;
	}
	case EXPR_BINARY: {
		printf_buf(buf, "%p%c %T\n", level, "[binary expr start]", &node->binary_expr.type);
		raviX_print_ast_node(buf, node->binary_expr.expr_left, level + 1);
		printf_buf(buf, "%p%s\n", level, raviX_get_binary_opr_str(node->binary_expr.binary_op));
		raviX_print_ast_node(buf, node->binary_expr.expr_right, level + 1);
		printf_buf(buf, "%p%c\n", level, "[binary expr end]");
		break;
	}
	case EXPR_UNARY: {
		printf_buf(buf, "%p%c %T\n", level, "[unary expr start]", &node->unary_expr.type);
		printf_buf(buf, "%p%s\n", level, raviX_get_unary_opr_str(node->unary_expr.unary_op));
		raviX_print_ast_node(buf, node->unary_expr.expr, level + 1);
		printf_buf(buf, "%p%c\n", level, "[unary expr end]");
		break;
	}
	case EXPR_LITERAL: {
		printf_buf(buf, "%p", level);
		switch (node->literal_expr.type.type_code) {
		case RAVI_TNIL:
			printf_buf(buf, "nil");
			break;
		case RAVI_TBOOLEAN:
			printf_buf(buf, "%b", node->literal_expr.u.i);
			break;
		case RAVI_TNUMINT:
			printf_buf(buf, "%i", node->literal_expr.u.i);
			break;
		case RAVI_TNUMFLT:
			printf_buf(buf, "%f", node->literal_expr.u.r);
			break;
		case RAVI_TSTRING:
			printf_buf(buf, "'%t'", node->literal_expr.u.ts);
			break;
		case RAVI_TVARARGS:
			printf_buf(buf, "...");
			break;
		default:
			assert(0);
		}
		printf_buf(buf, "\n");
		break;
	}
	case EXPR_FIELD_SELECTOR: {
		printf_buf(buf, "%p%c %T\n", level, "[field selector start]", &node->index_expr.type);
		printf_buf(buf, "%p.\n", level + 1);
		raviX_print_ast_node(buf, node->index_expr.expr, level + 2);
		printf_buf(buf, "%p%c\n", level, "[field selector end]");
		break;
	}
	case EXPR_Y_INDEX: {
		printf_buf(buf, "%p%c %T\n", level, "[Y index start]", &node->index_expr.type);
		printf_buf(buf, "%p[\n", level + 1);
		raviX_print_ast_node(buf, node->index_expr.expr, level + 2);
		printf_buf(buf, "%p]\n", level + 1);
		printf_buf(buf, "%p%c\n", level, "[Y index end]");
		break;
	}
	case EXPR_TABLE_ELEMENT_ASSIGN: {
		printf_buf(buf, "%p%c %T\n", level, "[indexed assign start]", &node->table_elem_assign_expr.type);
		if (node->table_elem_assign_expr.key_expr) {
			printf_buf(buf, "%p%c\n", level, "[index start]");
			raviX_print_ast_node(buf, node->table_elem_assign_expr.key_expr, level + 1);
			printf_buf(buf, "%p%c\n", level, "[index end]");
		}
		printf_buf(buf, "%p%c\n", level, "[value start]");
		raviX_print_ast_node(buf, node->table_elem_assign_expr.value_expr, level + 1);
		printf_buf(buf, "%p%c\n", level, "[value end]");
		printf_buf(buf, "%p%c\n", level, "[indexed assign end]");
		break;
	}
	case EXPR_TABLE_LITERAL: {
		printf_buf(buf, "%p{ %c %T\n", level, "[table constructor start]", &node->table_expr.type);
		print_ast_node_list(buf, node->table_expr.expr_list, level + 1, ",");
		printf_buf(buf, "%p} %c\n", level, "[table constructor end]");
		break;
	}
	default:
		printf_buf(buf, "%pUnsupported node type %d\n", level, node->type);
		assert(0);
	}
}

void raviX_output_ast(CompilerState *container, FILE *fp)
{
	TextBuffer mbuf;
	raviX_buffer_init(&mbuf, 1024);
	raviX_print_ast_node(&mbuf, container->main_function, 0);
	fputs(mbuf.buf, fp);
	raviX_buffer_free(&mbuf);
}
