/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.ma2;

import java.util.List;

/**
 * Indexes for Multidimensional arrays. An Index refers to a particular element of an array.
 * <p>
 * This is a generalization of index as int[]. Its main function is
 * to do the index arithmetic to translate an n-dim index into a 1-dim index.
 * The user obtains this by calling getIndex() on an Array.
 * The set() and seti() routines are convenience routines for 1-7 dim arrays.
 *
 * @author caron
 * @see Array
 */

public class Index implements Cloneable {
  public static final Index0D scalarIndexImmutable = new Index0D(); // immutable, so can be shared

  /**
   * Generate a subclass of Index optimized for this array's rank
   *
   * @param shape use this shape
   * @return a subclass of Index optimized for this array's rank
   */
  public static Index factory(int[] shape) {
    int rank = shape.length;
    switch (rank) {
      case 0:
        return new Index0D();
      case 1:
        return new Index1D(shape);
      case 2:
        return new Index2D(shape);
      case 3:
        return new Index3D(shape);
      case 4:
        return new Index4D(shape);
      case 5:
        return new Index5D(shape);
      case 6:
        return new Index6D(shape);
      case 7:
        return new Index7D(shape);
      default:
        return new Index(shape);
    }
  }

  private static Index factory(int rank) {
    switch (rank) {
      case 0:
        return new Index0D();
      case 1:
        return new Index1D();
      case 2:
        return new Index2D();
      case 3:
        return new Index3D();
      case 4:
        return new Index4D();
      case 5:
        return new Index5D();
      case 6:
        return new Index6D();
      case 7:
        return new Index7D();
      default:
        return new Index(rank);
    }
  }

  /**
   * Compute total number of elements in the array.
   * Stop at vlen
   *
   * @param shape length of array in each dimension.
   * @return total number of elements in the array.
   */
  public static long computeSize(int[] shape) {
    long product = 1;
    for (int aShape : shape) {
      if (aShape < 0)
        break; // stop at vlen
      product *= aShape;
    }
    return product;
  }

