// Copyright (c) 2001, 2002  Per M.A. Bothner and Brainfood Inc.
// This is free software;  for terms and warranty disclaimer see ./COPYING.

package gnu.lists;

/** A class to handle general multi-dimensional arrays.
 * This class is <strong>unfinished</strong>.
 * If the number of dimensions (the "rank") is one, should use
 * a class that implements Sequence.
 * GeneralArray uses a SimpleVector 'base' to store the actual data, and
 * provides general linear mapping from the array indexes to an
 * element index in the 'base' SimpleVector.  Thus such uperations as
 * transposing an array can be implement as just creating a simple
 * re-mapping of the indexes. */

public class GeneralArray extends AbstractSequence
implements Array //, Consumable
// Should implement Collection?
// Note this intentionally does not implement Sequence.
{
  SimpleVector base;
  int[] dimensions;
  int[] strides;
  int[] lowBounds;
  static final int[] zeros = new int[8];
  int offset;
  boolean simple = true;

  public static Array makeSimple(int[] lowBounds, int[] dimensions,
				 SimpleVector base)
  {
    int d = dimensions.length;
    if (lowBounds == null)
      {
	lowBounds = zeros;
	if (d > lowBounds.length)
	  lowBounds = new int[d];
      }
    if (d == 1 && lowBounds[0] == 0)
      return base;
    GeneralArray array = new GeneralArray();
    int[] strides = new int[d];
    int n = 1;
    for (int i = d;  --i >= 0; )
      {
	strides[i] = n;
	n *= dimensions[i];
      }
    array.strides = strides;
    array.dimensions = dimensions;
    array.lowBounds = lowBounds;
    array.base = base;
    return array;
  }

  public GeneralArray()
  {
  }

  public GeneralArray(int[] dimensions)
  {
    int total = 1;
    int rank = dimensions.length;
    if (rank <= zeros.length)
      lowBounds = zeros;
    else
      lowBounds = new int[rank]; 
    int[] strides = new int[rank];
    for (int i = rank;  --i >= 0; )
      {
	strides[i] = total;
	total *= dimensions[i];
      }
    base = new FVector(total);
    this.dimensions = dimensions;
    this.offset = 0;
  }

  public int rank() { return dimensions.length; }

  public int getEffectiveIndex(int[] indexes)
  {
    int result = offset;
    for (int i = dimensions.length;  --i >= 0; )
      {
	int index = indexes[i];
	int low = lowBounds[i];
	if (index < low || (index -= low) >= dimensions[i])
	  throw new IndexOutOfBoundsException();
	result += strides[i] * index;
      }
    return result;
  }

  public Object get (int index)
  {
    return getRowMajor(index);
  }

  public int createPos(int index, boolean isAfter)
  {
    int total = offset;
    for (int i = dimensions.length;  --i >= 0; )
      {
	int dim = dimensions[i];
	int cur = index % dim;
	index = index / dim;
	total = total + strides[i] * cur;
      }
    return (total << 1) | (isAfter ? 1 : 0);
  }

  public Object getRowMajor(int index)
  {
    if (simple)
      return base.get(index);
    int total = offset;
    for (int i = dimensions.length;  --i >= 0; )
      {
	int dim = dimensions[i];
	int cur = index % dim;
	index = index / dim;
	total = total + strides[i] * cur;
      }
    return base.get(total);
  }

  public Object get(int[] indexes)
  {
    return base.get(getEffectiveIndex(indexes));
  }

  public Object set(int[] indexes, Object value)
  {
    return base.set(getEffectiveIndex(indexes), value);
  }

  /** See java.util.Collection. */
  public int size()
  {
    int total = 1;
    for (int i = dimensions.length;  --i >= 0; )
      total *= dimensions[i];
    return total;
  }

  public int getLowBound(int dim)
  {
    return lowBounds[dim];
  }

  public int getSize(int dim)
  {
    return dimensions[dim];
  }

  public Array transpose(int[] lowBounds, int[] dimensions,
			 int offset0, int[] factors)
  {
    GeneralArray array =
      dimensions.length == 1 && lowBounds[0] == 0 ? new GeneralArray1()
      : new GeneralArray();
    array.offset = offset0;
    array.strides = factors;
    array.dimensions = dimensions;
    array.lowBounds = lowBounds;
    array.base = base;
    array.simple = false;
    return array;
  }

  public static void toString (Array array, StringBuffer sbuf)
  {
    sbuf.append("#<array");
    int r = array.rank();
    for (int i = 0;  i < r;  i++)
      {
	sbuf.append(' ');
	int lo = array.getLowBound(i);
	int sz = array.getSize(i);
	if (lo != 0)
	  {
	    sbuf.append(lo);
	    sbuf.append(':');
	  }
	sbuf.append(lo+sz);
      }
    sbuf.append('>');
  }

  public String toString ()
  {
    StringBuffer sbuf = new StringBuffer();
    toString(this, sbuf);
    return sbuf.toString();
  }
}
