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

import ucar.nc2.util.Indent;
import java.io.IOException;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;

/**
 * Superclass for implementations of Array of StructureData.
 * <p/>
 * The general way to access data in an ArrayStructure is to use
 * 
 * <pre>
 *  StructureData getStructureData(Index index).
 * </pre>
 * <p/>
 * For 1D arrays (or by calculating your own recnum for nD arrays), you can also use:
 * 
 * <pre>
 *  StructureData getStructureData(int recnum).
 * </pre>
 * <p/>
 * Once you have a StructureData object, you can access data in a general way by using:
 * 
 * <pre>
 *  Array StructureData.getArray(Member m)
 * </pre>
 * <p/>
 * When dealing with large arrays of Structures, there can be significant overhead in using the generic interfaces.
 * A number of convenience routines may be able to avoid extra Object creation, and so are recommended for efficiency.
 * The following may avoid the overhead of creating the StructureData object:
 * 
 * <pre>
 *  Array getArray(int recno, StructureMembers.Member m)
 * </pre>
 * <p/>
 * The following can be convenient for accessing all the data in the ArrayStructure for one member, but its efficiency
 * depends on the implementation:
 * 
 * <pre>
 *  Array getMemberArray(StructureMembers.Member m)
 * </pre>
 * <p/>
 * These require that you know the data types of the member data, but they are the most efficent:
 * 
 * <pre>
 * getScalarXXX(int recnum, Member m)
 * getJavaArrayXXX(int recnum, Member m)
 * </pre>
 * 
 * where XXX is Byte, Char, Double, Float, Int, Long, Short, or String. For members that are themselves Structures,
 * the equivilent is:
 * 
 * <pre>
 * StructureData getScalarStructure(int recnum, Member m)
 * ArrayStructure getArrayStructure(int recnum, Member m)
 * </pre>
 *
 * These will return any compatible type as a double or float, but will have extra overhead when the types dont match:
 * 
 * <pre>
 * convertScalarXXX(int recnum, Member m)
 * convertJavaArrayXXX(int recnum, Member m)
 * </pre>
 * 
 * where XXX is Double or Float
 *
 * @author caron
 * @see Array
 * @see StructureData
 */
public abstract class ArrayStructure extends Array implements Iterable<StructureData> {

  /*
   * Implementation notes
   * ArrayStructure contains the default implementation of storing the data in individual member arrays.
   * ArrayStructureMA uses all of these.
   * ArrayStructureW uses some of these.
   * ArrayStructureBB override all such methods.
   */

  protected StructureMembers members;
  protected int nelems;
  protected StructureData[] sdata;

  /**
   * Create a new Array of type StructureData and the given members and shape.
   * dimensions.length determines the rank of the new Array.
   *
   * @param members a description of the structure members
   * @param shape the shape of the Array.
   */
  protected ArrayStructure(StructureMembers members, int[] shape) {
    super(DataType.STRUCTURE, shape);
    this.members = members;
    this.nelems = (int) indexCalc.getSize();
  }

  // for subclasses to create views
  protected ArrayStructure(StructureMembers members, Index ima) {
    super(DataType.STRUCTURE, ima);
    this.members = members;
    this.nelems = (int) indexCalc.getSize();
  }

  // copy from javaArray to storage using the iterator: used by factory( Object);
  @Override
  protected void copyFrom1DJavaArray(IndexIterator iter, Object javaArray) {
    Object[] ja = (Object[]) javaArray;
    for (Object aJa : ja)
      iter.setObjectNext(aJa);
  }

  // copy to javaArray from storage using the iterator: used by copyToNDJavaArray;
  @Override
  protected void copyTo1DJavaArray(IndexIterator iter, Object javaArray) {
    Object[] ja = (Object[]) javaArray;
    for (int i = 0; i < ja.length; i++)
      ja[i] = iter.getObjectNext();
  }

  @Override
  public Class getElementType() {
    return StructureData.class;
  }

  /**
   * Get the StructureMembers object.
   *
   * @return the StructureMembers object.
   */
  public StructureMembers getStructureMembers() {
    return members;
  }

