/***********************************************************************
 *
 *  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
 */

#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 pass) 
{
	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(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(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 {  /* pass == PASS_2 */
		if(pi->list_line && pi->list_on) {
			fprintf(pi->list_file, "          %s", 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(pi->fi->buff, LINEBUFFER_LENGTH, pi->fi->fp)) {
			pi->fi->line_number++;
			i = 0;
			while(IS_HOR_SPACE(pi->fi->buff[i]) && !IS_END(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(pass == PASS_1) {
				if(loopok) {
					i = 0; /* find start of line */
					while(IS_HOR_SPACE(pi->fi->buff[i]) && !IS_END(pi->fi->buff[i])) {
     					i++;
					}
					start = i;
					/* find end of line */
					while(!IS_END(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(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) {
				if(pi->fi->buff[i] == ';')
					fprintf(pi->list_file, "         %s", pi->fi->buff);
				else
					fprintf(pi->list_file, "          %s", 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;
	struct def *def;

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

	if(c == 0)
	{
		name[p++] = 'v';
		name[p] = '\0';
		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 pass)
{
  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, "missig 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(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", 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(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, pass);
    if(ok) {
	  if((pass == PASS_2) && pi->list_line && pi->list_on)
	    fprintf(pi->list_file, "         %s", pi->list_line);
	  if(pi->error_count >= pi->max_errors) {
	    print_msg(pi, MSGTYPE_MESSAGE, "Maximum error count reached. Exiting...");
	  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 */

