package mars.assembler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import mars.ErrorList;
import mars.ErrorMessage;
import mars.MIPSprogram;
import mars.mips.hardware.RegisterFile;
import mars.mips.hardware.Coprocessor0;
import mars.mips.hardware.Coprocessor1;

/*
Copyright (c) 2013-2014.

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.

(MIT license, http://www.opensource.org/licenses/mit-license.html)
 */

/**
 * Stores information of a macro definition.
 * 
 * @author M.H.Sekhavat <sekhavat17@gmail.com>
 */
public class Macro {
   private String name;
   private MIPSprogram program;
   private ArrayList<String> labels;

/**
 * first and last line number of macro definition. first line starts with
 * .macro directive and last line is .end_macro directive.
 */
   private int fromLine, toLine;
   private int origFromLine, origToLine;
/**
 * arguments like <code>%arg</code> will be substituted by macro expansion
 */
   private ArrayList<String> args;

   public Macro() {
      name = "";
      program = null;
      fromLine = toLine = 0;
      origFromLine = origToLine = 0;
      args = new ArrayList<String>();
      labels = new ArrayList<String>();
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public MIPSprogram getProgram() {
      return program;
   }

   public void setProgram(MIPSprogram program) {
      this.program = program;
   }

   public int getFromLine() {
      return fromLine;
   }
	
   public int getOriginalFromLine() {
      return this.origFromLine;
   }
	
   public void setFromLine(int fromLine) {
      this.fromLine = fromLine;
   }

   public void setOriginalFromLine(int origFromLine) {
      this.origFromLine = origFromLine;
   }

   public int getToLine() {
      return toLine;
   }
	
   public int getOriginalToLine() {
      return this.origToLine;
   }
	
   public void setToLine(int toLine) {
      this.toLine = toLine;
   }

   public void setOriginalToLine(int origToLine) {
      this.origToLine = origToLine;
   }
	
   public ArrayList<String> getArgs() {
      return args;
   }

   public void setArgs(ArrayList<String> args) {
      this.args = args;
   }

/**
 * @param obj
 *            {@link Macro} object to check if their name and count of
 *            arguments are same
 */
   @Override
    public boolean equals(Object obj) {
      if (obj instanceof Macro) {
         Macro macro = (Macro) obj;
         return macro.getName().equals(name) && (macro.args.size() == args.size());
      }
      return super.equals(obj);
   }

   public void addArg(String value) {
      args.add(value);
   }

/**
 * Substitutes macro arguments in a line of source code inside macro
 * definition to be parsed after macro expansion. <br>
 * Also appends "_M#" to all labels defined inside macro body where # is value of <code>counter</code>
 * 
 * @param line
 *            source line number in macro definition to be substituted
 * @param args
 * @param counter
 *            unique macro expansion id
 * @param errors
 * @return <code>line</code>-th line of source code, with substituted
 *         arguments
 */

   public String getSubstitutedLine(int line, TokenList args, long counter, ErrorList errors) {
      TokenList tokens = (TokenList) program.getTokenList().get(line - 1);
      String s = program.getSourceLine(line);
   
      for (int i = tokens.size() - 1; i >= 0; i--) {
         Token token = tokens.get(i);
         if (tokenIsMacroParameter(token.getValue(), true)) {
            int repl = -1;
            for (int j = 0; j < this.args.size(); j++) {
               if (this.args.get(j).equals(token.getValue())) {
                  repl = j;
                  break;
               }
            }
            String substitute = token.getValue();
            if (repl != -1)
               substitute = args.get(repl + 1).toString();
            else {
               errors.add(new ErrorMessage(program, token.getSourceLine(),
                  token.getStartPos(), "Unknown macro parameter"));
            } 
            s = replaceToken(s, token, substitute);
         } 
         else if (tokenIsMacroLabel(token.getValue())){
            String substitute = token.getValue()+"_M"+counter;
            s=replaceToken(s, token, substitute); 
         }
      }
      return s;
   }


/**
 * returns true if <code>value</code> is name of a label defined in this macro's body.
 * @param value
 * @return
 */
   private boolean tokenIsMacroLabel(String value) {
      return (Collections.binarySearch(labels, value)>=0);
   }

/**
 * replaces token <code>tokenToBeReplaced</code> which is occured in <code>source</code> with <code>substitute</code>.
 * @param source
 * @param tokenToBeReplaced
 * @param substitute
 * @return 
 */
// Initially the position of the substitute was based on token position but that proved problematic
// in that the source string does not always match the token list from which the token comes. The
// token list has already had .eqv equivalences applied whereas the source may not.  This is because
// the source comes from a macro definition?  That has proven to be a tough question to answer. 
// DPS 12-feb-2013
   private String replaceToken(String source, Token tokenToBeReplaced, String substitute) {
      String stringToBeReplaced = tokenToBeReplaced.getValue();
      int pos = source.indexOf(stringToBeReplaced);
      return (pos < 0) ? source : source.substring(0, pos) + substitute + source.substring(pos+stringToBeReplaced.length());
   }  
	
/**
 * returns whether <code>tokenValue</code> is macro parameter or not
 * @param tokenValue
 * @param acceptSpimStyleParameters accepts SPIM-style parameters which begin with '$' if true
 * @return
 */
   public static boolean tokenIsMacroParameter(String tokenValue, boolean acceptSpimStyleParameters) {
      if (acceptSpimStyleParameters) {
         // Bug fix: SPIM accepts parameter names that start with $ instead of %.  This can
			// lead to problems since register names also start with $.  This IF condition
			// should filter out register names.  Originally filtered those from regular set but not
         // from Coprocessor0 or Coprocessor1 register sets.  Expanded the condition. 
			// DPS  7-July-2014.
         if (tokenValue.length() > 0 && tokenValue.charAt(0) == '$' &&
            RegisterFile.getUserRegister(tokenValue) == null &&
            Coprocessor0.getRegister(tokenValue) == null &&  // added 7-July-2014
            Coprocessor1.getRegister(tokenValue) == null)    // added 7-July-2014
         {
            return true;
         }
      }
      return tokenValue.length() > 1 && tokenValue.charAt(0) == '%';
   }

   public void addLabel(String value) {
      labels.add(value);
   }

/**
 * Operations to be done on this macro before it is committed in macro pool.
 */
   public void readyForCommit() {
      Collections.sort(labels);
   }



}