  /**
   * Get a list of structure members.
   *
   * @return the structure members.
   */
  public List<StructureMembers.Member> getMembers() {
    return members.getMembers();
  }

  /**
   * Get a list structure member names.
   *
   * @return the structure members.
   */
  public List<String> getStructureMemberNames() {
    return members.getMemberNames();
  }

  /**
   * Find a member by its name.
   *
   * @param memberName find member with this name
   * @return StructureMembers.Member matching the name, or null if not found
   */
  public StructureMembers.Member findMember(String memberName) {
    return members.findMember(memberName);
  }

  @Override
  public long getSizeBytes() {
    return indexCalc.getSize() * members.getStructureSize();
  }

  /**
   * Get the index-th StructureData of this ArrayStructure.
   *
   * @param i which one to get, specified by an Index.
   * @return object of type StructureData.
   */
  @Override
  public Object getObject(Index i) {
    return getObject(i.currentElement());
  }

  /**
   * Set one of the StructureData of this ArrayStructure.
   *
   * @param i which one to set, specified by an Index.
   * @param value must be type StructureData.
   */
  @Override
  public void setObject(Index i, Object value) {
    setObject(i.currentElement(), value);
  }

  /**
   * Get the index-th StructureData of this ArrayStructure.
   *
   * @param index which one to get, specified by an integer.
   * @return object of type StructureData.
   */
  @Override
  public Object getObject(int index) {
    return getStructureData(index);
  }

  /**
   * Set the index-th StructureData of this ArrayStructure.
   *
   * @param index which one to set.
   * @param value must be type StructureData.
   */
  @Override
  public void setObject(int index, Object value) {
    if (sdata == null)
      sdata = new StructureData[nelems];
    sdata[index] = (StructureData) value;
  }

  /**
   * Get the index-th StructureData of this ArrayStructure.
   *
   * @param i which one to get, specified by an Index.
   * @return object of type StructureData.
   */
  public StructureData getStructureData(Index i) {
    return getStructureData(i.currentElement());
  }

  /**
   * Get the index-th StructureData of this ArrayStructure.
   *
   * @param index which one to get, specified by an integer.
   * @return object of type StructureData.
   */
  public StructureData getStructureData(int index) {
    if (sdata == null)
      sdata = new StructureData[nelems];
    if (index >= sdata.length)
      throw new IllegalArgumentException(index + " > " + sdata.length);
    if (sdata[index] == null)
      sdata[index] = makeStructureData(this, index);
    return sdata[index];
  }

  @Override
  public Object getStorage() {
    // this fills the sdata array
    for (int i = 0; i < nelems; i++)
      getStructureData(i);
    return sdata;
  }

  protected abstract StructureData makeStructureData(ArrayStructure as, int recno);

  /**
   * Get the size of each StructureData object in bytes.
   *
   * @return the size of each StructureData object in bytes.
   */
  public int getStructureSize() {
    return members.getStructureSize();
  }

  @Override
  public Iterator<StructureData> iterator() { // throws java.io.IOException {
    return new ArrayStructureIterator();
  }

  public StructureDataIterator getStructureDataIterator() { // throws java.io.IOException {
    return new ArrayStructureIterator();
  }

  public class ArrayStructureIterator implements StructureDataIterator, Iterator<StructureData> {
    private int count;
    private int size = (int) getSize();

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

    @Override
    public StructureData next() {
      return getStructureData(count++);
    }

    @Override
    public StructureDataIterator reset() {
      count = 0;
      return this;
    }

    @Override
    public int getCurrentRecno() {
      return count - 1;
    }

    // debugging
    public ArrayStructure getArrayStructure() {
      return ArrayStructure.this;
    }
  }

  ///////////////////////////////////////////////////////////////////////////////

