/***********************************************************************
 *
 *  avra - Assembler for the Atmel AVR microcontroller series
 *
 *  Copyright (C) 1998-2004 Jon Anders Haugum, TObias Weber
 *
 *  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; see the file COPYING.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 *
 *
 *  Authors of avra can be reached at:
 *     email: jonah@omegav.ntnu.no, tobiw@suprafluid.com
 *     www: http://sourceforge.net/projects/avra
 */

/*
 * In append_type: added generic register names support
 * Alexey Pavluchenko, 16.Nov.2005
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "misc.h"
#include "args.h"
#include "avra.h"
#include "device.h"

/* Only Windows LIBC does support itoa, so we add this
   function for other systems here manually. Thank you
   Peter Hettkamp for your work. */

#ifndef WIN32
char * itoa(int num, char *str, const int number_format)
{
	int num1 = num;
	int num_chars = 0;
	int pos;

	while (num1>0)
	{
		num_chars++;
		num1 /= number_format;
	}

	if (num_chars == 0)
		num_chars = 1;

	str[num_chars] = 0;

	for (pos = num_chars-1; pos>=0; pos--)
	{
		int cur_char = num % number_format;

		if (cur_char < 10)         /* Insert number */
		{
			str[pos] = cur_char + '0';
		}
		else
		{
			str[pos] = cur_char-10 + 'A';
		}

		num /= number_format;
	}
	return(str);
}
#endif


int read_macro(struct prog_info *pi, char *name)
{
	int loopok;
	int i;
	int start;
	struct macro *macro;
	struct macro_line *macro_line;
	struct macro_line **last_macro_line = NULL;
	struct macro_label *macro_label;

	if (pi->pass == PASS_1)
	{
		if (!name)
		{
			print_msg(pi, MSGTYPE_ERROR, "missing macro name");
			return(True);
		}
		get_next_token(name, TERM_END);

		for (i = 0; !IS_END_OR_COMMENT(name[i]); i++)
		{
			if (!IS_LABEL(name[i]))
			{
				print_msg(pi, MSGTYPE_ERROR, "illegal characters used in macro name '%s'",name);
				return(False);
			}
		}

		macro = calloc(1, sizeof(struct macro));
		if (!macro)
		{
			print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
			return(False);
		}

		if (pi->last_macro)
			pi->last_macro->next = macro;
		else
			pi->first_macro = macro;
		pi->last_macro = macro;
		macro->name = malloc(strlen(name) + 1);
		if (!macro->name)
		{
			print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
			return(False);
		}
		strcpy(macro->name, name);
		macro->include_file = pi->fi->include_file;
		macro->first_line_number = pi->fi->line_number;
		last_macro_line = &macro->first_macro_line;
	}
	else        /* pi->pass == PASS_2 */
	{
		if (pi->list_line && pi->list_on)
		{
			fprintf(pi->list_file, "          %s\n", pi->list_line);
			pi->list_line = NULL;
		}
		// reset macro label running numbers
		get_next_token(name, TERM_END);
		macro = get_macro(pi, name);
		if (!macro)
		{
			print_msg(pi, MSGTYPE_ERROR, "macro inconsistency in '%s'", name);
			return(True);
		}
		for (macro_label = macro->first_label; macro_label; macro_label = macro_label->next)
		{
			macro_label->running_number = 0;
		}
	}

	loopok = True;
	while (loopok)
	{
		if (fgets_new(pi,pi->fi->buff, LINEBUFFER_LENGTH, pi->fi->fp))
		{
			pi->fi->line_number++;
			i = 0;
			while (IS_HOR_SPACE(pi->fi->buff[i]) && !IS_END_OR_COMMENT(pi->fi->buff[i]))
				i++;
			if (pi->fi->buff[i] == '.')
			{
				i++;
				if (!nocase_strncmp(&pi->fi->buff[i], "endm", 4))
					loopok = False;
				if (!nocase_strncmp(&pi->fi->buff[i], "endmacro", 8))
					loopok = False;
			}
			if (pi->pass == PASS_1)
			{
				if (loopok)
				{
					i = 0;                     /* find start of line */
					while (IS_HOR_SPACE(pi->fi->buff[i]) && !IS_END_OR_COMMENT(pi->fi->buff[i]))
					{
						i++;
					}
					start = i;
					/* find end of line */
					while (!IS_END_OR_COMMENT(pi->fi->buff[i]) && (IS_LABEL(pi->fi->buff[i]) || pi->fi->buff[i] == ':'))
					{
						i++;
					}
					if (pi->fi->buff[i-1] == ':' && (pi->fi->buff[i-2] == '%'
													 && (IS_HOR_SPACE(pi->fi->buff[i]) || IS_END_OR_COMMENT(pi->fi->buff[i]))))
					{
						if (macro->first_label)
						{
							for (macro_label = macro->first_label; macro_label->next; macro_label=macro_label->next)
							{
							}
							macro_label->next = calloc(1,sizeof(struct macro_label));
							macro_label = macro_label->next;
						}
						else
						{
							macro_label = calloc(1,sizeof(struct macro_label));
							macro->first_label = macro_label;
						}
						macro_label->label = malloc(strlen(&pi->fi->buff[start])+1);
						pi->fi->buff[i-1] = '\0';
						strcpy(macro_label->label, &pi->fi->buff[start]);
						pi->fi->buff[i-1] = ':';
						macro_label->running_number = 0;
					}

					macro_line = calloc(1, sizeof(struct macro_line));
					if (!macro_line)
					{
						print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
						return(False);
					}
					*last_macro_line = macro_line;
					last_macro_line = &macro_line->next;
					macro_line->line = malloc(strlen(pi->fi->buff) + 1);
					if (!macro_line->line)
					{
						print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
						return(False);
					}
					strcpy(macro_line->line, &pi->fi->buff[start]);
				}
			}
			else if (pi->fi->buff && pi->list_file && pi->list_on)
			{
				if (pi->fi->buff[i] == ';')
					fprintf(pi->list_file, "         %s\n", pi->fi->buff);
				else
					fprintf(pi->list_file, "          %s\n", pi->fi->buff);
			}
		}
		else
		{
			if (feof(pi->fi->fp))
			{
				print_msg(pi, MSGTYPE_ERROR, "Found no closing .ENDMACRO");
				return(True);
			}
			else
			{
				perror(pi->fi->include_file->name);
				return(False);
			}
		}
	}
	return(True);
}


