/*
 * 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.
 *

 * The general way to access data in an ArrayStructure is to use
 *
 StructureData getStructureData(Index index).

 *

 * For 1D arrays (or by calculating your own recnum for nD arrays), you can also use:
 *
 StructureData getStructureData(int recnum).

 *

 * Once you have a StructureData object, you can access data in a general way by using:
 *
 Array StructureData.getArray(Member m)

 *

 * 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:
 *
 Array getArray(int recno, StructureMembers.Member m)

 *

 * The following can be convenient for accessing all the data in the ArrayStructure for one member, but its efficiency
 * depends on the implementation:
 *
 Array getMemberArray(StructureMembers.Member m)

 *

 * These require that you know the data types of the member data, but they are the most efficent:
 *

 * getScalarXXX(int recnum, Member m)
 * getJavaArrayXXX(int recnum, Member m)

 * where XXX is Byte, Char, Double, Float, Int, Long, Short, or String. For members that are themselves Structures,
 * the equivilent is:
 *

 * StructureData getScalarStructure(int recnum, Member m)
 * ArrayStructure getArrayStructure(int recnum, Member m)

 *
 * These will return any compatible type as a double or float, but will have extra overhead when the types dont match:
 *

 * convertScalarXXX(int recnum, Member m)
 * convertJavaArrayXXX(int recnum, Member m)

 * 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);
  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;
  protected void copyTo1DJavaArray(IndexIterator iter, Object javaArray) {
    Object[] ja = (Object[]) javaArray;
    for (int i = 0; i < ja.length; i++)
      ja[i] = iter.getObjectNext();
  }

  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.
   */
  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.
   */
  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.
   */
  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.
   */
  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];
  }

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

  abstract protected 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();
  }

  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 = 0;
    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();

    /* special handling for sequences
    if (dataType == DataType.SEQUENCE) {
      List<StructureData> sdataList = new ArrayList<StructureData>();
      for (int recno=0; recno<getSize(); recno++) {
        ArraySequence2 seq = getArraySequence(recno, m);
        StructureDataIterator iter = seq.getStructureDataIterator();
        while (iter.hasNext())
          sdataList.add( iter.next());
      }
      ArraySequence2 seq = getArraySequence(0, m);
      int size = sdataList.size();
      StructureData[] sdataArray = sdataList.toArray( new StructureData[size]);
      return new ArrayStructureW( seq.getStructureMembers(), new int[] {size}, sdataArray);
   } */

    // 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 = new StructureMembers(m.getStructureMembers()); // no data arrays get propagated
      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 = new StructureMembers(array.getStructureMembers());
    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) throws InvalidRangeException {
    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
   */
  public Array copy() {
    throw new UnsupportedOperationException();
  }

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

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

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

  /**
   * DO NOT USE, throw ForbiddenConversionException
   */
  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
   */
  public void setLong(Index i, long value) {
    throw new ForbiddenConversionException();
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}


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

/**
 * Specialization of Index for rank 3 arrays.
 *
 * @see Index
 * @author caron
 */
public class Index3D extends Index {

  /** current element's indices */
  private int curr0, curr1, curr2;
  /** array strides */
  private int stride0, stride1, stride2;
  /** array shapes */
  private int shape0, shape1, shape2;

  Index3D() { super(3); }
  public Index3D( int[] shape) {
    super(shape);
    precalc();
  }

  protected void precalc() {
    shape0 = shape[0];
    shape1 = shape[1];
    shape2 = shape[2];

    stride0 = stride[0];
    stride1 = stride[1];
    stride2 = stride[2];

    curr0 = current[0];
    curr1 = current[1];
    curr2 = current[2];
  }

  public int [] getCurrentCounter() {
    current[0] = curr0;
    current[1] = curr1;
    current[2] = curr2;
    return current.clone();
  }

   public String toString() {
     return curr0+","+curr1+","+curr2;
   }

  public int currentElement() {
    return offset + curr0*stride0 + curr1*stride1 + curr2*stride2;
  }

  public int incr() {
    if (++curr2 >= shape2) {
      curr2 = 0;
      if (++curr1 >= shape1) {
        curr1 = 0;
        if (++curr0 >= shape0) {
          curr0 = 0;    // rollover !
        }
      }
    }
    return offset + curr0*stride0 + curr1*stride1 + curr2*stride2;
  }

  public void setDim(int dim, int value) {
    if (value < 0 || value >= shape[dim])  // check index here
      throw new ArrayIndexOutOfBoundsException();
    if (dim == 2)
      curr2 = value;
    else if (dim == 1)
      curr1 = value;
    else
      curr0 = value;
  }

  public Index set0(int v) {
    if (v < 0 || v >= shape0)  // check index here
      throw new ArrayIndexOutOfBoundsException();
    curr0 = v;
    return this;
  }

  public Index set1(int v) {
    if (v < 0 || v >= shape1)  // check index here
      throw new ArrayIndexOutOfBoundsException();
    curr1 = v;
    return this;
  }

  public Index set2(int v) {
    if (v < 0 || v >= shape2)  // check index here
      throw new ArrayIndexOutOfBoundsException();
    curr2 = v;
    return this;
  }

  public Index set(int v0, int v1, int v2) {
    set0(v0);
    set1(v1);
    set2(v2);
    return this;
  }

  public Index set(int[] index){
    if (index.length != rank)
      throw new ArrayIndexOutOfBoundsException();
    set0(index[0]);
    set1(index[1]);
    set2(index[2]);
    return this;
  }

  public Object clone() {
    return super.clone();
  }

    //experimental
  int setDirect(int v0, int v1, int v2) {
    if (v0 < 0 || v0 >= shape0)
      throw new ArrayIndexOutOfBoundsException();
    if (v1 < 0 || v1 >= shape1)
      throw new ArrayIndexOutOfBoundsException();
    if (v2 < 0 || v2 >= shape2)
      throw new ArrayIndexOutOfBoundsException();
    return offset + v0*stride0 + v1*stride1 + v2*stride2;
  }

}


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

import ucar.nc2.constants.CF;
import ucar.unidata.geoloc.*;

/**
 * Orthographic Projection spherical earth.
 * <p/>
 * See John Snyder, Map Projections used by the USGS, Bulletin 1532,
 * 2nd edition (1983), p 145
 *
 * @author Unidata Development Team
 * @see Projection
 * @see ProjectionImpl
 */

public class Orthographic extends ProjectionImpl {

  private double lat0, lon0; // center lat/lon in radians
  private final double R; // radius

  /**
   * constants from Snyder's equations
   */
  private double lon0Degrees;
  private double cosLat0, sinLat0;

  @Override
  public ProjectionImpl constructCopy() {
    ProjectionImpl result =  new Orthographic(getOriginLat(), getOriginLon(), R);
    result.setDefaultMapArea(defaultMapArea);
    result.setName(name);
    return result;
  }

  /**
   * Constructor with default parameters
   */
  public Orthographic() {
    this(0.0, 0.0);
  }

  /**
   * Construct a Orthographic Projection.
   *
   * @param lat0 lat origin of the coord. system on the projection plane
   * @param lon0 lon origin of the coord. system on the projection plane
   * @throws IllegalArgumentException
   */
  public Orthographic(double lat0, double lon0) {
    this(lat0, lon0, EARTH_RADIUS);
  }

  /**
   * Construct a Orthographic Projection, specify earth radius
   *
   * @param lat0        lat origin of the coord. system on the projection plane
   * @param lon0        lon origin of the coord. system on the projection plane
   * @param earthRadius radius of the earth
   * @throws IllegalArgumentException if lat0, par1, par2 = +/-90 deg
   */
  public Orthographic(double lat0, double lon0, double earthRadius) {
    super("Orthographic", false);

    this.lat0 = Math.toRadians(lat0);
    this.lon0 = Math.toRadians(lon0);
    R = earthRadius;

    precalculate();

    addParameter(CF.GRID_MAPPING_NAME, CF.ORTHOGRAPHIC);
    addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0);
    addParameter(CF.LONGITUDE_OF_PROJECTION_ORIGIN, lon0);
    addParameter(CF.EARTH_RADIUS, earthRadius * 1000);
  }

  // move this to ucar.unit or ucar.unidata.util

  /**
   * Precalculate some stuff
   */
  private void precalculate() {
    sinLat0 = Math.sin(lat0);
    cosLat0 = Math.cos(lat0);
    lon0Degrees = Math.toDegrees(lon0);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Orthographic that = (Orthographic) o;

    if (Double.compare(that.R, R) != 0) return false;
    if (Double.compare(that.lat0, lat0) != 0) return false;
    if (Double.compare(that.lon0, lon0) != 0) return false;
    if ((defaultMapArea == null) != (that.defaultMapArea == null)) return false; // common case is that these are null
    if (defaultMapArea != null && !that.defaultMapArea.equals(defaultMapArea)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    temp = lat0 != +0.0d ? Double.doubleToLongBits(lat0) : 0L;
    result = (int) (temp ^ (temp >>> 32));
    temp = lon0 != +0.0d ? Double.doubleToLongBits(lon0) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = R != +0.0d ? Double.doubleToLongBits(R) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

// bean properties

  /**
   * Get the origin longitude in degrees
   *
   * @return the origin longitude.
   */
  public double getOriginLon() {
    return Math.toDegrees(lon0);
  }

  /**
   * Get the origin latitude in degrees
   *
   * @return the origin latitude.
   */
  public double getOriginLat() {
    return Math.toDegrees(lat0);
  }

  //////////////////////////////////////////////
  // setters for IDV serialization - do not use except for object creating

  /**
   * Set the origin longitude.
   * @param lon   the origin longitude.
   */
  public void setOriginLon(double lon) {
      lon0 = Math.toRadians(lon);
      precalculate();
  }

  /**
   * Set the origin latitude.
   *
   * @param lat   the origin latitude.
   */
  public void setOriginLat(double lat) {
      lat0 = Math.toRadians(lat);
      precalculate();
  }

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

  /**
   * Get the label to be used in the gui for this type of projection
   *
   * @return Type label
   */
  public String getProjectionTypeLabel() {
    return "Orthographic";
  }

  /**
   * Create a String of the parameters.
   *
   * @return a String of the parameters
   */
  public String paramsToString() {
    return toString();
  }

  @Override
  public String toString() {
    return "Orthographic{" +
            "lat0=" + lat0 +
            ", lon0=" + lon0 +
            ", R=" + R +
            '}';
  }

  /**
   * This returns true when the line between pt1 and pt2 crosses the seam.
   * When the cone is flattened, the "seam" is lon0 +- 180.
   *
   * @param pt1 point 1
   * @param pt2 point 2
   * @return true when the line between pt1 and pt2 crosses the seam.
   */
  public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) {
    // either point is infinite
    if (ProjectionPointImpl.isInfinite(pt1) || ProjectionPointImpl.isInfinite(pt2))
      return true;

    // opposite signed X values, larger then 5000 km
    return (pt1.getX() * pt2.getX() < 0) && (Math.abs(pt1.getX() - pt2.getX()) > 5000.0);
  }

  /*MACROBODY
    latLonToProj {} {
      fromLat = Math.toRadians(fromLat);
      double lonDiff =
          Math.toRadians(LatLonPointImpl.lonNormal(fromLon-lon0Degrees));
      double cosc = sinLat0*Math.sin(fromLat) + cosLat0*Math.cos(fromLat)*Math.cos(lonDiff);
      if (cosc >= 0) {
          toX = R*Math.cos(fromLat)*Math.sin(lonDiff);
          toY = R*(cosLat0*Math.sin(fromLat) - sinLat0*Math.cos(fromLat)*Math.cos(lonDiff));
      } else {
          toX = Double.POSITIVE_INFINITY;
          toY = Double.POSITIVE_INFINITY;
      }
    }

    projToLatLon {} {

      fromX = fromX;
      fromY = fromY;
      double rho = Math.sqrt(fromX*fromX + fromY*fromY);
      double c = Math.asin(rho/R);

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c)*sinLat0 + (fromY*Math.sin(c)*cosLat0/rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) { // not 90 or -90
          temp = rho*cosLat0*Math.cos(c) - fromY*sinLat0*Math.sin(c);
          toLon = lon0 + Math.atan(fromX*Math.sin(c)/temp);
        } else if (lat0 == PI_OVER_4) {
          toLon = lon0 + Math.atan(fromX/-fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX/fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat= Math.toDegrees(toLat);
      toLon= Math.toDegrees(toLon);
      if (temp < 0) toLon += 180;
      toLon= LatLonPointImpl.lonNormal(toLon);
    }

  MACROBODY*/

  /*BEGINGENERATED*/

  /*
  Note this section has been generated using the convert.tcl script.
  This script, run as:
  tcl convert.tcl Orthographic.java
  takes the actual projection conversion code defined in the MACROBODY
  section above and generates the following 6 methods
  */

  /**
   * Convert a LatLonPoint to projection coordinates
   *
   * @param latLon convert from these lat, lon coordinates
   * @param result the object to write to
   * @return the given result
   */
  public ProjectionPoint latLonToProj(LatLonPoint latLon,
                                      ProjectionPointImpl result) {
    double toX, toY;
    double fromLat = latLon.getLatitude();
    double fromLon = latLon.getLongitude();

    fromLat = Math.toRadians(fromLat);
    double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon - lon0Degrees));
    double cosc = sinLat0 * Math.sin(fromLat) + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
    if (cosc >= 0) {
      toX = R * Math.cos(fromLat) * Math.sin(lonDiff);
      toY = R * (cosLat0 * Math.sin(fromLat) - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
    } else {
      toX = Double.POSITIVE_INFINITY;
      toY = Double.POSITIVE_INFINITY;
    }

    result.setLocation(toX, toY);
    return result;
  }

  /**
   * Convert projection coordinates to a LatLonPoint
   * Note: a new object is not created on each call for the return value.
   *
   * @param world  convert from these projection coordinates
   * @param result the object to write to
   * @return LatLonPoint convert to these lat/lon coordinates
   */
  public LatLonPoint projToLatLon(ProjectionPoint world, LatLonPointImpl result) {
    double toLat, toLon;
    double fromX = world.getX();
    double fromY = world.getY();

    double rho = Math.sqrt(fromX * fromX + fromY * fromY);
    double c = Math.asin(rho / R);

    toLon = lon0;
    double temp = 0;
    if (Math.abs(rho) > TOLERANCE) {
      toLat = Math.asin(Math.cos(c) * sinLat0
              + (fromY * Math.sin(c) * cosLat0 / rho));
      if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
        temp = rho * cosLat0 * Math.cos(c)
                - fromY * sinLat0 * Math.sin(c);
        toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
      } else if (Double.compare(lat0, PI_OVER_4) == 0) {
        toLon = lon0 + Math.atan(fromX / -fromY);
        temp = -fromY;
      } else {
        toLon = lon0 + Math.atan(fromX / fromY);
        temp = fromY;
      }
    } else {
      toLat = lat0;
    }
    toLat = Math.toDegrees(toLat);
    toLon = Math.toDegrees(toLon);
    if (temp < 0) {
      toLon += 180;
    }
    toLon = LatLonPointImpl.lonNormal(toLon);

    result.setLatitude(toLat);
    result.setLongitude(toLon);
    return result;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public float[][] latLonToProj(float[][] from, float[][] to, int latIndex,
                                int lonIndex) {
    int cnt = from[0].length;
    float[] fromLatA = from[latIndex];
    float[] fromLonA = from[lonIndex];
    float[] resultXA = to[INDEX_X];
    float[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double cosc = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
      if (cosc >= 0) {
        toX = R * Math.cos(fromLat) * Math.sin(lonDiff);
        toY = R * (cosLat0 * Math.sin(fromLat)
                - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
      } else {
        toX = Double.POSITIVE_INFINITY;
        toY = Double.POSITIVE_INFINITY;
      }

      resultXA[i] = (float) toX;
      resultYA[i] = (float) toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public float[][] projToLatLon(float[][] from, float[][] to) {
    int cnt = from[0].length;
    float[] fromXA = from[INDEX_X];
    float[] fromYA = from[INDEX_Y];
    float[] toLatA = to[INDEX_LAT];
    float[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double c = Math.asin(rho / R);

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = (float) toLat;
      toLonA[i] = (float) toLon;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public double[][] latLonToProj(double[][] from, double[][] to,
                                 int latIndex, int lonIndex) {
    int cnt = from[0].length;
    double[] fromLatA = from[latIndex];
    double[] fromLonA = from[lonIndex];
    double[] resultXA = to[INDEX_X];
    double[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double cosc = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
      if (cosc >= 0) {
        toX = R * Math.cos(fromLat) * Math.sin(lonDiff);
        toY = R * (cosLat0 * Math.sin(fromLat)
                - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
      } else {
        toX = Double.POSITIVE_INFINITY;
        toY = Double.POSITIVE_INFINITY;
      }

      resultXA[i] = toX;
      resultYA[i] = toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public double[][] projToLatLon(double[][] from, double[][] to) {
    int cnt = from[0].length;
    double[] fromXA = from[INDEX_X];
    double[] fromYA = from[INDEX_Y];
    double[] toLatA = to[INDEX_LAT];
    double[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double c = Math.asin(rho / R);

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = toLat;
      toLonA[i] = toLon;
    }
    return to;
  }

  /*ENDGENERATED*/

  /**
   * Test
   *
   * @param args not used
   */
  public static void main(String[] args) {
    Orthographic a = new Orthographic(40, -100);
    ProjectionPoint p = a.latLonToProj(30, -110);
    System.out.println("proj point = " + p);
    LatLonPoint ll = a.projToLatLon(p);
    System.out.println("ll = " + ll);
  }
}


/* Copyright 2012, UCAR/Unidata.
   See the LICENSE file for more information. */

package dap4.core.util;

import dap4.core.dmr.DapEnumConst;
import dap4.core.dmr.DapEnumeration;
import dap4.core.dmr.DapType;
import dap4.core.dmr.TypeSort;

import java.math.BigInteger;
import java.nio.ByteBuffer;

abstract public class CoreTypeFcns
{

    /**
     * Force an attribute value (typically string)
     * to match a given basetype
     *
     * @param type  expected type
     * @param value actual value
     * @return the value as made to conform to the expected type
     */
    static public Object
    attributeConvert(DapType type, Object value)
    {
        if(value == null) return value;
        if(type.isEnumType()) {
            if(value instanceof String) {
                // See if the constant is an int vs enum const name
                try {
                    int ival = Integer.parseInt(value.toString());
                    return ival;
                } catch (NumberFormatException nfe) {
                    // Assume it is an econst name; try to locate it
                    DapEnumConst dec = ((DapEnumeration) type).lookup(value.toString());
                    if(dec == null)
                        return value;
                    return dec.getValue();
                }
            } else if(value instanceof Long) {
              return (Long) value;
            }
        } else if(value instanceof Long) {
            return (Long) value;
        } else if(value instanceof Float) {
            return (Float) value;
        } else if(value instanceof Double) {
            return (Double) value;
        } else if(value instanceof Character) {
            return ((Character) value);
        }
        return value;
    }

    /* Get the size of an equivalent java object; zero if not defined */
    static public int getJavaSize(TypeSort atomtype)
    {
        switch (atomtype) {
        case Char:
        case Int8:
        case UInt8:
            return 1;
        case Int16:
        case UInt16:
            return 2;
        case Int32:
        case UInt32:
            return 4;
        case Int64:
        case UInt64:
            return 8;
        case Float32:
            return 4;
        case Float64:
            return 8;
        default:
            break;
        }
        return 0;
    }

    /**
     * Force a numeric value to be in a specified range
     * Only defined for simple integers (ValueClass LONG)
     * WARNING: unsigned values are forced into the
     * signed size, but the proper bit pattern is maintained.
     * The term "force" means that if the value is outside the typed
     * min/max values, it is pegged to the min or max value depending
     * on the sign. Note that truncation is not used.
     *
     * @param basetype the type to force value to in range
     * @param value    the value to force
     * @return forced value
     * @throws ConversionException if forcing is not possible
     */
    static public long forceRange(TypeSort basetype, long value)
    {
        assert basetype.isIntegerType() : "Internal error";
        switch (basetype) {
        case Char:
            value = minmax(value, 0, 255);
            break;
        case Int8:
            value = minmax(value, (long) Byte.MIN_VALUE, (long) Byte.MAX_VALUE);
            break;
        case UInt8:
            value = value & 0xFFL;
            break;
        case Int16:
            value = minmax(value, (long) Short.MIN_VALUE, (long) Short.MAX_VALUE);
            break;
        case UInt16:
            value = value & 0xFFFFL;
            break;
        case Int32:
            value = minmax(value, (long) Integer.MIN_VALUE, (long) Integer.MAX_VALUE);
            break;
        case UInt32:
            value = value & 0xFFFFFFFFL;
            break;
        case Int64:
        case UInt64:
            break; // value = value case Int64:
        default:
        }
        return value;
    }

    /**
     * Peg a value to either the min or max
     * depending on sign.
     *
     * @param value the value to peg
     * @param min   peg to this if value is < min
     * @param max   peg to this if value is > max
     * @return pegg'ed value
     */
    static protected long
    minmax(long value, long min, long max)
    {
        if(value < min) return min;
        if(value > max) return max;
        return value;
    }

    static public Object
    get(DapType type, Object v, int n)
    {
        switch (type.getAtomicType()) {
        case Char:
            return ((char[]) v)[n];
        case Int8:
        case UInt8:
            return ((byte[]) v)[n];
        case Int16:
        case UInt16:
            return ((short[]) v)[n];
        case Int32:
        case UInt32:
            return ((int[]) v)[n];
        case Int64:
        case UInt64:
            return ((long[]) v)[n];
        case Float32:
            return ((float[]) v)[n];
        case Float64:
            return ((double[]) v)[n];
        case String:
        case URL:
            return ((String[]) v)[n];
        case Opaque:
            return ((ByteBuffer[]) v)[n];
        case Enum:
            return get(((DapEnumeration) type).getBaseType(), v, n);
        case Structure:
        case Sequence:
        default:
            break;
        }
        throw new IllegalArgumentException();
    }

    static public void
    put(TypeSort sort, Object v, int n, Object value)
    {
        switch (sort) {
        case Char:
            ((char[]) v)[n] = ((char[]) value)[0];
            break;
        case Int8:
        case UInt8:
            ((byte[]) v)[n] = ((byte[]) value)[0];
            break;
        case Int16:
        case UInt16:
            ((short[]) v)[n] = ((short[]) value)[0];
            break;
        case Int32:
        case UInt32:
            ((int[]) v)[n] = ((int[]) value)[0];
            break;
        case Int64:
        case UInt64:
            ((long[]) v)[n] = ((long[]) value)[0];
            break;
        case Float32:
            ((float[]) v)[n] = ((float[]) value)[0];
            break;
        case Float64:
            ((double[]) v)[n] = ((double[]) value)[0];
            break;
        case String:
        case URL:
            ((String[]) v)[n] = ((String[]) value)[0];
            break;
        case Opaque:
            ((ByteBuffer[]) v)[n] = ((ByteBuffer[]) value)[0];
            break;
        case Enum:
        case Structure:
        case Sequence:
        default:
            throw new IllegalArgumentException();
        }
    }

    static public int
    putVector(TypeSort sort, Object v, int offset, Object vec)
    {
        int len = 0;
        switch (sort) {
        case Char:
            len = ((char[]) vec).length;
            System.arraycopy(((char[]) v), offset, ((char[]) vec), 0, len);
            break;
        case Int8:
        case UInt8:
            len = ((int[]) vec).length;
            System.arraycopy(((byte[]) v), offset, ((byte[]) vec), 0, len);
            break;
        case Int16:
        case UInt16:
            len = ((short[]) vec).length;
            System.arraycopy(((short[]) v), offset, ((short[]) vec), 0, len);
            break;
        case Int32:
        case UInt32:
            len = ((int[]) vec).length;
            System.arraycopy(((int[]) v), offset, ((int[]) vec), 0, len);
            break;
        case Int64:
        case UInt64:
            len = ((long[]) vec).length;
            System.arraycopy(((long[]) v), offset, ((long[]) vec), 0, len);
            break;
        case Float32:
            len = ((float[]) vec).length;
            System.arraycopy(((float[]) v), offset, ((float[]) vec), 0, len);
            break;
        case Float64:
            len = ((double[]) vec).length;
            System.arraycopy(((double[]) v), offset, ((double[]) vec), 0, len);
            break;
        case String:
        case URL:
            len = ((String[]) vec).length;
            System.arraycopy(((String[]) v), offset, ((String[]) vec), 0, len);
            break;
        case Opaque:
            len = ((ByteBuffer[]) vec).length;
            System.arraycopy(((ByteBuffer[]) v), offset, ((ByteBuffer[]) vec), 0, len);
            break;
        case Enum:
        case Structure:
        case Sequence:
        default:
            throw new IllegalArgumentException();
        }
        return len;
    }

    static public Object get(TypeSort sort, ByteBuffer b, int n)
    {
        switch (sort) {
        case Char:
            return (char) (b.get(n) & 0xFFL);
        case Int8:
        case UInt8:
            return (b.get(n));
        case Int16:
        case UInt16:
            return (b.getShort(n));
        case Int32:
        case UInt32:
            return (b.getInt(n));
        case Int64:
        case UInt64:
            return (b.getLong(n));
        case Float32:
            return (b.getFloat(n));
        case Float64:
            return (b.getDouble(n));
        case String:
        case URL:
            byte[] bytes = new byte[(int) b.getLong()];
            b.get(bytes);
            return new String(bytes, DapUtil.UTF8);
        case Opaque:
            bytes = new byte[(int) b.getLong()];
            b.get(bytes);
            return ByteBuffer.wrap(bytes);
        case Enum:
        case Structure:
        case Sequence:
        default:
            break;
        }
        throw new IllegalArgumentException();
    }

    static public Object
    createVector(TypeSort type, long count)
    {
        int icount = (int) count;
        Object vector = null;
        switch (type) {
        case Char:
            vector = new char[icount];
            break;
        case Int8:
        case UInt8:
            vector = new byte[icount];
            break;
        case Int16:
        case UInt16:
            vector = new short[icount];
            break;
        case Int32:
        case UInt32:
            vector = new int[icount];
            break;
        case Int64:
        case UInt64:
            vector = new long[icount];
            break;
        case Float32:
            vector = new float[icount];
            break;
        case Float64:
            vector = new double[icount];
            break;
        case String:
            vector = new String[icount];
            break;
        case Opaque:
            vector = new ByteBuffer[icount];
            break;
        default:
            throw new ConversionException();
        }
        return vector;
    }

}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package thredds.util;

import com.google.common.base.Preconditions;
import ucar.nc2.NetcdfFileWriter;

import javax.servlet.http.HttpServletRequest;

/**
 * Utilities for extracting path information from request.
 * Handles servlet and spring controller cases
 */
public class TdsPathUtils {

  // For "removePrefix/path" style servlet mappings.
  public static String extractPath(HttpServletRequest req, String removePrefix) {

    // may be in pathInfo (Servlet) or servletPath (Controller)
    String dataPath = req.getPathInfo();
    if (dataPath == null) {
      dataPath = req.getServletPath();
    }
    if (dataPath == null)  // not sure if this is possible
      return "";

    // removePrefix or "/"+removePrefix
    if (removePrefix != null) {
      if (dataPath.startsWith(removePrefix)) {
        dataPath = dataPath.substring(removePrefix.length());

      } else if (dataPath.startsWith("/")) {
        dataPath = dataPath.substring(1);
        if (dataPath.startsWith(removePrefix))
          dataPath = dataPath.substring(removePrefix.length());
      }

    }

    if (dataPath.startsWith("/"))
      dataPath = dataPath.substring(1);

    if (dataPath.contains(".."))  // LOOK what about escapes ??
      throw new IllegalArgumentException("path cannot contain '..'");

    return dataPath;
  }

  public static String extractPath(HttpServletRequest req, String removePrefix, String[] endings) {
    String path = extractPath(req, removePrefix);
    if (endings == null) return path;

    for (String ending : endings) {
      if (path.endsWith(ending)) {
        int len = path.length() - ending.length();
        path = path.substring(0, len);
        break;
      }
    }

    return path;
  }

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

  public static String getFileNameForResponse(String pathInfo, NetcdfFileWriter.Version version) {
    Preconditions.checkNotNull(version, "version == null");
    return getFileNameForResponse(pathInfo, version.getSuffix());
  }

  public static String getFileNameForResponse(String pathInfo, String extension) {
    Preconditions.checkArgument(pathInfo != null && !pathInfo.isEmpty(), "pathInfo == %s", pathInfo);
    Preconditions.checkNotNull(extension, "extension == null");

    String parentDirName = getBaseName(doGetPath(pathInfo, 0));
    String baseName = getBaseName(pathInfo);

    String dotExtension = extension.startsWith(".") ? extension : "." + extension;

    if (!parentDirName.isEmpty()) {
      return parentDirName + "_" + baseName + dotExtension;
    } else {
      return baseName + dotExtension;
    }
  }

  // taken from apache.common.io.FilenameUtils so we dont need to suck in entire common.io library
  private static final char EXTENSION_SEPARATOR = '.';
  private static final char UNIX_SEPARATOR = '/';
  private static final char WINDOWS_SEPARATOR = '\\';

  private static String getBaseName(String filename) {
    return removeExtension( getName(filename));
  }

  private static String removeExtension(String filename) {
    if (filename == null) {
      return null;
    }
    int index = indexOfExtension(filename);
    if (index == -1) {
      return filename;
    } else {
      return filename.substring(0, index);
    }
  }

  private static int indexOfExtension(String filename) {
    if (filename == null) {
      return -1;
    }
    int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
    int lastSeparator = indexOfLastSeparator(filename);
    return lastSeparator > extensionPos ? -1 : extensionPos;
  }

  private static String getName(String filename) {
    if (filename == null) {
      return null;
    }
    int index = indexOfLastSeparator(filename);
    return filename.substring(index + 1);
  }

  private static int indexOfLastSeparator(String filename) {
    if (filename == null) {
      return -1;
    }
    int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
    int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
    return Math.max(lastUnixPos, lastWindowsPos);
  }

  private static String doGetPath(String filename, int separatorAdd) {
    if (filename == null) {
      return null;
    }
    int prefix = getPrefixLength(filename);
    if (prefix < 0) {
      return null;
    }
    int index = indexOfLastSeparator(filename);
    int endIndex = index+separatorAdd;
    if (prefix >= filename.length() || index < 0 || prefix >= endIndex) {
      return "";
    }
    return filename.substring(prefix, endIndex);
  }

  private static int getPrefixLength(String filename) {
    if (filename == null) {
      return -1;
    }
    int len = filename.length();
    if (len == 0) {
      return 0;
    }
    char ch0 = filename.charAt(0);
    if (ch0 == ':') {
      return -1;
    }
    if (len == 1) {
      if (ch0 == '~') {
        return 2;  // return a length greater than the input
      }
      return isSeparator(ch0) ? 1 : 0;
    } else {
      if (ch0 == '~') {
        int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
        int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
        if (posUnix == -1 && posWin == -1) {
          return len + 1;  // return a length greater than the input
        }
        posUnix = posUnix == -1 ? posWin : posUnix;
        posWin = posWin == -1 ? posUnix : posWin;
        return Math.min(posUnix, posWin) + 1;
      }
      char ch1 = filename.charAt(1);
      if (ch1 == ':') {
        ch0 = Character.toUpperCase(ch0);
        if (ch0 >= 'A' && ch0 <= 'Z') {
          if (len == 2 || !isSeparator(filename.charAt(2))) {
            return 2;
          }
          return 3;
        }
        return -1;

      } else if (isSeparator(ch0) && isSeparator(ch1)) {
        int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
        int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
        if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) {
          return -1;
        }
        posUnix = posUnix == -1 ? posWin : posUnix;
        posWin = posWin == -1 ? posUnix : posWin;
        return Math.min(posUnix, posWin) + 1;
      } else {
        return isSeparator(ch0) ? 1 : 0;
      }
    }
  }

  private static boolean isSeparator(char ch) {
    return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR;
  }

}


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

package ucar.nc2.iosp.vis5d;

import ucar.nc2.iosp.grid.*;
import ucar.nc2.iosp.mcidas.McGridDefRecord;

/**
 * Class to hold the Vis5D grid navigation information.
 *
 * @author Unidata Development Team
 */
public class Vis5DGridDefRecord extends GridDefRecord {

  private static final int PROJ_GENERIC = 0;
  private static final int PROJ_LINEAR = 1;
  private static final int PROJ_CYLINDRICAL = 20;
  private static final int PROJ_SPHERICAL = 21;
  private static final int PROJ_LAMBERT = 2;
  private static final int PROJ_STEREO = 3;
  private static final int PROJ_ROTATED = 4;
  private int projection;

  //private Vis5DCoordinateSystem coord_sys;

  /**
   * Create a new grid nav block with the values
   *
   * @param Projection _more_
   * @param projargs   _more_
   * @param nr         _more_
   * @param nc         _more_
   */
  public Vis5DGridDefRecord(int Projection, double[] projargs, int nr, int nc) {
    projection = Projection;
    //coord_sys = new Vis5DCoordinateSystem(Projection, projargs, nr, nc);
    setParams(Projection, projargs);
    addParam(GDS_KEY, this.toString());
  }

  /**
   * Get a short name for this GDSKey for the netCDF group.
   * Subclasses should implement as a short description
   *
   * @return short name
   */
  public String getGroupName() {
    return getParam(PROJ) + "_" + getParam(NX) + "x" + getParam(NY);
  }

  public String toString() {
    // TODO: make this more unique
    return getParam(PROJ) + " X:" + getParam(NX) + " " + "Y:" + getParam(NY) + " Proj:" + projection;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Vis5DGridDefRecord)) {
      return false;
    }
    return this.toString().equals(o.toString());
  }

  public int hashCode() {
    return toString().hashCode();
  }

  private void setParams(int Projection, double[] projargs) {
    double NorthBound;
    double SouthBound;
    double WestBound;
    double EastBound;
    double RowInc;
    double ColInc;
    double Lat1;
    double Lat2;
    double PoleRow;
    double PoleCol;
    double CentralLat;
    double CentralLon;
    double CentralRow;
    double CentralCol;
    double Rotation;  /* radians */
    double Cone;
    double Hemisphere;
    double ConeFactor;
    double CosCentralLat;
    double SinCentralLat;
    double StereoScale;
    double InvScale;
    double CylinderScale;
    switch (Projection) {

      case PROJ_GENERIC:
        addParam(PROJ, "GENERIC");
      case PROJ_LINEAR:
        addParam(PROJ, "LINEAR");
      case PROJ_CYLINDRICAL:
        addParam(PROJ, "CYLINDRICAL");
      case PROJ_SPHERICAL:
        addParam(PROJ, "SPHERICAL");
        NorthBound = projargs[0];
        WestBound = -projargs[1];
        RowInc = projargs[2];
        ColInc = projargs[3];
        addParam(LA1, String.valueOf(NorthBound));
        addParam(LO1, String.valueOf(WestBound));
        addParam(DX, String.valueOf(ColInc));
        addParam(DY, String.valueOf(RowInc));
        break;

      case PROJ_ROTATED:
        addParam(PROJ, "ROTATED");
        NorthBound = projargs[0];
        WestBound = projargs[1];
        RowInc = projargs[2];
        ColInc = projargs[3];
        CentralLat = projargs[4];
        CentralLon = projargs[5];
        Rotation = projargs[6];
        break;

      case PROJ_LAMBERT:
        addParam(PROJ, "LAMBERT");
        Lat1 = projargs[0];
        Lat2 = projargs[1];
        PoleRow = projargs[2];
        PoleCol = projargs[3];
        CentralLon = projargs[4];
        ColInc = projargs[5];
        break;

      case PROJ_STEREO:
        addParam(PROJ, "STEREO");
        CentralLat = projargs[0];
        CentralLon = projargs[1];
        CentralRow = projargs[2];
        CentralCol = projargs[3];
        ColInc = projargs[4];
        break;

    }
  }

  /**
   * _more_
   *
   * @param Projection _more_
   * @param projargs   _more_
   */
  public static void printProjArgs(int Projection, double[] projargs) {
    double NorthBound;
    double SouthBound;
    double WestBound;
    double EastBound;
    double RowInc;
    double ColInc;
    double Lat1;
    double Lat2;
    double PoleRow;
    double PoleCol;
    double CentralLat;
    double CentralLon;
    double CentralRow;
    double CentralCol;
    double Rotation;  /* radians */
    double Cone;
    double Hemisphere;
    double ConeFactor;
    double CosCentralLat;
    double SinCentralLat;
    double StereoScale;
    double InvScale;
    double CylinderScale;
    switch (Projection) {

      case PROJ_GENERIC:
      case PROJ_LINEAR:
      case PROJ_CYLINDRICAL:
      case PROJ_SPHERICAL:
        NorthBound = projargs[0];
        WestBound = projargs[1];
        RowInc = projargs[2];
        ColInc = projargs[3];
        System.out.println("Generic, Linear, Cylindrical, Spherical:");
        System.out.println("NB: " + NorthBound + ", WB: " + WestBound
                + ", rowInc: " + RowInc + ", colInc: "
                + ColInc);
        break;

      case PROJ_ROTATED:
        NorthBound = projargs[0];
        WestBound = projargs[1];
        RowInc = projargs[2];
        ColInc = projargs[3];
        CentralLat = projargs[4];
        CentralLon = projargs[5];
        Rotation = projargs[6];
        System.out.println("Rotated:");
        System.out.println("NB: " + NorthBound + ", WB: " + WestBound
                + ", rowInc: " + RowInc + ", colInc: "
                + ColInc + ", clat: " + CentralLat
                + ", clon: " + CentralLon + ", rotation: "
                + Rotation);
        break;

      case PROJ_LAMBERT:
        Lat1 = projargs[0];
        Lat2 = projargs[1];
        PoleRow = projargs[2];
        PoleCol = projargs[3];
        CentralLon = projargs[4];
        ColInc = projargs[5];
        System.out.println("Lambert: ");
        System.out.println("lat1: " + Lat1 + ", lat2: " + Lat2
                + ", poleRow: " + PoleRow + ", PoleCol: "
                + PoleCol + ", clon: " + CentralLon
                + ", colInc: " + ColInc);
        break;

      case PROJ_STEREO:
        CentralLat = projargs[0];
        CentralLon = projargs[1];
        CentralRow = projargs[2];
        CentralCol = projargs[3];
        ColInc = projargs[4];
        System.out.println("Stereo: ");
        System.out.println("clat: " + CentralLat + ", clon: "
                + CentralLon + ", cRow: " + CentralRow
                + ", cCol: " + CentralCol + ", colInc: "
                + ColInc);
        break;

      default:
        System.out.println("Projection unknown");
    }
  }
}