  /**
   * Get member data of any type for a specific record as an Array.
   * This may avoid the overhead of creating the StructureData object, but is equivilent to
   * getStructure(recno).getArray( Member m).
   *
   * @param recno get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member.
   * @return Array values.
   */
  public Array getArray(int recno, StructureMembers.Member m) {

    DataType dataType = m.getDataType();
    switch (dataType) {

      case DOUBLE:
        double[] da = getJavaArrayDouble(recno, m);
        return Array.factory(dataType, m.getShape(), da);

      case FLOAT:
        float[] fa = getJavaArrayFloat(recno, m);
        return Array.factory(dataType, m.getShape(), fa);

      case BYTE:
      case UBYTE:
      case ENUM1:
        byte[] ba = getJavaArrayByte(recno, m);
        return Array.factory(dataType, m.getShape(), ba);

      case SHORT:
      case USHORT:
      case ENUM2:
        short[] sa = getJavaArrayShort(recno, m);
        return Array.factory(dataType, m.getShape(), sa);

      case INT:
      case UINT:
      case ENUM4:
        int[] ia = getJavaArrayInt(recno, m);
        return Array.factory(dataType, m.getShape(), ia);

      case ULONG:
      case LONG:
        long[] la = getJavaArrayLong(recno, m);
        return Array.factory(dataType, m.getShape(), la);

      case CHAR:
        char[] ca = getJavaArrayChar(recno, m);
        return Array.factory(dataType, m.getShape(), ca);

      case STRING:
        String[] str = getJavaArrayString(recno, m);
        return Array.factory(dataType, m.getShape(), str);

      case STRUCTURE:
        return getArrayStructure(recno, m);

      case SEQUENCE:
        return getArraySequence(recno, m);

      case OPAQUE:
        return getArrayObject(recno, m);
    }

    throw new RuntimeException("Dont have implemenation for " + dataType);


  }

  /**
   * Set data for one member, over all structures.
   * This is used by VariableDS to do scale/offset.
   *
   * @param m set data for this StructureMembers.Member.
   * @param memberArray Array values.
   */
  public void setMemberArray(StructureMembers.Member m, Array memberArray) {
    m.setDataArray(memberArray);
    if (memberArray instanceof ArrayStructure) { // LOOK
      ArrayStructure as = (ArrayStructure) memberArray;
      m.setStructureMembers(as.getStructureMembers());
    }
  }

  /**
   * Extract data for one member, over all structures.
   *
   * @param m get data from this StructureMembers.Member.
   * @return Array values.
   * @throws java.io.IOException on read error (only happens for Sequences, otherwise data is already read)
   */
  public Array extractMemberArray(StructureMembers.Member m) throws IOException {
    if (m.getDataArray() != null)
      return m.getDataArray();
    DataType dataType = m.getDataType();

    // combine the shapes
    int[] mshape = m.getShape();
    int rrank = rank + mshape.length;
    int[] rshape = new int[rrank];
    System.arraycopy(getShape(), 0, rshape, 0, rank);
    System.arraycopy(mshape, 0, rshape, rank, mshape.length);

    // create an empty array to hold the result
    Array result;
    if (dataType == DataType.STRUCTURE) {
      StructureMembers membersw = m.getStructureMembers().toBuilder(false).build();
      result = new ArrayStructureW(membersw, rshape);

    } else if (dataType == DataType.OPAQUE) {
      result = Array.factory(DataType.OPAQUE, rshape);

    } else {
      result = Array.factory(dataType, rshape);
    }

    IndexIterator resultIter = result.getIndexIterator();
    if (dataType == DataType.DOUBLE) {
      for (int recno = 0; recno < getSize(); recno++)
        copyDoubles(recno, m, resultIter);

    } else if (dataType == DataType.FLOAT) {
      for (int recno = 0; recno < getSize(); recno++)
        copyFloats(recno, m, resultIter);

    } else if (dataType.getPrimitiveClassType() == byte.class) {
      for (int recno = 0; recno < getSize(); recno++)
        copyBytes(recno, m, resultIter);

    } else if (dataType.getPrimitiveClassType() == short.class) {
      for (int recno = 0; recno < getSize(); recno++)
        copyShorts(recno, m, resultIter);

    } else if (dataType.getPrimitiveClassType() == int.class) {
      for (int recno = 0; recno < getSize(); recno++)
        copyInts(recno, m, resultIter);

    } else if (dataType.getPrimitiveClassType() == long.class) {
      for (int recno = 0; recno < getSize(); recno++)
        copyLongs(recno, m, resultIter);

    } else if (dataType == DataType.CHAR) {
      for (int recno = 0; recno < getSize(); recno++)
        copyChars(recno, m, resultIter);

    } else if ((dataType == DataType.STRING) || (dataType == DataType.OPAQUE)) {
      for (int recno = 0; recno < getSize(); recno++)
        copyObjects(recno, m, resultIter);

    } else if (dataType == DataType.STRUCTURE) {
      for (int recno = 0; recno < getSize(); recno++)
        copyStructures(recno, m, resultIter);

    } else if (dataType == DataType.SEQUENCE) {
      for (int recno = 0; recno < getSize(); recno++)
        copySequences(recno, m, resultIter);

    }

    return result;
  }

