package nxm.sys.intr;

import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;

import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.KeyVector;
import nxm.sys.lib.Keywords;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.lib.SortedIteration;
import nxm.sys.lib.TextFile;

/**
  Handle a block FOREACH control statement.

  @author Jeff Schoen / J. Czechowski IV / Neon Ngo

  @see Endfor
*/
public class Foreach extends Intrinsic {
  // The next set of variables control what is being iterated over.
  // Only one of these will be non-null at any given time.
  private Enumeration<?>     enumeration;  // Enumeration used for iteration
  private Iterator<?>        iterator;     // Iterator used for iteration
  private Keywords.Iterator  kwi;          // Iterator used for Keyword iteration
  private KeyVector.Iterator kvi;          // Iterator used for KeyVector iteration
  private DataFile           df;           // DataFile used for INDF
  private TextFile           tf;           // TextFile used for INTF
  private Object[]           array;        // Array used for INARRAY
  private Parser             list;         // Parsed list used for INLIST

  // General Variables
  private String             item;         // The results name that holds the current item
  private Map<?,?>           map;          // Map used for INTABLE/V iteration
  private boolean            reverse;      // Iterate in reverse (not all support this)
  private Data               data;         // Data buffer for INDF and INTF
  private int                index;        // Current index for INLIST, INSIZE and array
  private int                size;         // Total size for INLIST and INSIZE
  private boolean            doValues;     // Get value rather than key for Maps and Tables

  /** default constructor */
  public Foreach () {}

  public int process() {
    // Initialize values within process(), as same instance can be used in control block
    enumeration = null;
    iterator    = null;
    kwi         = null;
    kvi         = null;
    df          = null;
    tf          = null;
    array       = null;
    list        = null;
    map         = null;
    data        = null;
    index       = 0;
    size        = 0;
    index       = 0;
    if (M.macro == null) {
      String cmd = Macrofunc.enterIntrMacroText(M,"%foreach",MA.getRaw(),"endfor");
      Shell.runCommand(M,cmd);
      return FINISH;
    }

    String  func = MA.getU("FUNC");
    boolean sort = MA.getState("/SORT");

    item     = MA.getU("ITEM");
    reverse  = MA.getState("/REV");
    doValues = func.indexOf("/V") >= 0 || MA.getState("/VAL");

    if (func.startsWith("INTA")) { // INTABLE
      Object obj = MA.getO("IN");
      if (obj instanceof KeyVector) {
        kvi = ((KeyVector)obj).iterator();
      }
      else {
        nxm.sys.lib.Table tab = MA.getTable("IN");
        if (tab==null) M.error("No table found for INTAB option");
        /* NTN: Fix for Bug 1791 Table: Default Table mode (SLHM) incompatible with table.remove when iterating */
        Collection<?> col = (doValues)? tab.values() : tab.keySet();
        iterator = (sort)? new SortedIteration(col) : col.iterator();
      }
    }
    else if (func.startsWith("INDF") || func.startsWith("INF")) {
      df = MA.getDataFile("IN");
      df.open();
      data = df.getDataBuffer(1);
    }
    else if (func.startsWith("INTF")) {
      tf = MA.getTextFile("IN");
      tf.open(TextFile.FORCEABORT);
    }
    else if (func.startsWith("INLI")) {
      list = new Parser(MA.getS("IN"));
      list.setUseSpaceDelimiter(false);
      size = list.elements();
      index = (reverse)? size : 0;
    }
    else if (func.startsWith("INSI")) {
      size = MA.getL("IN");
      index = (reverse)? size : 0;
    }
    else if (func.startsWith("INKW")) {
      df = MA.getDataFile("IN");
      df.open();
      String scope = MA.getU("/SCOPE");
      if (scope.length()>0) {
        if (df.keywords.setScope(scope)==0)
        M.error("Could not set scope to "+scope);
      }
      kwi=df.keywords.iterator();
      df.close(); df=null;
    }
    else if (func.startsWith("IN")) { // Since 2.5.0
      Object obj = MA.getO("IN");

      // JC4: The following check for Collection should be changed to a check
      //      for Iterable after we move to Java 5.
      if (obj instanceof Data      ) obj = ((Data)obj).toVector();
      if (obj instanceof Map       ) obj = (doValues)? ((Map<?,?>)obj).values() : ((Map<?,?>)obj).keySet();
      if (obj instanceof Collection) obj = ((Collection<?>)obj).iterator(); // pre Java5
      // if (obj instanceof Iterable) obj = ((Iterable<?>)obj).iterator();     // post java5 (later)

           if (obj instanceof Iterator    ) { iterator    = (Iterator<?>)obj;    }
      else if (obj instanceof Object[]    ) { array       = (Object[])obj;    }
      else if (obj instanceof Enumeration ) { enumeration = (Enumeration<?>)obj; }
      else M.error("FOREACH: Type not supported with IN function.");

      /*
      if (reverse && (array != null)) {
        index = array.length;
      }
      */
      //Commented out block above and replaced with block below for DR 2577: LMT
      if (array != null) {
        index = (reverse) ? array.length : 0;
      }
    }
    else {
      M.error("Unsupported function: "+func);
    }

    // get the first item here
    if (!doNext()) M.macro.nextIndex = nextIndex+1;

    return FINISH;
  }

  /** Perform next foreach iteration
   *  @return true if iteration was completed
   */
  public boolean doNext () {
    String line; // Used with tf!=null

    if (enumeration!=null && enumeration.hasMoreElements()) {
      Object key = enumeration.nextElement();
      if (doValues) {
        MR.put(item,map.get(key));
      } else {
        MR.put(item,key);
      }
    }
    else if (iterator!=null && iterator.hasNext()) {
      MR.put(item,iterator.next());
    }
    else if (kvi!=null && kvi.getNext()) {
      if (doValues) {
        MR.put(item, kvi.value);
      } else {
        MR.put(item, kvi.key);
      }
    }
    else if (kwi!=null && kwi.getNext()) {
      MR.put(item,kwi);
    }
    else if (df!=null && df.read(data)>0) {
      MR.put(item,data);
    }
    else if (tf!=null && (line=tf.readProper())!=null) {
      MR.put(item,line);
    }
    else if (list!=null && !reverse && index<size) {
      MR.put(item,list.get(++index));
    }
    else if (list!=null && reverse && index>0) {
      MR.put(item,list.get(index--));
    }
    else if (array!=null && !reverse && index<array.length) {
      MR.put(item,array[index++]);
    }
    else if (array!=null && reverse && index>0) {
      MR.put(item,array[--index]);
    }
    else if (size>0 && !reverse && index<size) {
      MR.put(item,index++);
    }
    else if (size>0 && reverse && index>0) {
      MR.put(item,--index);
    }
    else {
      if (df!=null) df.close();
      if (tf!=null) tf.close();
      return false;
    }
    return true;
  }

  /** Terminates foreach
   *  @return "ENDFOR"
   */
  public String autoTerminate() { return "ENDFOR"; }

}