struct macro *get_macro(struct prog_info *pi, char *name)
{
	struct macro *macro;

	for (macro = pi->first_macro; macro; macro = macro->next)
		if (!nocase_strcmp(macro->name, name))
			return(macro);
	return(NULL);
}

void append_type(struct prog_info *pi, char *name, int c, char *value)
{
	int p, l;
	struct def *def;

	p = strlen(name);
	name[p++] = '_';

	if (c == 0)
	{
		name[p++] = 'v';
		name[p] = '\0';
		return;
	}

	l = strlen(value);
	if ((l==2 || l==3) && (tolower(value[0])=='r') && isdigit(value[1]) && (l==3 ? isdigit(value[2]) : 1) && (atoi(&value[1])<32))
	{
		itoa((c*8),&name[p],10);
		return;
	}


	for (def = pi->first_def; def; def = def->next)
		if (!nocase_strcmp(def->name, value))
		{
			itoa((c*8),&name[p],10);
			return;
		}

	name[p++] = 'i';
	name[p] = '\0';
}


/*********************************************************
* This routine replaces the macro call with mnemonics.  *
*********************************************************/

int expand_macro(struct prog_info *pi, struct macro *macro, char *rest_line)
{
	int ok = True, macro_arg_count = 0, off, a, b = 0, c, i = 0, j = 0;
	char  *line = NULL;
	char  *temp;
	char  *macro_args[MAX_MACRO_ARGS];
	char tmp[7];
	char buff[LINEBUFFER_LENGTH];
	char arg = False;
	char  *nmn; //string buffer for 'n'ew 'm'acro 'n'ame
	struct        macro_line *old_macro_line;
	struct        macro_call *macro_call;
	struct        macro_label *macro_label;

	if (rest_line)
	{
		//we reserve some extra space for extended macro parameters
		line = malloc(strlen(rest_line) + 20);
		if (!line)
		{
			print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
			return(False);
		}

		/* exchange amca word 'src' with YH:YL and 'dst' with ZH:ZL */
		for (c = 0, a = strlen(rest_line); c < a; c++)
		{
			switch (tolower(rest_line[c]))
			{
			case 's':
				if (IS_SEPARATOR(rest_line[c-1]) && (rest_line[c+1] == 'r') && (rest_line[c+2] == 'c') && IS_SEPARATOR(rest_line[c+3]))
				{
					strcpy(&line[b],"YH:YL");
					b += 5;
					c += 2;
				}
				else
				{
					line[b++] = rest_line[c];
				}
				break;
			case 'd':
				if (IS_SEPARATOR(rest_line[c-1]) && (rest_line[c+1] == 's') && (rest_line[c+2] == 't') && IS_SEPARATOR(rest_line[c+3]))
				{
					strcpy(&line[b],"ZH:ZL");
					b += 5;
					c += 2;
				}
				else
				{
					line[b++] = rest_line[c];
				}
				break;
//        case ';':
//          break;
			default:
				line[b++] = rest_line[c];
			}
		}
		strcpy(&line[b],"\n"); /* set CR/LF at the end of the line */


		/*  here we split up the macro arguments into "macro_args"
		 *  Extended macro code interpreter added by TW 2002
		 */

		temp = line;
		/* test for advanced parameters */
		if ( temp[0] == '[' )   // there must be "[" " then "]", else it is garbage
		{
			if (!strchr(temp, ']'))
			{
				print_msg(pi, MSGTYPE_ERROR, "found no closing ']'");
				return(False);
			}

			// Okay now we are within the advanced code interpreter

			temp++; // = &temp[1]; // skip the first bracket
			nmn = malloc(LINEBUFFER_LENGTH);
			if (!nmn)
			{
				print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
				return(False);
			}
			strcpy(nmn,macro->name); // create a new macro name buffer
			c = 1; // byte counter
			arg = True; // loop flag

			while (arg)
			{
				while (IS_HOR_SPACE(temp[0]))   //skip leading spaces
				{
					temp++; // = &temp[1];
				}
				off = 0; // pointer offset
				do
				{
					switch (temp[off]) //test current character code
					{
					case ':':
						temp[off] = '\0';
						if (off > 0)
						{
							c++;
							macro_args[macro_arg_count++] = temp;
						}
						else
						{
							print_msg(pi, MSGTYPE_ERROR, "missing register before ':'",nmn);
							return(False);
						}
						break;
					case ']':
						arg = False;
					case ',':
						a = off;
						do
							temp[a--] = '\0';
						while ( IS_HOR_SPACE(temp[a]) );
						if (off > 0)
						{
							macro_args[macro_arg_count++] = temp;
							append_type(pi, nmn, c, temp);
							c = 1;
						}
						else
						{
							append_type(pi, nmn, 0, temp);
							c = 1;
						}
						break;

					default:
						off++;
					}
				}
				while (temp[off] != '\0');

				if (arg)
					temp = &temp[off+1];
				else
					break;
			}

			macro = get_macro(pi,nmn);
			if (macro == NULL)
			{
				print_msg(pi, MSGTYPE_ERROR, "Macro %s is not defined !",nmn);
				return(False);
			}
			free(nmn);
		}
		/* or else, we handle the macro as normal macro */
		else
		{
			line = malloc(strlen(rest_line) + 1);
			if (!line)
			{
				print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
				return(False);
			}
			strcpy(line, rest_line);
			temp = line;
			while (temp)
			{
				macro_args[macro_arg_count++] = temp;
				temp = get_next_token(temp, TERM_COMMA);
			}
		}
	}

	if (pi->pass == PASS_1)
	{
		macro_call = calloc(1, sizeof(struct macro_call));
		if (!macro_call)
		{
			print_msg(pi, MSGTYPE_OUT_OF_MEM, NULL);
			return(False);
		}
		if (pi->last_macro_call)
			pi->last_macro_call->next = macro_call;
		else
			pi->first_macro_call = macro_call;

		pi->last_macro_call = macro_call;
		macro_call->line_number = pi->fi->line_number;
		macro_call->include_file = pi->fi->include_file;
		macro_call->macro = macro;
		macro_call->prev_on_stack = pi->macro_call;

		if (macro_call->prev_on_stack)
		{
			macro_call->nest_level = macro_call->prev_on_stack->nest_level + 1;
			macro_call->prev_line_index = macro_call->prev_on_stack->line_index;
		}
	}
	else
	{
		for (macro_call = pi->first_macro_call; macro_call; macro_call = macro_call->next)
		{
			if ((macro_call->include_file->num == pi->fi->include_file->num) && (macro_call->line_number == pi->fi->line_number))
			{
				if (pi->macro_call)
				{
					/* Find correct macro_call when using recursion and nesting */
					if (macro_call->prev_on_stack == pi->macro_call)
						if ((macro_call->nest_level == (pi->macro_call->nest_level + 1)) && (macro_call->prev_line_index == pi->macro_call->line_index))
							break;
				}
				else
					break;
			}
		}
		if (pi->list_line && pi->list_on)
		{
			fprintf(pi->list_file, "C:%06x   +  %s\n", pi->cseg_addr, pi->list_line);
			pi->list_line = NULL;
		}
	}

	macro_call->line_index = 0;
	pi->macro_call = macro_call;
	old_macro_line = pi->macro_line;

	//printf("\nconvert macro: '%s'\n",macro->name);

	for (pi->macro_line = macro->first_macro_line; pi->macro_line && ok; pi->macro_line = pi->macro_line->next)
	{
		macro_call->line_index++;
		if (GET_ARG(pi->args, ARG_LISTMAC))
			pi->list_line = buff;
		else
			pi->list_line = NULL;

		/* here we change jumps/calls within macro that corresponds to macro labels.
		   Only in case there is an entry in macro_label list */

		strcpy(buff,"\0");
		macro_label = get_macro_label(pi->macro_line->line,macro);
		if (macro_label)
		{
			/* test if the right macro label has been found */
			temp = strstr(pi->macro_line->line,macro_label->label);
			c = strlen(macro_label->label);
			if (temp[c] == ':') /* it is a label definition */
			{
				macro_label->running_number++;
				strncpy(buff, macro_label->label, c - 1);
				buff[c - 1] = 0;
				i = strlen(buff) + 2; /* we set the process indeafter label */
				/* add running number to it */
				strcpy(&buff[c-1],itoa(macro_label->running_number, tmp, 10));
				strcat(buff, ":\0");
			}
			else if (IS_HOR_SPACE(temp[c]) || IS_END_OR_COMMENT(temp[c])) /* it is a jump to a macro defined label */
			{
				strcpy(buff,pi->macro_line->line);
				temp = strstr(buff, macro_label->label);
				i = temp - buff + strlen(macro_label->label);
				strncpy(temp, macro_label->label, c - 1);
				strcpy(&temp[c-1], itoa(macro_label->running_number, tmp, 10));
			}
		}
		else
		{
			i = 0;
		}

		/* here we check every character of current line */
		for (j = i; pi->macro_line->line[i] != '\0'; i++)
		{
			/* check for register place holders */
			if (pi->macro_line->line[i] == '@')
			{
				i++;
				if (!isdigit(pi->macro_line->line[i]))
					print_msg(pi, MSGTYPE_ERROR, "@ must be followed by a number");
				else if ((pi->macro_line->line[i] - '0') >= macro_arg_count)
					print_msg(pi, MSGTYPE_ERROR, "Missing macro argument (for @%c)", pi->macro_line->line[i]);
				else
				{
					/* and replace them with given registers */
					strcat(&buff[j], macro_args[pi->macro_line->line[i] - '0']);
					j += strlen(macro_args[pi->macro_line->line[i] - '0']);
				}
			}
			else if (pi->macro_line->line[i] == ';')
			{
				strncat(buff, "\n", 1);
				break;
			}
			else
			{
				strncat(buff, &pi->macro_line->line[i], 1);
			}
		}

		ok = parse_line(pi, buff);
		if (ok)
		{
			if ((pi->pass == PASS_2) && pi->list_line && pi->list_on)
				fprintf(pi->list_file, "         %s\n", pi->list_line);
			if (pi->error_count >= pi->max_errors)
			{
				print_msg(pi, MSGTYPE_MESSAGE, "Maximum error count reached. Exiting...");
				ok = False;
				break;
			}
		}
	}

	pi->macro_line = old_macro_line;
	pi->macro_call = macro_call->prev_on_stack;
	if (rest_line)
		free(line);
	return(ok);
}

struct macro_label *get_macro_label(char *line, struct macro *macro)
{
	char *temp;
	struct macro_label *macro_label;

	for (macro_label = macro->first_label; macro_label; macro_label = macro_label->next)
	{
		temp = strstr(line,macro_label->label);
		if (temp)
		{
			return macro_label;
		}
	}
	return NULL;
}

/* end of macro.c */

