   package mars.assembler;

   import java.util.ArrayList;
   import java.util.Stack;

   import mars.ErrorList;
   import mars.MIPSprogram;

/*
Copyright (c) 2013.

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 macros defined by now. <br>
 * Will be used in first pass of assembling MIPS source code. When reached
 * <code>.macro</code> directive, parser calls
 * {@link MacroPool#BeginMacro(String, int)} and skips source code lines until
 * reaches <code>.end_macro</code> directive. then calls
 * {@link MacroPool#CommitMacro(int)} and the macro information stored in a
 * {@link Macro} instance will be added to {@link #macroList}. <br>
 * Each {@link MIPSprogram} will have one {@link MacroPool}<br>
 * NOTE: Forward referencing macros (macro expansion before its definition in
 * source code) and Nested macro definition (defining a macro inside other macro
 * definition) are not supported.
 * 
 * @author M.H.Sekhavat <sekhavat17@gmail.com>
 */
    public class MacroPool {
      private MIPSprogram program;
   /**
    * List of macros defined by now
    */
      private ArrayList<Macro> macroList;
   /**
    * @see #BeginMacro(String, int)
    */
      private Macro current;
      private ArrayList<Integer> callStack;
      private ArrayList<Integer> callStackOrigLines;
   /**
    * @see #getNextCounter()
    */
      private int counter;
   
   
   /**
   * Create an empty MacroPool for given program
   * @param mipsProgram  associated MIPS program
   */
       public MacroPool(MIPSprogram mipsProgram) {
         this.program = mipsProgram;
         macroList = new ArrayList<Macro>();
         callStack=new ArrayList<Integer>();
         callStackOrigLines=new ArrayList<Integer>();
         current = null;
         counter = 0;
      }
   
   /**
    * This method will be called by parser when reached <code>.macro</code>
    * directive.<br>
    * Instantiates a new {@link Macro} object and stores it in {@link #current}
    * . {@link #current} will be added to {@link #macroList} by
    * {@link #CommitMacro(int)}
    * 
    * @param nameToken
    *            Token containing name of macro after <code>.macro</code> directive
    */
   
       public void beginMacro(Token nameToken) {
         current = new Macro();
         current.setName(nameToken.getValue());
         current.setFromLine(nameToken.getSourceLine());
         current.setOriginalFromLine(nameToken.getOriginalSourceLine());
         current.setProgram(program);
      }	   
   
   /**
    * This method will be called by parser when reached <code>.end_macro</code>
    * directive. <br>
    * Adds/Replaces {@link #current} macro into the {@link #macroList}.
    * 
    * @param endToken
    *            Token containing <code>.end_macro</code> directive in source code
    */
   
       public void commitMacro(Token endToken) {
         current.setToLine(endToken.getSourceLine());
         current.setOriginalToLine(endToken.getOriginalSourceLine());
         current.readyForCommit();
         macroList.add(current);
         current = null;
      }
   	   		
   /**
    * Will be called by parser when reaches a macro expansion call
    * 
    * @param tokens
    *            tokens passed to macro expansion call
    * @return {@link Macro} object matching the name and argument count of
    *         tokens passed
    */
       public Macro getMatchingMacro(TokenList tokens, int callerLine) {
         if (tokens.size() < 1)
            return null;
         Macro ret = null;
         Token firstToken = tokens.get(0);
         for (Macro macro : macroList) {
            if (macro.getName().equals(firstToken.getValue())
            	&& macro.getArgs().size() + 1 == tokens.size()
            	//&& macro.getToLine() < callerLine  // condition removed; doesn't work nicely in conjunction with .include, and does not seem necessary.  DPS 8-MAR-2013
            	&& (ret == null || ret.getFromLine() < macro.getFromLine()))
               ret = macro;
         }
         return ret;
      }
   
   /**
    * @param value
    * @return true if any macros have been defined with name <code>value</code>
    *         by now, not concerning arguments count.
    */
       public boolean matchesAnyMacroName(String value) {
         for (Macro macro : macroList)
            if (macro.getName().equals(value))
               return true;
         return false;
      }
   
   
       public Macro getCurrent() {
         return current;
      }
   
       public void setCurrent(Macro current) {
         this.current = current;
      }
   
   /**
    * {@link #counter} will be set to 0 on construction of this class and will
    * be incremented by each call. parser calls this method once for every
    * expansions. it will be a unique id for each expansion of macro in a file
    * 
    * @return counter value
    */
       public int getNextCounter() {
         return counter++;
      }
   
   
       public ArrayList<Integer> getCallStack() {
         return callStack;
      }
   
   
       public boolean pushOnCallStack(Token token) { //returns true if detected expansion loop
         int sourceLine = token.getSourceLine();
         int origSourceLine = token.getOriginalSourceLine();
         if (callStack.contains(sourceLine))
            return true;
         callStack.add(sourceLine);
         callStackOrigLines.add(origSourceLine);
         return false;
      }
   
       public void popFromCallStack() {
         callStack.remove(callStack.size()-1);
         callStackOrigLines.remove(callStackOrigLines.size()-1);
      }
   
   
       public String getExpansionHistory() {
         String ret="";
         for (int i=0; i<callStackOrigLines.size(); i++){
            if (i>0)
               ret+="->";
            ret+=callStackOrigLines.get(i).toString();
         }
         return ret;
      }
   }
