import gnu.mapping.*;
import gnu.lists.*;

/*
 * Based on map by Per Bothner
 * This code is licensed under the Gnu Public License
 * See COPYING from the original Kawa distribution for full terms.
 */

public class mapx extends ProcedureN
{
    public static final mapx mapxFunc = new mapx("mapx", true);
    public static final mapx foreachxFunc = new mapx("for-eachx", false);

    private boolean collect;

    public mapx(String name, boolean collect)
    {
        super(name);
        this.collect = collect;
    }

  /** An optimized single-list version of map. */
  static public Object map1 (Procedure proc, Object list) throws Throwable
  {
    Object result = LList.Empty;
    Pair last = null;
    while (list != LList.Empty)
      {
	Pair pair = (Pair) list;
	Pair new_pair = new Pair (proc.apply1 (pair.car), LList.Empty);
	if (last == null)
	  result = new_pair;
	else
	  last.cdr = new_pair;
	last = new_pair;
	list = pair.cdr;
      }
    return result;
  }

  /** An optimized single-list version of map. */
  static public Object map1j (Procedure proc, java.util.Iterator list) throws Throwable
  {
    Object result = LList.Empty;
    Pair last = null;
    while (list.hasNext())
      {
	Pair new_pair = new Pair (proc.apply1 (list.next()), LList.Empty);
	if (last == null)
	  result = new_pair;
	else
	  last.cdr = new_pair;
	last = new_pair;
      }
    return result;
  }

  /** An optimized single-list version of map. */
  static public Object map1je (Procedure proc, java.util.Enumeration list) throws Throwable
  {
    Object result = LList.Empty;
    Pair last = null;
    while (list.hasMoreElements())
      {
	Pair new_pair = new Pair (proc.apply1 (list.nextElement()), LList.Empty);
	if (last == null)
	  result = new_pair;
	else
	  last.cdr = new_pair;
	last = new_pair;
      }
    return result;
  }

  /** An optimized single-list version of map. */
  static public Object map1a (Procedure proc, java.lang.Object arr) throws Throwable
  {
    Object result = LList.Empty;
    Pair last = null;
    int len = java.lang.reflect.Array.getLength(arr);
    for (int i=0; i < len; i++)
      {
	Pair new_pair = new Pair (proc.apply1 (java.lang.reflect.Array.get(arr, i)), LList.Empty);
	if (last == null)
	  result = new_pair;
	else
	  last.cdr = new_pair;
	last = new_pair;
      }
    return result;
  }

  /** An optimized single-list version of for-each. */
  static public void forEach1 (Procedure proc, Object list) throws Throwable
  {
    while (list != LList.Empty)
      {
	Pair pair = (Pair) list;
	proc.apply1 (pair.car);
	list = pair.cdr;
      }
  }

  /** An optimized single-list version of for-each. */
  static public void forEach1j (Procedure proc, java.util.Iterator list) throws Throwable
  {
    while (list.hasNext())
      {
	proc.apply1 (list.next());
      }
  }

  /** An optimized single-list version of for-each. */
  static public void forEach1je (Procedure proc, java.util.Enumeration list) throws Throwable
  {
    while (list.hasMoreElements())
      {
	proc.apply1 (list.nextElement());
      }
  }

  /** An optimized single-list version of for-each. */
  static public void forEach1a (Procedure proc, Object arr) throws Throwable
  {
    int len = java.lang.reflect.Array.getLength(arr);
    for (int i=0; i < len; i++)
      {
	proc.apply1 (java.lang.reflect.Array.get(arr, i));
      }
  }

  public Object apply2 (Object arg1, Object arg2) throws Throwable
  {
    Procedure proc = (Procedure) arg1;
    if (collect) {
        if (arg2 instanceof Pair)
            return map1 (proc, arg2);
        else if (arg2 instanceof java.util.Collection)
            return map1j(proc, ((java.util.Collection) arg2).iterator());
        else if (arg2 instanceof java.util.Iterator)
            return map1j(proc, (java.util.Iterator) arg2);
        else if (arg2 instanceof gnu.lists.Sequence)
            return map1je(proc, ((gnu.lists.Sequence) arg2).elements());
        else if (arg2 instanceof java.util.Enumeration)
            return map1je(proc, (java.util.Enumeration) arg2);
        else if (arg2.getClass().isArray())
            return map1a(proc, arg2);
        else
            return map1 (proc, arg2);
    }
    if (arg2 instanceof Pair)
        forEach1 (proc, arg2);
    else if (arg2 instanceof java.util.Collection)
        forEach1j(proc, ((java.util.Collection) arg2).iterator());
    else if (arg2 instanceof java.util.Iterator)
        forEach1j(proc, (java.util.Iterator) arg2);
    else if (arg2 instanceof gnu.lists.Sequence)
        forEach1je(proc, ((gnu.lists.Sequence) arg2).elements());
    else if (arg2 instanceof java.util.Enumeration)
        forEach1je(proc, (java.util.Enumeration) arg2);
    else if (arg2.getClass().isArray())
        forEach1a(proc, arg2);
    else
        forEach1 (proc, arg2);
    return Values.empty;
  }

