#define _GNU_SOURCE

#include <stdlib.h>
#include <string.h>
#include "spasm.h"
#include "pass_one.h"
#include "pass_two.h"
#include "utils.h"
#include "parser.h"
#include "directive.h"
#include "console.h"

void write_arg (int value, arg_type type, int or);
char *expand_expr (char *expr);

expr_t *expr_list = NULL, *expr_list_tail = NULL;
output_t *output_list = NULL, *output_list_tail = NULL;

/*
 * Tries to evaluate an expression,
 * if it succeeds writes the data
 * to the output, otherwise writes
 * 0s to fill the space and adds
 * the expression to the list to be
 * parsed in pass two
 */

void add_pass_two_expr (char *expr, arg_type type, int or) {
	int value;

	//if we're in code counter or stats mode, where we don't need actual expressions, then just skip this crap
	if ((mode & MODE_CODE_COUNTER) || (mode & MODE_STATS))
		return;

	if (type == ARG_IX_IY_OFFSET) {
		//if it's an IX or IY offset, it's allowed to have a + in front of it, so skip that
		// so the parser doesn't try to interpret it as a local label
		if (*expr == '+') {
			expr++;
		} else if (*expr == '\0') {
			//IX/IY offsets are also allowed to be blank - a 0 is assumed then
			write_arg (0, ARG_IX_IY_OFFSET, 0);
			return;
		}
	}

	//first try to parse it
	suppress_errors = true;

	if (!parse_num (expr, &value)) {
		expr_t *new_expr;

		suppress_errors = false;

		//if that didn't work, then add it to the pass two expression list
		new_expr = (expr_t *)malloc (sizeof (expr_t));
		new_expr->program_counter = program_counter;
		new_expr->line_num = line_num;
		new_expr->curr_reusable = get_curr_reusable();
		new_expr->out_ptr = out_ptr;
		if (mode & MODE_LIST)
			new_expr->listing_offset = listing_offset;
		new_expr->type = type;
		new_expr->input_file = curr_input_file;
		new_expr->listing_on = listing_on;
		new_expr->or = or;
		new_expr->next = NULL;

		if (expr_list_tail)
			expr_list_tail->next = new_expr;
		expr_list_tail = new_expr;
		if (!expr_list)
			expr_list = new_expr;

		/* Important stuff here: to prevent forward references
		   combined with changing #defines (either through
		   redefinition, or with macro arguments, for example),
		   all #defines need to be fully expanded in the saved
		   expression for the second pass */

		//store the contents of the expanded expression for the second pass
		new_expr->expr = expand_expr (expr);

		//and write a blank value in its place
		if (type == ARG_ADDR_OFFSET)
			type = ARG_NUM_8;
		write_arg (0, type, 0);

	} else {
		suppress_errors = false;
		// Reparse the value to generate errors
		parse_num (expr, &value);
		//write the value now
		write_arg (value, type, or);
	}
}


/*
 * Adds an expression to be
 * echo'ed on the second pass
 */

void add_pass_two_output (char *expr, output_type type) {
	output_t *new_output;

	new_output = (output_t *)malloc (sizeof (output_t));
	new_output->program_counter = program_counter;
	new_output->line_num = line_num;
	new_output->curr_reusable = get_curr_reusable();
	new_output->type = type;
	new_output->input_file = curr_input_file;
	new_output->next = NULL;

	if (type == OUTPUT_SHOW)
		new_output->expr = strdup (expr);
	else
		new_output->expr = expand_expr (expr);

	if (output_list_tail)
		output_list_tail->next = new_output;
	output_list_tail = new_output;
	if (!output_list)
		output_list = new_output;
}


/*
 * Goes through the list of
 * expressions that couldn't
 * be parsed before and evaluates
 * them and writes the values
 * to the output file
 */

extern unsigned char *output_contents;

void run_second_pass () {
	int value;
	expr_t *old_expr;
	output_t *old_output;
	unsigned char *saved_out_ptr = out_ptr;
	int saved_listing_offset = listing_offset;

	pass_one = false;

	//FILE *file = fopen ("passtwoexprs.txt", "w");

	//printf("running through the list %p\n", expr_list);
	while (expr_list) {
		//go through each expression and evaluate it
		program_counter = expr_list->program_counter;
		line_num = expr_list->line_num;
		curr_input_file = expr_list->input_file;
		set_curr_reusable(expr_list->curr_reusable);

		//fprintf(file, "%s:%d:offset(%d): %s\n", curr_input_file, line_num, expr_list->out_ptr - output_contents, expr_list->expr);

		//printf("passtwoexpr: '%s'\n", expr_list->expr);
		if (parse_num (expr_list->expr, &value)) {
			//if that was successful, then write it to the file
			if (mode & MODE_LIST)
				listing_offset = expr_list->listing_offset;

			out_ptr = expr_list->out_ptr;
			listing_on = expr_list->listing_on;
			write_arg (value, expr_list->type, expr_list->or);
		}
		free (expr_list->expr);
		old_expr = expr_list;
		expr_list = expr_list->next;
		free (old_expr);
	}

	//fclose(file);

	out_ptr = saved_out_ptr;
	if (mode & MODE_LIST)
		listing_offset = saved_listing_offset;

	while (output_list) {
		//show each saved echo
		program_counter = output_list->program_counter;
		line_num = output_list->line_num;
		curr_input_file = output_list->input_file;
		set_curr_reusable(output_list->curr_reusable);

		switch (output_list->type) {
			case OUTPUT_ECHO:
				set_console_attributes (COLOR_GREEN);
				parse_emit_string (output_list->expr, ES_ECHO, stdout);
				break;
			case OUTPUT_SHOW:
			{
				define_t *define;
				if (!(define = search_defines (output_list->expr))) {
					show_error ("Can't find macro '%s'", output_list->expr);
					break;
				}
				show_define (define);
				break;
			}
		}
		free (output_list->expr);
		old_output = output_list;
		output_list = output_list->next;
		free (old_output);
	}
}


/*
 * Writes an argument
 * directly to the file,
 * OR'ing it with a value
 * for bit numbers
 */

void write_arg (int value, arg_type type, int or) {

	switch (type) {
		case ARG_NUM_8:
			if (value < -128 || value > 255)
				show_warning ("Number is too large to fit in 8 bits, truncating");
			write_out (value & 0xFF);
			break;
		case ARG_NUM_16:
			//no range checking for 16 bits, as higher bits of labels are used to store page info
			write_out (value & 0xFF);
			write_out ((value >> 8) & 0xFF);
			break;
		case ARG_ADDR_OFFSET:
			value -= (program_counter + 2);
			
			if (abs (value - 1) > 128) {
				show_error ("Relative jump distance is over 128 bytes (%d)", value);
				value = 0;
			}
			write_out (value & 0xFF);
			break;
		case ARG_IX_IY_OFFSET:
			if (value > 127 || value < -128) {
				show_error ("ix/iy offset can only range from -127 to +128");
				value = 0;
			}
			write_out (value & 0xFF);
			break;
		case ARG_BIT_NUM:
			if (value < 0 || value > 7) {
				show_error ("Bit number can only range from 0 to 7");
				value = 0;
			}
			write_out (((value & 0x07) << 3) | or);
			break;
	}
}