  /**
   * Compute standard strides based on array's shape.
   * Ignore vlen
   *
   * @param shape length of array in each dimension.
   * @param stride put result here
   * @return standard strides based on array's shape.
   */
  private static long computeStrides(int[] shape, int[] stride) {
    long product = 1;
    for (int ii = shape.length - 1; ii >= 0; ii--) {
      int thisDim = shape[ii];
      if (thisDim < 0)
        continue; // ignore vlen
      stride[ii] = (int) product;
      product *= thisDim;
    }
    return product;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////
  protected int[] shape;
  protected int[] stride;
  protected int rank;

  protected long size; // total number of elements
  protected int offset; // element = offset + stride[0]*current[0] + ...
  private boolean fastIterator = true; // use fast iterator if in canonical order

  protected int[] current; // current element's index, used only for the general case

  protected boolean hasvlen;

  /**
   * General case Index - use when you want to manipulate current elements yourself
   *
   * @param rank rank of the Index
   */
  protected Index(int rank) {
    this.rank = rank;
    shape = new int[rank];
    current = new int[rank];
    stride = new int[rank];
    hasvlen = false;
  }

  /**
   * Constructor for subclasses only.
   *
   * @param _shape describes an index section: slowest varying comes first (row major)
   */
  protected Index(int[] _shape) {
    this.shape = new int[_shape.length]; // optimization over clone
    System.arraycopy(_shape, 0, this.shape, 0, _shape.length);

    rank = shape.length;
    current = new int[rank];
    stride = new int[rank];
    size = computeStrides(shape, stride);
    offset = 0;
    hasvlen = (shape.length > 0 && shape[shape.length - 1] < 0);
  }

  /**
   * Constructor that lets you set the strides yourself.
   * This is used as a counter, not a description of an index section.
   *
   * @param _shape Index shape
   * @param _stride Index stride
   */
  public Index(int[] _shape, int[] _stride) {
    this.shape = new int[_shape.length]; // optimization over clone
    System.arraycopy(_shape, 0, this.shape, 0, _shape.length);

    this.stride = new int[_stride.length]; // optimization over clone
    System.arraycopy(_stride, 0, this.stride, 0, _stride.length);

    rank = shape.length;
    current = new int[rank];
    size = computeSize(shape);
    offset = 0;
    hasvlen = (shape.length > 0 && shape[shape.length - 1] < 0);
  }

  /**
   * subclass specialization/optimization calculations
   */
  protected void precalc() {}

  /**
   * Create a new Index based on current one, except
   * flip the index so that it runs from shape[index]-1 to 0.
   * Leave rightmost vlen alone.
   *
   * @param index dimension to flip
   * @return new index with flipped dimension
   */
  Index flip(int index) {
    if ((index < 0) || (index >= rank))
      throw new IllegalArgumentException();

    Index i = (Index) this.clone();
    if (shape[index] >= 0) { // !vlen case
      i.offset += stride[index] * (shape[index] - 1);
      i.stride[index] = -stride[index];
    }
    i.fastIterator = false;
    i.precalc(); // any subclass-specific optimizations
    return i;
  }

  /**
   * create a new Index based on a subsection of this one, with rank reduction if
   * dimension length == 1.
   *
   * @param ranges array of Ranges that specify the array subset.
   *        Must be same rank as original Array.
   *        A particular Range: 1) may be a subset; 2) may be null, meaning use entire Range.
   * @return new Index, with same or smaller rank as original.
   * @throws InvalidRangeException if ranges dont match current shape
   */
  Index section(List<Range> ranges) throws InvalidRangeException {

    // check ranges are valid
    if (ranges.size() != rank)
      throw new InvalidRangeException("Bad ranges [] length");
    for (int ii = 0; ii < rank; ii++) {
      Range r = ranges.get(ii);
      if (r == null)
        continue;
      if (r == Range.VLEN)
        continue;
      if ((r.first() < 0) || (r.first() >= shape[ii]))
        throw new InvalidRangeException("Bad range starting value at index " + ii + " == " + r.first());
      if ((r.last() < 0) || (r.last() >= shape[ii]))
        throw new InvalidRangeException("Bad range ending value at index " + ii + " == " + r.last());
    }

    int reducedRank = rank;
    for (Range r : ranges) {
      if ((r != null) && (r.length() == 1))
        reducedRank--;
    }
    Index newindex = Index.factory(reducedRank);
    newindex.offset = offset;

    // calc shape, size, and index transformations
    // calc strides into original (backing) store
    int newDim = 0;
    for (int ii = 0; ii < rank; ii++) {
      Range r = ranges.get(ii);
      if (r == null) { // null range means use the whole original dimension
        newindex.shape[newDim] = shape[ii];
        newindex.stride[newDim] = stride[ii];
        // if (name != null) newindex.name[newDim] = name[ii];
        newDim++;
      } else if (r.length() != 1) {
        newindex.shape[newDim] = r.length();
        newindex.stride[newDim] = stride[ii] * r.stride();
        newindex.offset += stride[ii] * r.first();
        // if (name != null) newindex.name[newDim] = name[ii];
        newDim++;
      } else {
        newindex.offset += stride[ii] * r.first(); // constant due to rank reduction
      }
    }
    newindex.size = computeSize(newindex.shape);
    newindex.fastIterator = fastIterator && (newindex.size == size); // if equal, then its not a real subset, so can
                                                                     // still use fastIterator
    newindex.precalc(); // any subclass-specific optimizations
    return newindex;
  }

  /**
   * create a new Index based on a subsection of this one, without rank reduction.
   *
   * @param ranges list of Ranges that specify the array subset.
   *        Must be same rank as original Array.
   *        A particular Range: 1) may be a subset; 2) may be null, meaning use entire Range.
   * @return new Index, with same rank as original.
   * @throws InvalidRangeException if ranges dont match current shape
   */
  Index sectionNoReduce(List<Range> ranges) throws InvalidRangeException {

    // check ranges are valid
    if (ranges.size() != rank)
      throw new InvalidRangeException("Bad ranges [] length");
    for (int ii = 0; ii < rank; ii++) {
      Range r = ranges.get(ii);
      if (r == null)
        continue;
      if (r == Range.VLEN)
        continue;
      if ((r.first() < 0) || (r.first() >= shape[ii]))
        throw new InvalidRangeException("Bad range starting value at index " + ii + " == " + r.first());
      if ((r.last() < 0) || (r.last() >= shape[ii]))
        throw new InvalidRangeException("Bad range ending value at index " + ii + " == " + r.last());
    }

    // allocate
    Index newindex = Index.factory(rank);
    newindex.offset = offset;

    // calc shape, size, and index transformations
    // calc strides into original (backing) store
    for (int ii = 0; ii < rank; ii++) {
      Range r = ranges.get(ii);
      if (r == null) { // null range means use the whole original dimension
        newindex.shape[ii] = shape[ii];
        newindex.stride[ii] = stride[ii];
      } else {
        newindex.shape[ii] = r.length();
        newindex.stride[ii] = stride[ii] * r.stride();
        newindex.offset += stride[ii] * r.first();
      }
      // if (name != null) newindex.name[ii] = name[ii];
    }
    newindex.size = computeSize(newindex.shape);
    newindex.fastIterator = fastIterator && (newindex.size == size); // if equal, then its not a real subset, so can
                                                                     // still use fastIterator
    newindex.precalc(); // any subclass-specific optimizations
    return newindex;
  }

  /**
   * Create a new Index based on current one by
   * eliminating any dimensions with length one.
   *
   * @return the new Index
   */
  Index reduce() {
    Index c = this;
    for (int ii = 0; ii < rank; ii++)
      if (shape[ii] == 1) { // do this on the first one you find
        Index newc = c.reduce(ii);
        return newc.reduce(); // any more to do?
      }
    return c;
  }

  /**
   * Create a new Index based on current one by
   * eliminating the specified dimension;
   *
   * @param dim: dimension to eliminate: must be of length one, else IllegalArgumentException
   * @return the new Index
   */
  Index reduce(int dim) {
    if ((dim < 0) || (dim >= rank))
      throw new IllegalArgumentException("illegal reduce dim " + dim);
    if (shape[dim] != 1)
      throw new IllegalArgumentException("illegal reduce dim " + dim + " : length != 1");

    Index newindex = Index.factory(rank - 1);
    newindex.offset = offset;
    int count = 0;
    for (int ii = 0; ii < rank; ii++) {
      if (ii != dim) {
        newindex.shape[count] = shape[ii];
        newindex.stride[count] = stride[ii];
        // if (name != null) newindex.name[count] = name[ii];

        count++;
      }
    }
    newindex.size = computeSize(newindex.shape);
    newindex.fastIterator = fastIterator;
    newindex.precalc(); // any subclass-specific optimizations
    return newindex;
  }

  /**
   * create a new Index based on current one, except
   * transpose two of the indices.
   *
   * @param index1 transpose these two indices
   * @param index2 transpose these two indices
   * @return new Index with transposed indices
   */
  Index transpose(int index1, int index2) {
    if ((index1 < 0) || (index1 >= rank))
      throw new IllegalArgumentException();
    if ((index2 < 0) || (index2 >= rank))
      throw new IllegalArgumentException();

    Index newIndex = (Index) this.clone();
    newIndex.stride[index1] = stride[index2];
    newIndex.stride[index2] = stride[index1];
    newIndex.shape[index1] = shape[index2];
    newIndex.shape[index2] = shape[index1];
    /*
     * if (name != null) {
     * newIndex.name[index1] = name[index2];
     * newIndex.name[index2] = name[index1];
     * }
     */

    newIndex.fastIterator = false;
    newIndex.precalc(); // any subclass-specific optimizations
    return newIndex;
  }

  /**
   * create a new Index based on a permutation of the current indices; vlen fails.
   *
   * @param dims: the old index dim[k] becomes the new kth index.
   * @return new Index with permuted indices
   */
  Index permute(int[] dims) {
    if (dims.length != shape.length)
      throw new IllegalArgumentException();
    for (int dim : dims)
      if ((dim < 0) || (dim >= rank))
        throw new IllegalArgumentException();

    boolean isPermuted = false;
    Index newIndex = (Index) this.clone();
    for (int i = 0; i < dims.length; i++) {
      newIndex.stride[i] = stride[dims[i]];
      newIndex.shape[i] = shape[dims[i]];
      // if (name != null) newIndex.name[i] = name[dims[i]];
      if (i != dims[i])
        isPermuted = true;
    }

    newIndex.fastIterator = fastIterator && !isPermuted; // useful optimization
    newIndex.precalc(); // any subclass-specific optimizations
    return newIndex;
  }


  /**
   * Get the number of dimensions in the array.
   *
   * @return the number of dimensions in the array.
   */
  public int getRank() {
    return rank;
  }

  /**
   * Get the shape: length of array in each dimension.
   *
   * @return the shape
   */
  public int[] getShape() {
    int[] result = new int[shape.length]; // optimization over clone
    System.arraycopy(shape, 0, result, 0, shape.length);
    return result;
  }

  /**
   * Get the length of the ith dimension.
   *
   * @param index which dimension. must be in [0, getRank())
   * @return the ith dimension length
   */
  public int getShape(int index) {
    return shape[index];
  }

  /**
   * Get an index iterator for traversing the array in canonical order.
   *
   * @param maa the array to iterate through
   * @return an index iterator for traversing the array in canonical order.
   * @see IndexIterator
   */
  IndexIterator getIndexIterator(Array maa) {
    if (fastIterator)
      return new IteratorFast(size, maa);
    else
      return new IteratorImpl(maa);
  }

  // debugging
  IndexIterator getSlowIndexIterator(Array maa) {
    return new IteratorImpl(maa);
  }

  IteratorFast getIndexIteratorFast(Array maa) {
    return new IteratorFast(size, maa);
  }

  boolean isFastIterator() {
    return fastIterator;
  }

  /**
   * Get the total number of elements in the array.
   *
   * @return the total number of elements in the array.
   */
  public long getSize() {
    return size;
  }

  /**
   * Get the current element's index into the 1D backing array.
   * VLEN stops processing.
   *
   * @return the current element's index into the 1D backing array.
   */
  public int currentElement() {
    int value = offset; // NB: dont have to check each index again
    for (int ii = 0; ii < rank; ii++) { // general rank
      if (shape[ii] < 0)
        break;// vlen
      value += current[ii] * stride[ii];
    }
    return value;
  }


  /**
   * Get the current counter.
   *
   * @return copy of the current counter.
   */
  public int[] getCurrentCounter() {
    return current.clone();
  }

  // only use from FastIterator, where the indices are not permuted

  /**
   * Set the current counter from the 1D "current element"
   * currElement = offset + stride[0]*current[0] + ...
   *
   * @param currElement set to this value
   */
  public void setCurrentCounter(int currElement) {
    currElement -= offset;
    for (int ii = 0; ii < rank; ii++) { // general rank
      if (shape[ii] < 0) {
        current[ii] = -1;
        break;
      }
      current[ii] = currElement / stride[ii];
      currElement -= current[ii] * stride[ii];
    }
    set(current); // transfer to subclass fields
  }

  /**
   * Increment the current element by 1. Used by IndexIterator.
   * General rank, with subclass specialization. Vlen skipped.
   *
   * @return currentElement()
   */
  public int incr() {
    int digit = rank - 1;
    while (digit >= 0) {
      if (shape[digit] < 0) {
        current[digit] = -1;
        continue;
      } // do not increment vlen
      current[digit]++;
      if (current[digit] < shape[digit])
        break; // normal exit
      current[digit] = 0; // else, carry
      digit--;
    }
    return currentElement();
  }


  /**
   * Set the current element's index. General-rank case.
   *
   * @param index set current value to these values
   * @return this, so you can use A.get(i.set(i))
   * @throws ArrayIndexOutOfBoundsException if index.length != rank.
   */
  public Index set(int[] index) {
    if (index.length != rank)
      throw new ArrayIndexOutOfBoundsException();
    if (rank == 0)
      return this;
    int prefixrank = (hasvlen ? rank : rank - 1);
    System.arraycopy(index, 0, current, 0, prefixrank);
    if (hasvlen)
      current[prefixrank] = -1;
    return this;
  }


  /**
   * set current element at dimension dim to v
   *
   * @param dim set this dimension
   * @param value to this value
   */
  public void setDim(int dim, int value) {
    if (value < 0 || value >= shape[dim]) // check index here
      throw new ArrayIndexOutOfBoundsException();
    if (shape[dim] >= 0) // !vlen
      current[dim] = value;
  }

  /**
   * set current element at dimension 0 to v
   *
   * @param v set 0th dimension index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set0(int v) {
    setDim(0, v);
    return this;
  }

  /**
   * set current element at dimension 1 to v
   *
   * @param v set dimension 1 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set1(int v) {
    setDim(1, v);
    return this;
  }

  /**
   * set current element at dimension 2 to v
   *
   * @param v set dimension 2 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set2(int v) {
    setDim(2, v);
    return this;
  }

  /**
   * set current element at dimension 3 to v
   *
   * @param v set dimension 3 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set3(int v) {
    setDim(3, v);
    return this;
  }

  /**
   * set current element at dimension 4 to v
   *
   * @param v set dimension 4 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set4(int v) {
    setDim(4, v);
    return this;
  }

  /**
   * set current element at dimension 5 to v
   *
   * @param v set dimension 5 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set5(int v) {
    setDim(5, v);
    return this;
  }

  /**
   * set current element at dimension 6 to v
   *
   * @param v set dimension 6 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set6(int v) {
    setDim(6, v);
    return this;
  }

  /**
   * set current element at dimension 0 to v0
   *
   * @param v0 set dimension 0 index to this value
   * @return this, so you can use A.get(i.set(i))
   */
  public Index set(int v0) {
    setDim(0, v0);
    return this;
  }

  /**
   * set current element at dimension 0,1 to v0,v1
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @return this, so you can use A.get(i.set(i,j))
   */
  public Index set(int v0, int v1) {
    setDim(0, v0);
    setDim(1, v1);
    return this;
  }

  /**
   * set current element at dimension 0,1,2 to v0,v1,v2
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @param v2 set dimension 2 index to this value
   * @return this, so you can use A.get(i.set(i,j,k))
   */
  public Index set(int v0, int v1, int v2) {
    setDim(0, v0);
    setDim(1, v1);
    setDim(2, v2);
    return this;
  }

  /**
   * set current element at dimension 0,1,2,3 to v0,v1,v2,v3
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @param v2 set dimension 2 index to this value
   * @param v3 set dimension 3 index to this value
   * @return this, so you can use A.get(i.set(i,j,k,l))
   */
  public Index set(int v0, int v1, int v2, int v3) {
    setDim(0, v0);
    setDim(1, v1);
    setDim(2, v2);
    setDim(3, v3);
    return this;
  }

  /**
   * set current element at dimension 0,1,2,3,4 to v0,v1,v2,v3,v4
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @param v2 set dimension 2 index to this value
   * @param v3 set dimension 3 index to this value
   * @param v4 set dimension 4 index to this value
   * @return this, so you can use A.get(i.set(i,j,k,l,m))
   */
  public Index set(int v0, int v1, int v2, int v3, int v4) {
    setDim(0, v0);
    setDim(1, v1);
    setDim(2, v2);
    setDim(3, v3);
    setDim(4, v4);
    return this;
  }

  /**
   * set current element at dimension 0,1,2,3,4,5 to v0,v1,v2,v3,v4,v5
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @param v2 set dimension 2 index to this value
   * @param v3 set dimension 3 index to this value
   * @param v4 set dimension 4 index to this value
   * @param v5 set dimension 5 index to this value
   * @return this, so you can use A.get(i.set(i,j,k,l,m,n))
   */
  public Index set(int v0, int v1, int v2, int v3, int v4, int v5) {
    setDim(0, v0);
    setDim(1, v1);
    setDim(2, v2);
    setDim(3, v3);
    setDim(4, v4);
    setDim(5, v5);
    return this;
  }

  /**
   * set current element at dimension 0,1,2,3,4,5,6 to v0,v1,v2,v3,v4,v5,v6
   *
   * @param v0 set dimension 0 index to this value
   * @param v1 set dimension 1 index to this value
   * @param v2 set dimension 2 index to this value
   * @param v3 set dimension 3 index to this value
   * @param v4 set dimension 4 index to this value
   * @param v5 set dimension 5 index to this value
   * @param v6 set dimension 6 index to this value
   * @return this, so you can use A.get(i.set(i,j,k,l,m,n,p))
   */
  public Index set(int v0, int v1, int v2, int v3, int v4, int v5, int v6) {
    setDim(0, v0);
    setDim(1, v1);
    setDim(2, v2);
    setDim(3, v3);
    setDim(4, v4);
    setDim(5, v5);
    setDim(6, v6);
    return this;
  }


  /**
   * String representation
   *
   * @return String representation
   */
  public String toStringDebug() {
    StringBuilder sbuff = new StringBuilder(100);
    sbuff.setLength(0);

    sbuff.append(" shape= ");
    for (int ii = 0; ii < rank; ii++) {
      sbuff.append(shape[ii]);
      sbuff.append(" ");
    }

    sbuff.append(" stride= ");
    for (int ii = 0; ii < rank; ii++) {
      sbuff.append(stride[ii]);
      sbuff.append(" ");
    }

    /*
     * if (name != null) {
     * sbuff.append(" names= ");
     * for (int ii = 0; ii < rank; ii++) {
     * sbuff.append(name[ii]);
     * sbuff.append(" ");
     * }
     * }
     */

    sbuff.append(" offset= ").append(offset);
    sbuff.append(" rank= ").append(rank);
    sbuff.append(" size= ").append(size);

    sbuff.append(" current= ");
    for (int ii = 0; ii < rank; ii++) {
      sbuff.append(current[ii]);
      sbuff.append(" ");
    }

    return sbuff.toString();
  }

  public String toString() {
    StringBuilder sbuff = new StringBuilder(100);
    sbuff.setLength(0);
    for (int ii = 0; ii < rank; ii++) {
      if (ii > 0)
        sbuff.append(",");
      sbuff.append(current[ii]);
    }
    return sbuff.toString();
  }

  public Object clone() {
    Index i;
    try {
      i = (Index) super.clone();
    } catch (CloneNotSupportedException e) {
      return null;
    }
    i.stride = stride.clone();
    i.shape = shape.clone();
    i.current = new int[rank]; // want zeros

    // if (name != null) i.name = name.clone();

    return i;
  }

  //////////////////////////////////////////////////////////////
  /*
   * Set the name of one of the indices.
   *
   * @param dim which index?
   * 
   * @param indexName name of index
   *
   * public void setIndexName(int dim, String indexName) {
   * if (name == null) name = new String[rank];
   * name[dim] = indexName;
   * }
   * 
   * /*
   * Get the name of one of the indices.
   *
   * @param dim which index?
   * 
   * @return name of index, or null if none.
   *
   * public String getIndexName(int dim) {
   * if (name == null) return null;
   * return name[dim];
   * }
   */

  private class IteratorImpl implements IndexIterator {
    private int count;
    private int currElement;
    private Index counter;
    private Array maa;

    private IteratorImpl(Array maa) {
      this.maa = maa;
      counter = (Index) Index.this.clone(); // could be subtype of Index
      if (rank > 0)
        counter.current[rank - 1] = -1; // avoid "if first" on every incr.
      counter.precalc();
    }

    public boolean hasNext() {
      return count < size;
    }

    public String toString() {
      return counter.toString();
    }

    public int[] getCurrentCounter() {
      return counter.getCurrentCounter();
    }

    public Object next() {
      count++;
      currElement = counter.incr();
      return maa.getObject(currElement);
    }

    public double getDoubleCurrent() {
      return maa.getDouble(currElement);
    }

    public double getDoubleNext() {
      count++;
      currElement = counter.incr();
      return maa.getDouble(currElement);
    }

    public void setDoubleCurrent(double val) {
      maa.setDouble(currElement, val);
    }

    public void setDoubleNext(double val) {
      count++;
      currElement = counter.incr();
      maa.setDouble(currElement, val);
    }

    public float getFloatCurrent() {
      return maa.getFloat(currElement);
    }

    public float getFloatNext() {
      count++;
      currElement = counter.incr();
      return maa.getFloat(currElement);
    }

    public void setFloatCurrent(float val) {
      maa.setFloat(currElement, val);
    }

    public void setFloatNext(float val) {
      count++;
      currElement = counter.incr();
      maa.setFloat(currElement, val);
    }

    public long getLongCurrent() {
      return maa.getLong(currElement);
    }

    public long getLongNext() {
      count++;
      currElement = counter.incr();
      return maa.getLong(currElement);
    }

    public void setLongCurrent(long val) {
      maa.setLong(currElement, val);
    }

    public void setLongNext(long val) {
      count++;
      currElement = counter.incr();
      maa.setLong(currElement, val);
    }

    public int getIntCurrent() {
      return maa.getInt(currElement);
    }

    public int getIntNext() {
      count++;
      currElement = counter.incr();
      return maa.getInt(currElement);
    }

    public void setIntCurrent(int val) {
      maa.setInt(currElement, val);
    }

    public void setIntNext(int val) {
      count++;
      currElement = counter.incr();
      maa.setInt(currElement, val);
    }

    public short getShortCurrent() {
      return maa.getShort(currElement);
    }

    public short getShortNext() {
      count++;
      currElement = counter.incr();
      return maa.getShort(currElement);
    }

    public void setShortCurrent(short val) {
      maa.setShort(currElement, val);
    }

    public void setShortNext(short val) {
      count++;
      currElement = counter.incr();
      maa.setShort(currElement, val);
    }

    public byte getByteCurrent() {
      return maa.getByte(currElement);
    }

    public byte getByteNext() {
      count++;
      currElement = counter.incr();
      return maa.getByte(currElement);
    }

    public void setByteCurrent(byte val) {
      maa.setByte(currElement, val);
    }

    public void setByteNext(byte val) {
      count++;
      currElement = counter.incr();
      maa.setByte(currElement, val);
    }

    public char getCharCurrent() {
      return maa.getChar(currElement);
    }

    public char getCharNext() {
      count++;
      currElement = counter.incr();
      return maa.getChar(currElement);
    }

    public void setCharCurrent(char val) {
      maa.setChar(currElement, val);
    }

    public void setCharNext(char val) {
      count++;
      currElement = counter.incr();
      maa.setChar(currElement, val);
    }

    public boolean getBooleanCurrent() {
      return maa.getBoolean(currElement);
    }

    public boolean getBooleanNext() {
      count++;
      currElement = counter.incr();
      return maa.getBoolean(currElement);
    }

    public void setBooleanCurrent(boolean val) {
      maa.setBoolean(currElement, val);
    }

    public void setBooleanNext(boolean val) {
      count++;
      currElement = counter.incr();
      maa.setBoolean(currElement, val);
    }

    public Object getObjectCurrent() {
      return maa.getObject(currElement);
    }

    public Object getObjectNext() {
      count++;
      currElement = counter.incr();
      return maa.getObject(currElement);
    }

    public void setObjectCurrent(Object val) {
      maa.setObject(currElement, val);
    }

    public void setObjectNext(Object val) {
      count++;
      currElement = counter.incr();
      maa.setObject(currElement, val);
    }
  }

}