  protected void copyChars(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setCharNext(dataIter.getCharNext());
  }

  protected void copyDoubles(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setDoubleNext(dataIter.getDoubleNext());
  }

  protected void copyFloats(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setFloatNext(dataIter.getFloatNext());
  }

  protected void copyBytes(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setByteNext(dataIter.getByteNext());
  }

  protected void copyShorts(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setShortNext(dataIter.getShortNext());
  }

  protected void copyInts(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setIntNext(dataIter.getIntNext());
  }

  protected void copyLongs(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setLongNext(dataIter.getLongNext());
  }

  protected void copyObjects(int recnum, StructureMembers.Member m, IndexIterator result) {
    IndexIterator dataIter = getArray(recnum, m).getIndexIterator();
    while (dataIter.hasNext())
      result.setObjectNext(dataIter.getObjectNext());
  }

  // from the recnum-th structure, copy the member data into result.
  // member data is itself a structure, and may be an array of structures.
  protected void copyStructures(int recnum, StructureMembers.Member m, IndexIterator result) {
    Array data = getArray(recnum, m);
    IndexIterator dataIter = data.getIndexIterator();
    while (dataIter.hasNext())
      result.setObjectNext(dataIter.getObjectNext());
  }

  protected void copySequences(int recnum, StructureMembers.Member m, IndexIterator result) {
    // there can only be one sequence, not an array; copy to an ArrayObject
    Array data = getArray(recnum, m);
    result.setObjectNext(data);
  }

  /**
   * Get member data array of any type as an Object, eg, Float, Double, String, StructureData etc.
   *
   * @param recno get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member.
   * @return value as Float, Double, etc..
   */
  public Object getScalarObject(int recno, StructureMembers.Member m) {
    DataType dataType = m.getDataType();

    if (dataType == DataType.DOUBLE) {
      return getScalarDouble(recno, m);

    } else if (dataType == DataType.FLOAT) {
      return getScalarFloat(recno, m);

    } else if (dataType.getPrimitiveClassType() == byte.class) {
      return getScalarByte(recno, m);

    } else if (dataType.getPrimitiveClassType() == short.class) {
      return getScalarShort(recno, m);

    } else if (dataType.getPrimitiveClassType() == int.class) {
      return getScalarInt(recno, m);

    } else if (dataType.getPrimitiveClassType() == long.class) {
      return getScalarLong(recno, m);

    } else if (dataType == DataType.CHAR) {
      return getScalarString(recno, m);

    } else if (dataType == DataType.STRING) {
      return getScalarString(recno, m);

    } else if (dataType == DataType.STRUCTURE) {
      return getScalarStructure(recno, m);

    } else if (dataType == DataType.OPAQUE) {
      ArrayObject data = (ArrayObject) m.getDataArray();
      return data.getObject(recno * m.getSize()); // LOOK ??
    }

    throw new RuntimeException("Dont have implementation for " + dataType);
  }