  public Object applyN (Object[] args) throws Throwable
  {
    Procedure proc = (Procedure) (args[0]);
    int arity = args.length - 1;
    if (arity == 1)
      {
    if (collect) {
        if (args[1] instanceof Pair)
            return map1 (proc, args[1]);
        else if (args[1] instanceof java.util.Collection)
            return map1j(proc, ((java.util.Collection) args[1]).iterator());
        else if (args[1] instanceof java.util.Iterator)
            return map1j(proc, (java.util.Iterator) args[1]);
        else if (args[1] instanceof gnu.lists.Sequence)
            return map1je(proc, ((gnu.lists.Sequence) args[1]).elements());
        else if (args[1] instanceof java.util.Enumeration)
            return map1je(proc, (java.util.Enumeration) args[1]);
        else if (args[1].getClass().isArray())
            return map1a(proc, args[1]);
        else
            return map1 (proc, args[1]);
    }
    if (args[1] instanceof Pair)
        forEach1 (proc, args[1]);
    else if (args[1] instanceof java.util.Collection)
        forEach1j(proc, ((java.util.Collection) args[1]).iterator());
    else if (args[1] instanceof java.util.Iterator)
        forEach1j(proc, (java.util.Iterator) args[1]);
    else if (args[1] instanceof gnu.lists.Sequence)
        forEach1je(proc, ((gnu.lists.Sequence) args[1]).elements());
    else if (args[1] instanceof java.util.Enumeration)
        forEach1je(proc, (java.util.Enumeration) args[1]);
    else if (args[1].getClass().isArray())
        forEach1a(proc, args[1]);
    else
        forEach1 (proc, args[1]);

	return Values.empty;
      }
    Object result;
    Pair last = null;
    if (collect)
      result = LList.Empty;
    else
      result = Values.empty;;
    Object[] rest = new Object [arity];
    java.util.Iterator[] jrest = null;
    java.util.Enumeration[] jerest = null;
    int restArraySize[] = null;
    System.arraycopy (args, 1, rest, 0, arity);
    for (int i=0; i < arity; i++)
    {
        if (rest[i] instanceof java.util.Collection)
        {
            if (jrest == null) jrest = new java.util.Iterator[arity];
            jrest[i] = ((java.util.Collection) rest[i]).iterator();
            rest[i] = null;
        }
        else if (rest[i] instanceof java.util.Iterator)
        {
            if (jrest == null) jrest = new java.util.Iterator[arity];
            jrest[i] = (java.util.Iterator) rest[i];
            rest[i] = null;
        }
        else if (rest[i] instanceof gnu.lists.Sequence)
        {
            if (jerest == null) jerest = new java.util.Enumeration[arity];
            jerest[i] = ((gnu.lists.Sequence) rest[i]).elements();
            rest[i] = null;
        }
        else if (rest[i] instanceof java.util.Enumeration)
        {
            if (jerest == null) jerest = new java.util.Enumeration[arity];
            jerest[i] = (java.util.Enumeration) rest[i];
            rest[i] = null;
        }
        else if (rest[i].getClass().isArray())
        {
            if (restArraySize == null)
            {
                restArraySize = new int[arity];
                for (int j=0; j < arity; j++) restArraySize[j] = -1;
            }
            restArraySize[i] = java.lang.reflect.Array.getLength(rest[i]);
        }
    }

    Object[] each_args = new Object [arity];
    int currSize = 0;
    for (;;)
      {
	for (int i = 0;  i < arity;  i++)
	  {
	    Object list = rest[i];
	    if (list == LList.Empty)
	      return result;
        if (list instanceof Pair)
        {
	        Pair pair = (Pair) list;
	        each_args[i] = pair.car;
	        rest[i] = pair.cdr;
        }
        else if ((jrest != null) && (jrest[i] != null))
        {
            if (!jrest[i].hasNext()) return result;
            each_args[i] = jrest[i].next();
        }
        else if ((jerest != null) && (jerest[i] != null))
        {
            if (!jerest[i].hasMoreElements()) return result;
            each_args[i] = jerest[i].nextElement();
        }
        else if ((restArraySize != null) && (restArraySize[i] >= 0))
        {
            if (restArraySize[i] <= currSize) return result;
            each_args[i] = java.lang.reflect.Array.get(rest[i], currSize);
        }
        else
        {
            return result;
        }
	  }
      currSize++;
	Object value = proc.applyN (each_args);
	if (collect)
	  {
	    Pair new_pair = new Pair (value, LList.Empty);
	    if (last == null)
	      result = new_pair;
	    else
	      last.cdr = new_pair;
	    last = new_pair;
	  }
      }
  }
}