  /////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Get scalar value as a float, with conversion as needed. Underlying type must be convertible to float.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m member Variable.
   * @return scalar float value
   * @throws ForbiddenConversionException if not convertible to float.
   */
  public float convertScalarFloat(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.FLOAT)
      return getScalarFloat(recnum, m);
    if (m.getDataType() == DataType.DOUBLE)
      return (float) getScalarDouble(recnum, m);
    Object o = getScalarObject(recnum, m);
    if (o instanceof Number)
      return ((Number) o).floatValue();
    throw new ForbiddenConversionException("Type is " + m.getDataType() + ", not convertible to float");
  }

  /**
   * Get scalar value as a double, with conversion as needed. Underlying type must be convertible to double.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m member Variable.
   * @return scalar double value
   * @throws ForbiddenConversionException if not convertible to double.
   */
  public double convertScalarDouble(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.DOUBLE)
      return getScalarDouble(recnum, m);
    if (m.getDataType() == DataType.FLOAT)
      return (double) getScalarFloat(recnum, m);
    Object o = getScalarObject(recnum, m);
    if (o instanceof Number)
      return ((Number) o).doubleValue();
    throw new ForbiddenConversionException("Type is " + m.getDataType() + ", not convertible to double");
  }

  /**
   * Get scalar value as an int, with conversion as needed. Underlying type must be convertible to int.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m member Variable.
   * @return scalar double value
   * @throws ForbiddenConversionException if not convertible to double.
   */
  public int convertScalarInt(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.INT || m.getDataType() == DataType.UINT)
      return getScalarInt(recnum, m);
    if (m.getDataType() == DataType.SHORT)
      return (int) getScalarShort(recnum, m);
    if (m.getDataType() == DataType.USHORT)
      return DataType.unsignedShortToInt(getScalarShort(recnum, m));
    if (m.getDataType() == DataType.BYTE)
      return (int) getScalarByte(recnum, m);
    if (m.getDataType() == DataType.UBYTE)
      return (int) DataType.unsignedByteToShort(getScalarByte(recnum, m));
    if (m.getDataType() == DataType.LONG || m.getDataType() == DataType.ULONG)
      return (int) getScalarLong(recnum, m);
    Object o = getScalarObject(recnum, m);
    if (o instanceof Number)
      return ((Number) o).intValue();
    throw new ForbiddenConversionException("Type is " + m.getDataType() + ", not convertible to int");
  }

  public long convertScalarLong(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.LONG || m.getDataType() == DataType.ULONG)
      return getScalarLong(recnum, m);
    if (m.getDataType() == DataType.INT)
      return (long) getScalarInt(recnum, m);
    if (m.getDataType() == DataType.UINT)
      return DataType.unsignedIntToLong(getScalarInt(recnum, m));
    if (m.getDataType() == DataType.SHORT)
      return (long) getScalarShort(recnum, m);
    if (m.getDataType() == DataType.USHORT)
      return (long) DataType.unsignedShortToInt(getScalarShort(recnum, m));
    if (m.getDataType() == DataType.BYTE)
      return (long) getScalarByte(recnum, m);
    if (m.getDataType() == DataType.UBYTE)
      return (long) DataType.unsignedByteToShort(getScalarByte(recnum, m));
    Object o = getScalarObject(recnum, m);
    if (o instanceof Number)
      return ((Number) o).longValue();
    throw new ForbiddenConversionException("Type is " + m.getDataType() + ", not convertible to int");
  }

  /////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Get scalar member data of type double.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type double.
   * @return scalar double value
   */
  public double getScalarDouble(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.DOUBLE)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be double");
    Array data = m.getDataArray();
    return data.getDouble(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type double as a 1D array. The member data may be any rank.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type double.
   * @return double[]
   */
  public double[] getJavaArrayDouble(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.DOUBLE)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be double");
    int count = m.getSize();
    Array data = m.getDataArray();
    double[] pa = new double[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getDouble(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type float.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type float.
   * @return scalar double value
   */
  public float getScalarFloat(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.FLOAT)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be float");
    Array data = m.getDataArray();
    return data.getFloat(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type float as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type float.
   * @return float[]
   */
  public float[] getJavaArrayFloat(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.FLOAT)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be float");
    int count = m.getSize();
    Array data = m.getDataArray();
    float[] pa = new float[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getFloat(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type byte.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type byte.
   * @return scalar double value
   */
  public byte getScalarByte(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == byte.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be byte");
    Array data = m.getDataArray();
    return data.getByte(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type byte as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type byte.
   * @return byte[]
   */
  public byte[] getJavaArrayByte(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == byte.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be byte");
    int count = m.getSize();
    Array data = m.getDataArray();
    byte[] pa = new byte[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getByte(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type short.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type short.
   * @return scalar double value
   */
  public short getScalarShort(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == short.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be short");
    Array data = m.getDataArray();
    return data.getShort(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type short as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type float.
   * @return short[]
   */
  public short[] getJavaArrayShort(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == short.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be short");
    int count = m.getSize();
    Array data = m.getDataArray();
    short[] pa = new short[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getShort(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type int.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type int.
   * @return scalar double value
   */
  public int getScalarInt(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == int.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be int");
    Array data = m.getDataArray();
    return data.getInt(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type int as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type int.
   * @return int[]
   */
  public int[] getJavaArrayInt(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == int.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be int");
    int count = m.getSize();
    Array data = m.getDataArray();
    int[] pa = new int[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getInt(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type long.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type long.
   * @return scalar double value
   */
  public long getScalarLong(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == long.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be long");
    Array data = m.getDataArray();
    return data.getLong(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type long as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type long.
   * @return long[]
   */
  public long[] getJavaArrayLong(int recnum, StructureMembers.Member m) {
    if (!(m.getDataType().getPrimitiveClassType() == long.class))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be long");
    int count = m.getSize();
    Array data = m.getDataArray();
    long[] pa = new long[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getLong(recnum * count + i);
    return pa;
  }

  /**
   * Get scalar member data of type char.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type char.
   * @return scalar double value
   */
  public char getScalarChar(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.CHAR)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be char");
    Array data = m.getDataArray();
    return data.getChar(recnum * m.getSize()); // gets first one in the array
  }

  /**
   * Get member data of type char as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type char.
   * @return char[]
   */
  public char[] getJavaArrayChar(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.CHAR)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be char");
    int count = m.getSize();
    Array data = m.getDataArray();
    char[] pa = new char[count];
    for (int i = 0; i < count; i++)
      pa[i] = data.getChar(recnum * count + i);
    return pa;
  }

  /**
   * Get member data of type String or char.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type String or char.
   * @return scalar String value
   */
  public String getScalarString(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.CHAR) {
      ArrayChar data = (ArrayChar) m.getDataArray();
      return data.getString(recnum);
    }

    if (m.getDataType() == DataType.STRING) {
      Array data = m.getDataArray();
      return (String) data.getObject(recnum);
    }

    throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be String or char");
  }

  /**
   * Get member data of type String as a 1D array.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type String.
   * @return String[]
   */
  public String[] getJavaArrayString(int recnum, StructureMembers.Member m) {
    if (m.getDataType() == DataType.STRING) {
      int n = m.getSize();
      String[] result = new String[n];
      Array data = m.getDataArray();
      for (int i = 0; i < n; i++)
        result[i] = (String) data.getObject(recnum * n + i);
      return result;
    }

    if (m.getDataType() == DataType.CHAR) {
      int strlen = indexCalc.getShape(rank - 1);
      int n = m.getSize() / strlen;
      String[] result = new String[n];
      ArrayChar data = (ArrayChar) m.getDataArray();
      for (int i = 0; i < n; i++)
        result[i] = data.getString((recnum * n + i) * strlen);
      return result;
    }

    throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be String or char");
  }

  /**
   * Get member data of type Structure.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type Structure.
   * @return scalar StructureData
   */
  public StructureData getScalarStructure(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.STRUCTURE)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be Structure");

    ArrayStructure data = (ArrayStructure) m.getDataArray();
    return data.getStructureData(recnum * m.getSize()); // gets first in the array
  }

  /**
   * Get member data of type array of Structure.
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type Structure.
   * @return nested ArrayStructure.
   */
  public ArrayStructure getArrayStructure(int recnum, StructureMembers.Member m) {
    if ((m.getDataType() != DataType.STRUCTURE) && (m.getDataType() != DataType.SEQUENCE))
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be Structure or Sequence");

    if (m.getDataType() == DataType.SEQUENCE)
      return getArraySequence(recnum, m);

    ArrayStructure array = (ArrayStructure) m.getDataArray();
    int count = m.getSize();
    StructureData[] this_sdata = new StructureData[count];
    for (int i = 0; i < count; i++)
      this_sdata[i] = array.getStructureData(recnum * count + i);

    // make a copy of the members, but remove the data arrays, since the structureData must be used instead
    StructureMembers membersw = array.getStructureMembers().toBuilder(false).build();
    return new ArrayStructureW(membersw, m.getShape(), this_sdata);
  }

  /**
   * Get member data of type ArraySequence
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type Structure.
   * @return nested ArrayStructure.
   */
  public ArraySequence getArraySequence(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.SEQUENCE)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be Sequence");

    // should store sequences as ArrayObject of ArraySequence objects
    ArrayObject array = (ArrayObject) m.getDataArray();
    return (ArraySequence) array.getObject(recnum);
  }

  /**
   * Get member data of type ArrayObject
   *
   * @param recnum get data from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
   * @param m get data from this StructureMembers.Member. Must be of type Structure.
   * @return ArrayObject.
   */
  public ArrayObject getArrayObject(int recnum, StructureMembers.Member m) {
    if (m.getDataType() != DataType.OPAQUE)
      throw new IllegalArgumentException("Type is " + m.getDataType() + ", must be Sequence");

    ArrayObject array = (ArrayObject) m.getDataArray();
    return (ArrayObject) array.getObject(recnum); // LOOK ??
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////
  public void showInternal(Formatter f, Indent indent) {
    f.format("%sArrayStructure %s size=%d class=%s hash=0x%x%n", indent, members.getName(), getSize(),
        this.getClass().getName(), hashCode());
  }

  public void showInternalMembers(Formatter f, Indent indent) {
    f.format("%sArrayStructure %s class=%s hash=0x%x%n", indent, members.getName(), this.getClass().getName(),
        hashCode());
    indent.incr();
    for (StructureMembers.Member m : getMembers())
      m.showInternal(f, indent);
    indent.incr();
  }

  /////////////////////////////////////////////////////////////////////////////////////////////////////

  @Override
  public Array createView(Index index) {
    // Section viewSection = index.getSection(); / LOOK if we could do this, we could make this work

    throw new UnsupportedOperationException();
  }

  @Override
  public Array sectionNoReduce(List<Range> ranges) {
    Section viewSection = new Section(ranges);
    ArrayStructureW result = new ArrayStructureW(this.members, viewSection.getShape());
    int count = 0;
    Section.Iterator iter = viewSection.getIterator(getShape());
    while (iter.hasNext()) {
      int recno = iter.next(null);
      StructureData sd = getStructureData(recno);
      result.setStructureData(sd, count++);
    }
    return result;
  }

  /**
   * DO NOT USE, throws UnsupportedOperationException
   */
  @Override
  public Array copy() {
    throw new UnsupportedOperationException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public double getDouble(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setDouble(Index i, double value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public float getFloat(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setFloat(Index i, float value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  public long getLong(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setLong(Index i, long value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public int getInt(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setInt(Index i, int value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public short getShort(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setShort(Index i, short value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public byte getByte(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setByte(Index i, byte value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public boolean getBoolean(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setBoolean(Index i, boolean value) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public char getChar(Index i) {
    throw new ForbiddenConversionException();
  }

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  @Override
  public void setChar(Index i, char value) {
    throw new ForbiddenConversionException();
  }

  // trusted, assumes that individual dimension lengths have been checked
  // package private : mostly for iterators
  @Override
  public double getDouble(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setDouble(int index, double value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public float getFloat(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setFloat(int index, float value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public long getLong(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setLong(int index, long value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public int getInt(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setInt(int index, int value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public short getShort(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setShort(int index, short value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public byte getByte(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setByte(int index, byte value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public char getChar(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setChar(int index, char value) {
    throw new ForbiddenConversionException();
  }

  @Override
  public boolean getBoolean(int index) {
    throw new ForbiddenConversionException();
  }

  @Override
  public void setBoolean(int index, boolean value) {
    throw new ForbiddenConversionException();
  }

}
