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

import java.util.List;

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

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

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

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

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

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

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

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

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

  protected boolean hasvlen = false;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  boolean isFastIterator() {
    return fastIterator;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return sbuff.toString();
  }

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

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

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

    return i;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}


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

import ucar.nc2.grib.GribUtils;
import ucar.nc2.grib.grib1.Grib1ParamTime;
import ucar.nc2.grib.grib1.Grib1Record;
import ucar.nc2.grib.grib1.Grib1SectionProductDefinition;
import ucar.nc2.grib.grib1.tables.Grib1Customizer;
import ucar.nc2.grib.grib2.Grib2Pds;
import ucar.nc2.grib.grib2.Grib2Record;
import ucar.nc2.grib.grib2.table.Grib2Tables;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.time.CalendarPeriod;
import ucar.nc2.util.Counters;
import ucar.nc2.util.Indent;
import ucar.nc2.util.Misc;

import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;

/**
 * Time coordinates that are intervals.
 *
 * @author John
 * @since 11/28/13
 */
@Immutable
public class CoordinateTimeIntv extends CoordinateTimeAbstract implements Coordinate {
  private final List<TimeCoordIntvValue> timeIntervals;

  public CoordinateTimeIntv(int code, CalendarPeriod timeUnit, CalendarDate refDate, List<TimeCoordIntvValue> timeIntervals, int[] time2runtime) {
    super(code, timeUnit, refDate, time2runtime);
    this.timeIntervals = Collections.unmodifiableList(timeIntervals);
  }

  CoordinateTimeIntv(CoordinateTimeIntv org, CalendarDate refDate) {
    super(org.code, org.timeUnit, refDate, null);
    this.timeIntervals = org.getTimeIntervals();
  }

  public List<TimeCoordIntvValue> getTimeIntervals() {
    return timeIntervals;
  }

  @Override
  public List<?> getValues() {
    return timeIntervals;
  }

  @Override
  public Object getValue(int idx) {
    return timeIntervals.get(idx);
  }

  @Override
  public int getIndex(Object val) {
    return Collections.binarySearch(timeIntervals, (TimeCoordIntvValue) val);
  }

  @Override
  public int getSize() {
    return timeIntervals.size();
  }

  @Override
  public int estMemorySize() {
    return 616 + getSize() * (24);  // LOOK wrong
  }

  @Override
  public Type getType() {
    return Type.timeIntv;
  }

  /**
   * Check if we all time intervals have the same length.
   * @return time interval name or MIXED_INTERVALS
   */
  public String getTimeIntervalName() {
    // are they the same length ?
    int firstValue = -1;
    for (TimeCoordIntvValue tinv : timeIntervals) {
      int value = (tinv.getBounds2() - tinv.getBounds1());
      if (firstValue < 0) firstValue = value;
      else if (value != firstValue) return MIXED_INTERVALS;
    }

    firstValue = (firstValue * timeUnit.getValue());
    return firstValue + "_" + timeUnit.getField().toString();
  }

  /**
   * Make calendar date range, using the first and last ending bounds
   * @param cal  optional calendar, may be null
   * @return  calendar date range
   */
  @Override
  public CalendarDateRange makeCalendarDateRange(ucar.nc2.time.Calendar cal) {
    CalendarDateUnit cdu = CalendarDateUnit.of(cal, timeUnit.getField(), refDate);
    CalendarDate start = cdu.makeCalendarDate( timeUnit.getValue() * timeIntervals.get(0).getBounds2());
    CalendarDate end = cdu.makeCalendarDate(timeUnit.getValue() * timeIntervals.get(getSize()-1).getBounds2());
    return CalendarDateRange.of(start, end);
  }

  @Override
  public void showInfo(Formatter info, Indent indent) {
    info.format("%s%s:", indent, getType());
    for (TimeCoordIntvValue cd : timeIntervals)
      info.format(" %s,", cd);
    info.format(" (%d) %n", timeIntervals.size());
    if (time2runtime != null)
      info.format("%stime2runtime: %s", indent, Misc.showInts(time2runtime));
  }

  @Override
  public void showCoords(Formatter info) {
    info.format("Time Interval offsets: (%s) ref=%s%n", getTimeUnit(), getRefDate());
    for (TimeCoordIntvValue cd : timeIntervals)
      info.format("   (%3d - %3d)  %d%n", cd.getBounds1(), cd.getBounds2(), cd.getBounds2() - cd.getBounds1());
  }

  @Override
  public Counters calcDistributions() {
    ucar.nc2.util.Counters counters = new Counters();
    counters.add("resol");
    counters.add("intv");

    List<TimeCoordIntvValue> offsets = getTimeIntervals();
    for (int i = 0; i < offsets.size(); i++) {
      int intv = offsets.get(i).getBounds2() - offsets.get(i).getBounds1();
      counters.count("intv", intv);
      if (i < offsets.size() - 1) {
        int resol = offsets.get(i + 1).getBounds1() - offsets.get(i).getBounds1();
        counters.count("resol", resol);
      }
    }

    return counters;
  }

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

    CoordinateTimeIntv that = (CoordinateTimeIntv) o;

    if (code != that.code) return false;
    if (!timeIntervals.equals(that.timeIntervals)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = code;
    result = 31 * result + timeIntervals.hashCode();
    return result;
  }

  protected CoordinateTimeIntv makeBestFromComplete(int[] best, int n) {
    List<TimeCoordIntvValue> timeIntervalsBest = new ArrayList<>(timeIntervals.size());
    int[] time2runtimeBest = new int[n];
    int count = 0;
    for (int i=0; i<best.length; i++) {
      int time = best[i];
      if (time >= 0) {
        time2runtimeBest[count] = time;
        timeIntervalsBest.add(timeIntervals.get(i));
        count++;
      }
    }

    return new CoordinateTimeIntv(code, timeUnit, refDate, timeIntervalsBest, time2runtimeBest);
  }

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

  public static class Builder2 extends CoordinateBuilderImpl<Grib2Record> {
    private final Grib2Tables cust;
    private final int code;                  // pdsFirst.getTimeUnit()
    private final CalendarPeriod timeUnit;
    private final CalendarDate refDate;

    public Builder2(Grib2Tables cust, int code, CalendarPeriod timeUnit, CalendarDate refDate) {
      this.cust = cust;
      this.code = code;
      this.timeUnit = timeUnit;
      this.refDate = refDate;
    }

    public Builder2(CoordinateTimeIntv from) {
      this.cust = null;
      this.code = from.getCode();
      this.timeUnit = from.getTimeUnit();
      this.refDate = from.getRefDate();
    }

    @Override
    public Object extract(Grib2Record gr) {
      TimeCoordIntvValue tinv;

      Grib2Pds pds = gr.getPDS();
      int tuInRecord = pds.getTimeUnit();
      if (tuInRecord == code) {
        int[] intv = cust.getForecastTimeIntervalOffset(gr);
        if (intv == null) throw new IllegalStateException("CoordinateTimeIntv must have TimeIntervalOffset");
        tinv = new TimeCoordIntvValue(intv[0], intv[1]);

      } else {
        TimeCoordIntvDateValue tinvd = cust.getForecastTimeInterval(gr); // converts to calendar date
        if (tinvd == null) throw new IllegalStateException("CoordinateTimeIntv has no ForecastTime");
        tinv = tinvd.convertReferenceDate(refDate, timeUnit);
        if (tinv == null) throw new IllegalStateException("CoordinateTimeIntv has no ReferenceTime");
      }

      return tinv;
    }

    @Override
    public Coordinate makeCoordinate(List<Object> values) {
      List<TimeCoordIntvValue> offsetSorted = new ArrayList<>(values.size());
      for (Object val : values) offsetSorted.add( (TimeCoordIntvValue) val);
      Collections.sort(offsetSorted);

      return new CoordinateTimeIntv(code, timeUnit, refDate, offsetSorted, null);
    }
  }

  public static class Builder1 extends CoordinateBuilderImpl<Grib1Record> {
    private final Grib1Customizer cust;
    private final int code;                  // pdsFirst.getTimeUnit()
    private final CalendarPeriod timeUnit;
    private final CalendarDate refDate;

    Builder1(Grib1Customizer cust, int code, CalendarPeriod timeUnit, CalendarDate refDate) {
      this.cust = cust;
      this.code = code;
      this.timeUnit = timeUnit;
      this.refDate = refDate;
    }

    @Override
    public Object extract(Grib1Record gr) {
      Grib1SectionProductDefinition pds = gr.getPDSsection();
      Grib1ParamTime ptime = gr.getParamTime(cust);
      int[] intv = ptime.getInterval();
      TimeCoordIntvValue  tinv = new TimeCoordIntvValue(intv[0], intv[1]);

      // If its a different time unit, we have to adjust the interval.
      int tuInRecord = pds.getTimeUnit();
      if (tuInRecord != code) {
        CalendarPeriod unitInRecord = GribUtils.getCalendarPeriod(tuInRecord);
        tinv = tinv.convertReferenceDate(gr.getReferenceDate(), unitInRecord, refDate, timeUnit);
      }
      return tinv;
    }

    @Override
    public Coordinate makeCoordinate(List<Object> values) {
      List<TimeCoordIntvValue> offsetSorted = new ArrayList<>(values.size());
      for (Object val : values) offsetSorted.add( (TimeCoordIntvValue) val);
      Collections.sort(offsetSorted);

      return new CoordinateTimeIntv(code, timeUnit, refDate, offsetSorted, null);
    }
  }

}


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

package ucar.nc2.iosp.gempak;

import java.io.*;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;

/**
 * Class to hold common methods for reading surface and sounding files
 *
 * @author IDV Development Team
 */
public abstract class AbstractGempakStationFileReader extends GempakFileReader {

  /**
   * date key identifier
   */
  public static final String DATE = "DATE";

  /**
   * time key identifier
   */
  public static final String TIME = "TIME";

  /**
   * obs date format
   */
  private static final String DATE_FORMAT = "yyMMdd'/'HHmm";

  /**
   * date/time keys
   */
  private List<Key> dateTimeKeys;

  /**
   * station keys
   */
  private List<Key> stationKeys;

  /**
   * unique list of dates as Strings
   */
  private List<String> dateList;

  /**
   * unique list of dates as Dates
   */
  private List<Date> dates;

  /**
   * list of stations
   */
  private List<GempakStation> stations;

  /**
   * list of parameters
   */
  // private List<GempakParameter> params;

  /**
   * list of parameters
   */
  private Map<String, List<GempakParameter>> partParamMap = new HashMap<>();

  /**
   * data formatter
   */
  private SimpleDateFormat dateFmt = new SimpleDateFormat(DATE_FORMAT);

  /**
   * The file subtype
   */
  protected String subType = "";

  /**
   * Default ctor
   */
  AbstractGempakStationFileReader() {
  }

  /**
   * Initialize this reader.  Read all the metadata
   *
   * @return true if successful
   * @throws IOException problem reading the data
   */
  protected boolean init() throws IOException {
    return init(true);
  }

  /**
   * Initialize this reader.  Get the Grid specific info
   *
   * @param fullCheck check to make sure there are grids we can handle
   * @return true if successful
   * @throws IOException problem reading the data
   */
  protected boolean init(boolean fullCheck) throws IOException {
    //Trace.startTrace();

    if (!super.init(fullCheck)) {
      return false;
    }

    if ((dmLabel.kftype != MFSN) && (dmLabel.kftype != MFSF)) {
      logError("not a point data file ");
      return false;
    }
    return true;

  }

  /**
   * Read in the stations and times.  Subclasses should
   * call this during init()
   *
   * @param uniqueTimes make a set of unique times
   * @return true if stations and times were read okay.
   */
  protected boolean readStationsAndTimes(boolean uniqueTimes) {

    for (DMPart apart : parts) {
      List<GempakParameter> params = makeParams(apart);
      partParamMap.put(apart.kprtnm, params);
    }

    // get the date/time keys
    dateTimeKeys = getDateTimeKeys();
    if ((dateTimeKeys == null) || dateTimeKeys.isEmpty()) {
      return false;
    }

    // get the station info
    stationKeys = findStationKeys();
    if ((stationKeys == null) || stationKeys.isEmpty()) {
      return false;
    }
    stations = getStationList();
    makeFileSubType();

    // null out the old cached list
    dates = null;
    dateList = makeDateList(uniqueTimes);

    return true;

  }

  /**
   * Get the date/time information
   *
   * @return the list of date/time keys
   */
  private List<Key> getDateTimeKeys() {
    Key date = findKey(DATE);
    Key time = findKey(TIME);
    if ((date == null) || (time == null)
            || !date.type.equals(time.type)) {
      return null;
    }
    List<Key> dt = new ArrayList<>(2);
    dt.add(date);
    dt.add(time);
    return dt;
  }

  /**
   * Get the list of dates
   *
   * @param unique true for unique list
   * @return the list of dates
   */
  protected List<String> makeDateList(boolean unique) {
    Key date = dateTimeKeys.get(0);
    Key time = dateTimeKeys.get(1);
    List<int[]> toCheck;
    if (date.type.equals(ROW)) {
      toCheck = headers.rowHeaders;
    } else {
      toCheck = headers.colHeaders;
    }
    List<String> fileDates = new ArrayList<>();
    for (int[] header : toCheck) {
      if (header[0] != IMISSD) {
        // convert to GEMPAK date/time
        int idate = header[date.loc + 1];
        int itime = header[time.loc + 1];
        // TODO: Add in the century
        String dateTime = GempakUtil.TI_CDTM(idate, itime);
        fileDates.add(dateTime);
      }
    }
    if (unique && !fileDates.isEmpty()) {
      SortedSet<String> uniqueTimes =
              Collections.synchronizedSortedSet(new TreeSet<String>());
      uniqueTimes.addAll(fileDates);
      fileDates.clear();
      fileDates.addAll(uniqueTimes);
    }

    return fileDates;
  }

  /**
   * Make GempakParameters from the list of
   *
   * @param part the part to use
   * @return parameters from the info in that part.
   */
  private List<GempakParameter> makeParams(DMPart part) {
    List<GempakParameter> gemparms = new ArrayList<>(part.kparms);
    for (DMParam param : part.params) {
      String name = param.kprmnm;
      GempakParameter parm = GempakParameters.getParameter(name);
      if (parm == null) {
        //System.out.println("couldn't find " + name
        //                   + " in params table");
        parm = new GempakParameter(1, name, name, "", 0);
      }
      gemparms.add(parm);
    }
    return gemparms;
  }

  /**
   * Get the list of parameters for the part
   *
   * @param partName name of the part
   * @return list of parameters
   */
  public List<GempakParameter> getParameters(String partName) {
    return partParamMap.get(partName);
  }

  /**
   * Get the station list
   *
   * @return the list of stations
   */
  private List<GempakStation> getStationList() {
    Key slat = findKey(GempakStation.SLAT);
    if (slat == null) {
      return null;
    }
    List<int[]> toCheck;
    if (slat.type.equals(ROW)) {
      toCheck = headers.rowHeaders;
    } else {
      toCheck = headers.colHeaders;
    }
    List<GempakStation> fileStations = new ArrayList<>();
    int i = 0;
    for (int[] header : toCheck) {
      if (header[0] != IMISSD) {
        GempakStation station = makeStation(header);
        if (station != null) {
          station.setIndex(i + 1);
          fileStations.add(station);
        }
      }
      i++;
    }
    return fileStations;
  }

  /**
   * Make a station from the header info
   *
   * @param header the station header
   * @return the corresponding station
   */
  private GempakStation makeStation(int[] header) {
    if ((stationKeys == null) || stationKeys.isEmpty()) {
      return null;
    }
    GempakStation newStation = new GempakStation();
    for (Key key : stationKeys) {
      int loc = key.loc + 1;
      switch (key.name) {
        case GempakStation.STID:
          newStation.setSTID(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
        case GempakStation.STNM:
          newStation.setSTNM(header[loc]);
          break;
        case GempakStation.SLAT:
          newStation.setSLAT(header[loc]);
          break;
        case GempakStation.SLON:
          newStation.setSLON(header[loc]);
          break;
        case GempakStation.SELV:
          newStation.setSELV(header[loc]);
          break;
        case GempakStation.SPRI:
          newStation.setSPRI(header[loc]);
          break;
        case GempakStation.STAT:
          newStation.setSTAT(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
        case GempakStation.COUN:
          newStation.setCOUN(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
        case GempakStation.SWFO:
          newStation.setSWFO(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
        case GempakStation.WFO2:
          newStation.setWFO2(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
        case GempakStation.STD2:
          newStation.setSTD2(GempakUtil.ST_ITOC(header[loc]).trim());
          break;
      }
    }
    return newStation;
  }

  /**
   * Get the station key names
   *
   * @return the list of station key names
   */
  public List<String> getStationKeyNames() {
    List<String> keys = new ArrayList<>();
    if ((stationKeys != null) && !stationKeys.isEmpty()) {
      for (Key key : stationKeys) {
        keys.add(key.name);
      }
    }
    return keys;
  }

  /**
   * Get the station keys
   *
   * @return the list of station keys
   */
  private List<Key> findStationKeys() {
    Key stid = findKey(GempakStation.STID);
    Key stnm = findKey(GempakStation.STNM);
    Key slat = findKey(GempakStation.SLAT);
    Key slon = findKey(GempakStation.SLON);
    Key selv = findKey(GempakStation.SELV);
    Key stat = findKey(GempakStation.STAT);
    Key coun = findKey(GempakStation.COUN);
    Key std2 = findKey(GempakStation.STD2);
    Key spri = findKey(GempakStation.SPRI);
    Key swfo = findKey(GempakStation.SWFO);
    Key wfo2 = findKey(GempakStation.WFO2);
    if ((slat == null) || (slon == null)
            || !slat.type.equals(slon.type)) {
      return null;
    }
    String tslat = slat.type;
    // check to make sure they are all in the same set of keys
    List<Key> stKeys = new ArrayList<>();
    stKeys.add(slat);
    stKeys.add(slon);
    if ((stid != null) && !stid.type.equals(tslat)) {
      return null;
    } else if ((stnm != null) && !stnm.type.equals(tslat)) {
      return null;
    } else if ((selv != null) && !selv.type.equals(tslat)) {
      return null;
    } else if ((stat != null) && !stat.type.equals(tslat)) {
      return null;
    } else if ((coun != null) && !coun.type.equals(tslat)) {
      return null;
    } else if ((std2 != null) && !std2.type.equals(tslat)) {
      return null;
    } else if ((spri != null) && !spri.type.equals(tslat)) {
      return null;
    } else if ((swfo != null) && !swfo.type.equals(tslat)) {
      return null;
    } else if ((wfo2 != null) && !wfo2.type.equals(tslat)) {
      return null;
    }
    if (stid != null) {
      stKeys.add(stid);
    }
    if (stnm != null) {
      stKeys.add(stnm);
    }
    if (selv != null) {
      stKeys.add(selv);
    }
    if (stat != null) {
      stKeys.add(stat);
    }
    if (coun != null) {
      stKeys.add(coun);
    }
    if (std2 != null) {
      stKeys.add(std2);
    }
    if (spri != null) {
      stKeys.add(spri);
    }
    if (swfo != null) {
      stKeys.add(swfo);
    }
    if (wfo2 != null) {
      stKeys.add(wfo2);
    }
    return stKeys;
  }

  /**
   * Get the list of stations in this file.
   *
   * @return list of stations.
   */
  public List<GempakStation> getStations() {
    return stations;
  }

  /**
   * Get the list of dates in this file.
   *
   * @return list of dates.
   */
  public List<Date> getDates() {
    if ((dates == null || dates.isEmpty()) && !dateList.isEmpty()) {
      dates = new ArrayList<>(dateList.size());
      dateFmt.setTimeZone(TimeZone.getTimeZone("GMT"));
      for (String dateString : dateList) {
        Date d = dateFmt.parse(dateString, new ParsePosition(0));
        //DateFromString.getDateUsingSimpleDateFormat(dateString,
        //    DATE_FORMAT);
        dates.add(d);
      }
    }
    return dates;
  }

  /**
   * Get the date string at the index
   *
   * @param index index (row or column)
   * @return the date at that index
   */
  protected String getDateString(int index) {
    return dateList.get(index);
  }

  /**
   * Print the list of dates in the file
   */
  public void printDates() {
    StringBuilder builder = new StringBuilder();
    builder.append("\nDates:\n");
    for (String date : dateList) {
      builder.append("\t");
      builder.append(date);
      builder.append("\n");
    }
    System.out.println(builder.toString());
  }

  /**
   * Print the list of dates in the file
   *
   * @param list true to list each station, false to list summary
   */
  public void printStations(boolean list) {
    StringBuilder builder = new StringBuilder();
    builder.append("\nStations:\n");
    if (list) {
      for (GempakStation station : getStations()) {
        builder.append(station);
        builder.append("\n");
      }
    } else {
      builder.append("\t");
      builder.append(getStations().size());
    }
    System.out.println(builder.toString());
  }

  /**
   * Find the station index for the specified station id.
   *
   * @param id station id (case sensitive)
   * @return index or -1 if not found.
   */
  public int findStationIndex(String id) {
    for (GempakStation station : getStations()) {
      if (station.getSTID().equals(id)) {
        return station.getIndex();
      }
    }
    return -1;
  }

  /**
   * Get the type for this file
   *
   * @return file type
   */
  public String getFileType() {
    String type = "Unknown";
    switch (dmLabel.kftype) {

      case MFSN:
        type = "Sounding";
        break;

      case MFSF:
        type = "Surface";
        break;

      default:
    }
    if (!subType.equals("")) {
      type = type + " (" + subType + ")";
    }
    return type;
  }

  /**
   * Subclasses need to set the subtype. This is an abstract
   * method so users will implement something to set the type.
   */
  protected abstract void makeFileSubType();

  /**
   * Get the file sub type
   *
   * @return the subtype.
   */
  protected String getFileSubType() {
    return subType;
  }

}


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

package dap4.core.util;

import java.io.*;
import java.util.Stack;

/**
 * Extend PrintWriter to provide indent support
 */

public class IndentWriter extends PrintWriter
{

    //////////////////////////////////////////////////
    // Constants
    static final int MAXDEPTH = 100;

    //////////////////////////////////////////////////
    // Instance variables

    String blanks = "                                        ";
    int delta = 4;
    int depth = 0;

    //////////////////////////////////////////////////
    // Constructors

    public IndentWriter(File file) throws FileNotFoundException
    {
        super(file);
    }

    public IndentWriter(Writer writer)
    {
        super(writer);
    }

    public IndentWriter(OutputStream stream)
    {
        super(stream);
    }

    //////////////////////////////////////////////////
    // Indent methods

    public void indent()
    {
        indent(1);
    }

    public void outdent()
    {
        outdent(1);
    }

    public void outdent(int n)
    {
        indent(-n);
    }

    /**
     * Set depth += n
     *
     * @param n depth increment/decrement(if neg)
     */
    public void indent(int n)
    {
        depth += n;
        if(depth < 0)
            depth = 0;
        else if(depth > MAXDEPTH)
            depth = MAXDEPTH;
    }

    /**
     * Set depth = n
     *
     * @param n absolute depth
     */
    public void setIndent(int n)
    {
        depth = n;
        if(depth < 0)
            depth = 0;
        else if(depth > MAXDEPTH)
            depth = MAXDEPTH;
    }

    public void margin()
    {
        super.write(getMargin());
    }

    public String getMargin()
    {
        int nspaces = delta * depth;
        while(blanks.length() < nspaces)
            blanks = blanks + blanks;
        return blanks.substring(0, nspaces);
    }

    public int getIndent()
    {
        return depth;
    }

    // Provide an indent stack
    Stack<Integer> indentstack = new Stack<Integer>();

    public void push()
    {
        indentstack.push(depth);
    }

    public void pop()
    {
        int n = (indentstack.empty() ? 0 : indentstack.pop());
        setIndent(n);
    }

    //////////////////////////////////////////////////
    // Margin cognizant print functions

    public void eol()
    {
        super.println("");
    }

    public void marginPrintln(String text)
    {
        marginPrint(text + "\n");
    }

    public void marginPrintln()
    {
        marginPrintln("");
    }

    public void marginPrint(String text)
    {
        super.print(getMargin() + text);
    }

    public void marginPrintf(String fmt, Object... args)
    {
        String s = String.format(fmt, args);
        marginPrint(s);
    }

} // class IndentWriter


package opendap.servers.parsers;

import opendap.servers.*;
import opendap.dap.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.Vector;

/**
 * Created by IntelliJ IDEA.
 * User: dmh
 * Date: 4/19/11
 * Time: 11:13 AM
 * To change this template use File | Settings | File Templates.
 */
abstract public class AST
{
    CEEvaluator ceEval;
    ClauseFactory clauseFactory;
    BaseTypeFactory factory;
    ServerDDS sdds;

    opendap.servers.parsers.AST root = null;

    void setRoot(opendap.servers.parsers.AST root)
    {
        this.root = root;
    }

    CEEvaluator getCeEval()
    {
        return root.ceEval;
    }

    ClauseFactory getClauseFactory()
    {
        return root.clauseFactory;
    }

    BaseTypeFactory getFactory()
    {
        return root.factory;
    }

    ServerDDS getSdds()
    {
        return root.sdds;
    }

    public AST(List<AST> nodes)
    {
        if(nodes == null) return;
        if(!nodes.contains(this)) nodes.add(this);
    }

    /* Only call on the root node */
    public void init(CEEvaluator ceEval,
                     BaseTypeFactory factory,
                     ClauseFactory clauseFactory,
                     ServerDDS sdds,
                     List<AST> nodes)
    {
        this.ceEval = ceEval;
        this.clauseFactory = clauseFactory;
        this.factory = factory;
        this.sdds = sdds;
        this.root = this;
        for(AST node : nodes) node.setRoot(this);
    }

    // abstract public String toString();

    Clause translate()
        throws DAP2ServerSideException, DAP2Exception, NoSuchFunctionException, NoSuchVariableException
    {
        return null;
    } // translate returns a subclause

    Stack collect(Stack components)// collect returns a stack of variables
        throws DAP2ServerSideException, DAP2Exception, NoSuchFunctionException, NoSuchVariableException
    {
        return null;
    }

    void walk() // walk operates by side effect
        throws DAP2ServerSideException, DAP2Exception, NoSuchFunctionException, NoSuchVariableException
    {
        return;
    }

} // class AST

class ASTconstraint extends AST
{
    List<ASTprojection> projections = null;
    List<ASTclause> selections = null;

    public ASTconstraint(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        if (projections != null)
            for(ASTprojection p : projections) {
                buf.append((first ? "" : ",") + p.toString());
                first = false;
            }
        first = true;
        if (selections != null)
            for(ASTclause c : selections) {
                buf.append(c.toString());
                first = false;
            }
        return buf.toString();
    }
*/

    public void walkConstraint()
        throws DAP2ServerSideException, DAP2Exception
    {
        if(projections != null)
            for(ASTprojection proj : projections) {
                proj.walk(ceEval);
            }
        else
            getCeEval().markAll(true);
        if(selections != null)
            for(ASTclause cl : selections) {
                getCeEval().appendClause(cl.translate());
            }
    }
}

class ASTprojection extends AST
{
    ASTvar var = null;
    ASTfcn fcn = null;

    public ASTprojection(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
	if(var != null)
	    return var.toString();
	else
	    return fcn.toString();
    }
*/

    void walk(CEEvaluator ceEval)
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        if(fcn != null) {
            SubClause subclause = fcn.translate();
            getCeEval().appendClause(subclause);
        } else {
            Stack components = new Stack();
            components = var.collect(components);
            markStackedVariables(components);
        }
    }

    /** Given a stack of BaseType variables, mark these as part of the
     * current projection. This function assumes that if the TOS contains a
     * Ctor type variable, all of its members are to be projected. Also
     * assume  all variables under the TOS are Ctor variables and
     * only the ctor itself is to be projected; the member within the Ctor
     * that is part of the projection will be on the stack, too.
     */

    /**
     * CEEValuator.markstackedvariables()
     * automatically marks all fields of a constructor as projected.
     * This causes problems with printDecl, which automatically recurses
     * on it fields. This means that constructor fields end up printed twice.
     * The original code, with the bad cloning, did not do this for some reason.
     * The solution taken here is mark which nodes were marked by constructor
     * recursion and which were marked directly. This info is then used in
     * printDecl to properly print the fields once and only once.
     */
    private void markStackedVariables(Stack s)
    {
        // Reverse the stack.
        Stack bts = new Stack();
        // LogStream.err.println("Variables to be marked:");
        while(!s.empty()) {
            //LogStream.err.println(((BaseType)s.peek()).getName());
            bts.push(s.pop());
        }

        // For each but the last stack element, set the projection.
        // setProject(true, false) for a ctor type sets the projection for
        // the ctor itself but *does not* set the projection for all its
        // children. Thus, if a user wants the variable S.X, and S contains Y
        // and Z too, S's projection will be set (so serialize will descend
        // into S) but X, Y and Z's projection remain clear. In this example,
        // X's projection is set by the code that follows the while loop.
        // 1/28/2000 jhrg
        while(bts.size() > 1) {
            DAPNode dn = (DAPNode) bts.pop();
            ServerMethods ct = (ServerMethods) dn;
            ct.setProject(true, false);
            //LogStream.err.println("mark singleton: " + dn.getName());
        }
        DAPNode dn = (DAPNode) bts.pop();
        // For the last element, project the entire variable.
        ServerMethods bt = (ServerMethods) dn;
        bt.setProject(true, true);
        //LogStream.err.println("mark all: " + dn.getName()); LogStream.err.flush();
    }

}

class ASTfcn extends ASTvalue
{
    String fcnname = null;
    List<ASTvalue> args = null;

    public ASTfcn(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
	buf.append(fcnname);
	buf.append("(");
        if(args != null)
            for(ASTvalue arg: args) {
	            if(!first) buf.append(","); else {first = false;}
                buf.append(arg.toString());
            }
        buf.append(")");
	return buf.toString();
    }
*/

    SubClause translate()
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        SubClause subclause = null;
        Vector<SubClause> cvtargs = new Vector<SubClause>();
        if(args != null)
            for(ASTvalue arg : args)
                cvtargs.addElement(arg.translate());
        subclause = getClauseFactory().newBTFunctionClause(fcnname, cvtargs);
        return subclause;
    }

}

class ASTvar extends ASTvalue
{
    List<ASTsegment> segments = new ArrayList<ASTsegment>();

    public ASTvar(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
	boolean first = true;
        for(ASTsegment seg : segments) {
            buf.append((first ? "" : ".") + seg.toString());
            first = false;
        }
        return buf.toString();
    }
*/

    Stack collect(Stack components)
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        for(ASTsegment segment : segments) {
            components = segment.collect(components);
        }
        return components;
    }

}

class ASTsegment extends AST
{
    String name = null;    // must be DAP decoded
    List<ASTslice> slices = new ArrayList<ASTslice>();

    public ASTsegment(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        buf.append(name);
        if (slices != null)
            for(ASTslice slice : slices) {
                buf.append(slice.toString());
            }
        return buf.toString();
    }
*/

    Stack collect(Stack components)
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        BaseType bt = null;
        ServerArrayMethods sam = null;
        components = getSdds().search(name, components);
        if(slices != null && slices.size() > 0) {
            try {
                bt = (BaseType) components.peek();
            } catch (ClassCastException cce) {
                String msg = "Attempt to treat the variable `" + name
                    + "' as if it is an array.";
                throw new DAP2Exception(DAP2Exception.MALFORMED_EXPR, msg);
            }
            if(bt instanceof DGrid) {// project the grid and the coordinate variable
                DGrid grid = ((DGrid) bt);
                bt = grid.getArray();
                sam = (ServerArrayMethods) bt;
                for(int i = 0;i < slices.size();i++) {
                    ASTslice slice = slices.get(i);
                    slice.walk(sam, i);
                }
                // walk the coordinate variables also
                for(int i = 0;i < slices.size();i++) {
                    ASTslice slice = slices.get(i);
                    bt = grid.getVar(i + 1);
                    sam = (ServerArrayMethods) bt;
                    slice.walk(sam, 0);
                }
            } else if(bt instanceof ServerArrayMethods) {
                sam = (ServerArrayMethods) bt;
                for(int i = 0;i < slices.size();i++) {
                    ASTslice slice = slices.get(i);
                    slice.walk(sam, i);
                }
            }
        }
        return components;
    }

}

class ASTslice extends AST
{
    long start = 0;
    long stop = 0;
    long stride = 1;

    public ASTslice(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        if (stride == 1) {
            if (first == last)
                return String.format("[%d]", first);
            else
                return String.format("[%d:%d]", first, last);
        } else
            return String.format("[%d:%d:%d]", first, stride, last);
    }
*/

    void walk(ServerArrayMethods sam, int index)
        throws InvalidDimensionException, SBHException
    {
        sam.setProjection(index, (int) start, (int) stride, (int) stop);
    }
}

class ASTvalue extends AST
{
    ASTconstant constant = null;
    ASTvar var = null; // tag == VAR
    ASTfcn fcn = null; // tag == FUNCTION

    public ASTvalue(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
	if(constant != null)
	    buf.append(constant.toString());
	else if(var != null)
	    buf.append(var.toString());
	else if(fcn != null)
	    buf.append(fcn.toString());
	else
	    assert (false);
        return buf.toString();
    }
*/

    SubClause translate()
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        SubClause subclause = null;
        if(constant != null) {
            subclause = constant.translate();
        } else if(var != null) {
            Stack components = new Stack();
            components = var.collect(components);
            subclause = getClauseFactory().newValueClause((BaseType) components.pop(), false);
        } else if(fcn != null) {
            subclause = fcn.translate();
        } else
            assert (false);
        return subclause;
    }
}

class ASTconstant extends ASTvalue
{
    int tag = 0; // See ExprParsefConstants
    String text = null; // tag == STRINGCONST
    long intvalue = 0; // tag == INTCONST
    double floatvalue = 0.0; // tag == FLOATCONST

    public ASTconstant(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        switch (tag) {
        case STRINGCONST:
            buf.append(String.format("\"%s\"", text));
            break;
        case INTCONST:
            buf.append(String.format("%d", intvalue));
            break;
        case FLOATCONST:
            buf.append(String.format("%.1f", floatvalue));
            break;
        default:
            assert(false);
        }
        return buf.toString();
    }
*/

    SubClause translate()
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        SubClause subclause = null;
        switch (tag) {
        case ExprParserConstants.INTCONST: {
            String s = String.format("%d", intvalue);
            DInt32 i = getFactory().newDInt32(s);
            i.setValue((int) intvalue);
            ((ServerMethods) i).setRead(true);
            ((ServerMethods) i).setProject(true);
            subclause = getClauseFactory().newValueClause(i, false);
        }
        break;
        case ExprParserConstants.FLOATCONST: {
            String s = String.format("%.1f", floatvalue);
            DFloat64 f = getFactory().newDFloat64(s);
            f.setValue(floatvalue);
            subclause = getClauseFactory().newValueClause(f, false);
        }
        break;
        case ExprParserConstants.STRINGCONST: {
            DString s = getFactory().newDString(text);
            s.setValue(text);
            ((ServerMethods) s).setRead(true);
            ((ServerMethods) s).setProject(true);
            subclause = getClauseFactory().newValueClause(s, false);
        }
        break;
        default:
            assert (false);
        }
        return subclause;
    }
}

class ASTclause extends AST
{
    int operator = 0; // See ExprParsefConstants
    ASTvalue lhs = null;
    List<ASTvalue> rhs = null;
    // Coverity[FB.UWF_NULL_FIELD]
    ASTfcn boolfcn = null;

    public ASTclause(List<AST> nodes)
    {
        super(nodes);
    }

/*
    public String toString()
    {
	if(boolfcn != null) {
	    return "&"+boolfcn.toString();
	} else {
            StringBuilder buf = new StringBuilder("&");
  	        buf.append(lhs.toString());
            buf.append(operatorString(operator));
            boolean first = true;
            if (rhs.size() > 1) buf.append("{");
            if (rhs != null)
                for(ASTvalue value : rhs) {
                    buf.append((first ? "" : ",") + value.toString());
                    first = false;
                }
            if (rhs.size() > 1) buf.append("}");
            return buf.toString();
	}
    }
*/

    public Clause translate()
        throws DAP2ServerSideException, DAP2Exception,
        NoSuchFunctionException, NoSuchVariableException
    {
        Clause clause = null;
        if(boolfcn != null)
            clause = boolfcn.translate();
        else {
            Vector<SubClause> cvtrhs = new Vector<SubClause>();
            for(ASTvalue v : rhs) cvtrhs.addElement(v.translate());
            SubClause lhsclause = lhs.translate();
            clause = getClauseFactory().newRelOpClause(operator, lhsclause, cvtrhs);
        }
        return clause;
    }

/*
    static String operatorString(int operator)
    {
        if (operator < 0
            || operator >= operatorImage.length
            || operatorImage[operator] == null)
        LogStream.err.println("Illegal operator");
        return operatorImage[operator];
    }
*/

}


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

package ucar.nc2.ui.op;

import java.awt.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import javax.swing.*;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.general.Dataset;
import org.jfree.data.general.Series;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.AbstractIntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;

import ucar.ma2.Array;
import ucar.ma2.Index;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;
import ucar.nc2.dataset.CoordinateAxis1DTime;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.time.CalendarDate;
import ucar.util.prefs.PreferencesExt;

/**
 * Plot with JFreeChart
 *
 * @author https://github.com/petejan
 */
public class VariablePlot extends JPanel {

    private final static org.slf4j.Logger log
                = org.slf4j.LoggerFactory.getLogger (MethodHandles.lookup ( ).lookupClass ( ));

  final JFreeChart chart;

  public VariablePlot(PreferencesExt prefs) {
    // this.prefs = prefs;

    setLayout(new BorderLayout());
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    chart = createChart();

    final ChartPanel chartPanel = new ChartPanel(chart);
    chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
    chartPanel.setMouseZoomable(true, false);

    if (prefs != null)
      chartPanel.setBounds((Rectangle) prefs.getBean("PlotWindowBounds", new Rectangle(300, 300, 600, 600)));
    else
      chartPanel.setBounds(new Rectangle(300, 300, 600, 600));

    XYPlot plot = chart.getXYPlot();

    plot.setBackgroundPaint(Color.white);
    plot.setDomainGridlinePaint(Color.GRAY);
    plot.setDomainGridlinesVisible(true);
    plot.setRangeGridlinePaint(Color.GRAY);
    plot.setAxisOffset(RectangleInsets.ZERO_INSETS);
    LegendTitle legend = chart.getLegend();
    if (legend != null) {
      legend.setPosition(RectangleEdge.BOTTOM);
    }
    add(chartPanel);

  }

  private JFreeChart createChart() {
    final JFreeChart chart = ChartFactory.createTimeSeriesChart(
            "Variable Plot",
            "X",
            "Value",
            null,
            true,
            true,
            false
    );
    return chart;
  }

  NetcdfFile file;

  public void setDataset(NetcdfFile ncFile) {
	  log.info("setDataset");
	  file = ncFile;
  }

  public void autoScale() {
	chart.getXYPlot().getRangeAxis().setAutoRange(true);
	chart.getXYPlot().getDomainAxis().setAutoRange(true);
  }

  public void clear() {
	  XYPlot p = chart.getXYPlot();

	  for(int i = 0;i<p.getDatasetCount();i++)
	  {
		  Dataset dataset = p.getDataset(i);

		  log.info("clear dataset " + i + " dataset " + dataset);

		  if (dataset instanceof TimeSeriesCollection)
			  ((TimeSeriesCollection)dataset).removeAllSeries();
		  if (dataset instanceof XYSeriesCollection)
			  ((XYSeriesCollection)dataset).removeAllSeries();

		  p.setDataset(i, null);
		  if (i > 0)
			  p.setRangeAxis(i, null);
	  }
  }

  // assume first dimension is TIME series
  public void setVariable(Variable v) throws IOException {
    log.info("variable " + v.getShortName());

    AbstractIntervalXYDataset dataset = null;

    Dimension dim = v.getDimension(0);
    String dimName = dim.getShortName();

    Attribute title = file.findGlobalAttribute("title");
    if (title != null)
      chart.setTitle(title.getStringValue());

    Variable varXdim = file.findVariable(null, dimName);
    boolean hasXdim = false;
    if (varXdim != null)
    	hasXdim = true;

    boolean xIsTime = false;

    XYPlot p = chart.getXYPlot();
    if (hasXdim)
    {
	    Attribute xUnit = varXdim.findAttribute("units");
	    Attribute xAxis = varXdim.findAttribute("axis");
	    if (xUnit != null)
	    	if (xUnit.getStringValue().contains("since"))
	    		xIsTime = true;
	    if (xAxis != null)
	    	if (xAxis.getStringValue().equals("T"))
	    		xIsTime = true;
	    if (xUnit != null)
	    	p.getDomainAxis().setLabel(xUnit.getStringValue());
	    else
	    	p.getDomainAxis().setLabel(dimName);

        if (xAxis != null)
	        log.info("X axis type " + xUnit.getDataType() + " value " + xUnit.toString() + " is Time " + xIsTime);
    }

    int ax = 0;
    log.info("dataset count " + p.getDatasetCount());
    if (p.getDatasetCount() >= 1)
    {
    	if (p.getDataset(p.getDatasetCount()-1) != null)
    	{
		    log.info("number in dataset " + p.getDataset(0));
		    ax = p.getDatasetCount();
		    if (ax > 0)
		    {
		    	p.setRangeAxis(ax, new NumberAxis());
		    }
    	}
    }
    log.info("axis number " + ax);

    final XYItemRenderer renderer = p.getRenderer();
    if (xIsTime)
    {
        final StandardXYToolTipGenerator g = new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, new SimpleDateFormat("d-MMM-yyyy"), new DecimalFormat("0.00"));
        renderer.setBaseToolTipGenerator(g);

    	dataset = new TimeSeriesCollection();
    }
    else
    {
        final StandardXYToolTipGenerator g = new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, new DecimalFormat("0.00"), new DecimalFormat("0.00"));
        renderer.setBaseToolTipGenerator(g);

        dataset = new XYSeriesCollection();
        p.setDomainAxis(new NumberAxis()); // change to NumberAxis from DateAxis which is what is created
    }
    p.getRangeAxis(ax).setAutoRange(true);

    Attribute vUnit = v.findAttribute("units");
    Attribute vfill = v.findAttribute("_FillValue");
    double dfill = Double.NaN;
    if (vfill != null)
      dfill = vfill.getNumericValue().doubleValue();

    if (vUnit != null)
    	p.getRangeAxis(ax).setLabel(vUnit.getStringValue());

    NetcdfDataset fds = new NetcdfDataset(file);

    CoordinateAxis1DTime tm = null;
    List<CalendarDate> dates = null;
    Array varXarray = null;

    if (hasXdim)
    {
    	varXarray = varXdim.read();
	    if (xIsTime)
	    {
		    tm = CoordinateAxis1DTime.factory(fds, new VariableDS(null, varXdim, true), null);
		    dates = tm.getCalendarDates();
	    }
    }

    Array a = v.read();

    Index idx = a.getIndex();
    idx.setCurrentCounter(0);

    int d2 = 1;
    int rank = idx.getRank();
    for (int k = 1; k < rank; k++) {
      d2 *= idx.getShape(k);
    }
    log.info("variable : " + v.getShortName() + " : dims " + v.getDimensionsString() + " rank " + rank + " d2 " + d2);

    double max = -1000;
    double min = 1000;

    for (int j = 0; j < d2; j++) {
      if (rank > 1)
        idx.set1(j); // this wont work for 3rd dimension > 1

      String name = v.getShortName();
      if (d2 > 1)
        name += "-" + j;

      Series s1;
      if (xIsTime)
    	  s1 = new TimeSeries(name);
      else
    	  s1 = new XYSeries(name);

      for (int i = 0; i < idx.getShape(0); i++) {
        idx.set0(i);
        float f = a.getFloat(idx);
        if (f != dfill) {
        	if (!Float.isNaN(f))
        	{
	        	max = Math.max(max,  f);
	        	min = Math.min(min,  f);
        	}
        	if (xIsTime)
        	{
        		Date ts = new Date(dates.get(i).getMillis());
        		((TimeSeries)s1).addOrUpdate(new Second(ts), f);
        	}
        	else if (hasXdim)
        	{
        		((XYSeries)s1).addOrUpdate(varXarray.getDouble(i), f);
        	}
        	else
        	{
        		((XYSeries)s1).addOrUpdate(i, f);
        	}
        }
      }
	  if (dataset instanceof TimeSeriesCollection)
		  ((TimeSeriesCollection)dataset).addSeries((TimeSeries)s1);
	  if (dataset instanceof XYSeriesCollection)
		  ((XYSeriesCollection)dataset).addSeries((XYSeries)s1);
    }
    final XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
    p.setRenderer(ax, renderer1);

    log.info("dataset " + ax + " max " + max + " min " + min + " : " + dataset);
    p.setDataset(ax, dataset);
    p.mapDatasetToRangeAxis(ax, ax);
    p.getRangeAxis(ax).setLowerBound(min);
    p.getRangeAxis(ax).setUpperBound(max);

  }
}


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

import ucar.nc2.ui.util.ScreenUtils;

import java.awt.*;
import java.beans.*;
import javax.swing.*;

/**
 *  Provides common L&F for managing independent dialogs
 *  Takes RootPaneContainer as parent, to work with both applet and app
 *  Will reset L&F
 *
 *  example of use:
     infoWindow = new IndependentDialog(topLevel.getRootPaneContainer(), false, "Dataset Information");
     datasetInfoTA = new TextHistoryPane(500, 100, true);
     Container cp = infoWindow.getContentPane();
     cp.add(datasetInfoTA, BorderLayout.CENTER);
     infoWindow.pack();
     infoWindow.setSize(700,700);
     infoWindow.setLocation(100,100);
 *
 * @author John Caron
 */
public class IndependentDialog extends JDialog {
  protected JFrame parent;

  /** constructor
     @param parent      JFrame (application) or JApplet (applet)
     @param modal     is modal
     @param title       Window title
   */
  public IndependentDialog(JFrame parent, boolean modal, String title) {
    // having a parent JFrame is better. But what to do about applets?
    super(parent == null ? ucar.util.prefs.ui.PrefPanel.findActiveFrame() : parent);
    this.parent = parent;
    setModal(modal);
    if (title != null)
      setTitle(title);

    // L&F may change
    UIManager.addPropertyChangeListener( new PropertyChangeListener() {
      public void propertyChange( PropertyChangeEvent e) {
        if (e.getPropertyName().equals("lookAndFeel"))
          SwingUtilities.updateComponentTreeUI( IndependentDialog.this);
      }
    });

    // set position based on owned
    Window owner = getOwner();
    if (owner != null) {
      Rectangle b = owner.getBounds();
      int x = (int) ( b.getX() + b.getWidth()/2);
      int y = (int) ( b.getY() + b.getHeight()/2);
      setLocation( x, y);
    }
  }

  public IndependentDialog(JFrame parent, boolean modal, String title, Component comp) {
    this(parent, modal, title);
    setComponent( comp);
  }

  public void setComponent( Component comp) {
    Container cp = getContentPane();
    cp.add(comp, BorderLayout.CENTER);
    pack();
  }

  /* @Override
     public void setBounds(Rectangle r) {
    if (parent != null) {
      Rectangle have = parent.getGraphicsConfiguration().getBounds();
      if (r.getX() < have.getWidth() - 25); // may be off screen when switching between 2 monitor system
      setBounds(r.x, r.y, r.width, r.height);
    }
  } */

  @Override
  public void setBounds(Rectangle r) {
    // keep window on the screen
    Rectangle screenSize = ScreenUtils.getScreenVirtualSize();
    Rectangle result = r.intersection(screenSize);
    if (!result.isEmpty())
      super.setBounds(result);
  }
}


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

package ucar.nc2.grib;

import javax.annotation.Nullable;
import thredds.inventory.CollectionManager;
import thredds.inventory.CollectionUpdateType;
import thredds.inventory.MFile;
import ucar.nc2.grib.grib1.Grib1Index;
import ucar.nc2.grib.grib2.Grib2Index;
import ucar.unidata.io.RandomAccessFile;

import java.io.File;
import java.io.IOException;

/**
 * Abstract superclass for Grib1Index and Grib2Index.
 * Handles gbx9 index for grib.
 * <p/>
 * Static methods for creating gbx9 indices for a single file.
 *
 * @author John
 * @since 9/5/11
 */
public abstract class GribIndex {
  public static final String GBX9_IDX = ".gbx9";
  public static final boolean debug = false;

  private static final CollectionManager.ChangeChecker gribCC = new CollectionManager.ChangeChecker() {
    public boolean hasChangedSince(MFile file, long when) {
      String idxPath = file.getPath();
      if (!idxPath.endsWith(GBX9_IDX)) idxPath += GBX9_IDX;
      File idxFile = GribIndexCache.getExistingFileOrCache(idxPath);
      if (idxFile == null) return true;

      long idxLastModified =  idxFile.lastModified();
      if (idxLastModified < file.getLastModified()) return true;
      if (0 < when && when < idxLastModified) return true;
      return false;
    }
    public boolean hasntChangedSince(MFile file, long when) {
      String idxPath = file.getPath();
      if (!idxPath.endsWith(GBX9_IDX)) idxPath += GBX9_IDX;
      File idxFile = GribIndexCache.getExistingFileOrCache(idxPath);
      if (idxFile == null) return true;

      if (idxFile.lastModified() < file.getLastModified()) return true;
      if (0 < when && idxFile.lastModified() < when) return true;
      return false;
    }
  };
  /////////////////////////////////////////////////////////////////////////

  public static CollectionManager.ChangeChecker getChangeChecker() {
    return gribCC;
  }

  @Nullable
  public static GribIndex open(boolean isGrib1, MFile mfile) {

    GribIndex index = isGrib1 ? new Grib1Index() : new Grib2Index();

    if (!index.readIndex(mfile.getPath(), mfile.getLastModified(), CollectionUpdateType.never)) {
      return null;
    }

    return index;
  }

  /**
   * Create a gbx9 index from a single grib1 or grib2 file.
   * Use the existing index if it already exists.
   *
   * @param isGrib1 true if grib1
   * @param mfile the grib file
   * @param force  force writing index
   * @return the resulting GribIndex
   * @throws IOException on io error
   */
  public static GribIndex readOrCreateIndexFromSingleFile(boolean isGrib1, MFile mfile, CollectionUpdateType force, org.slf4j.Logger logger) throws IOException {

    GribIndex index = isGrib1 ? new Grib1Index() : new Grib2Index();

    if (!index.readIndex(mfile.getPath(), mfile.getLastModified(), force)) { // heres where the index date is checked against the data file
      index.makeIndex(mfile.getPath(), null);
      logger.debug("  Index written: {} == {} records", mfile.getName() + GBX9_IDX, index.getNRecords());
    } else if (debug) {
      logger.debug("  Index read: {} == {} records", mfile.getName() + GBX9_IDX, index.getNRecords());
    }

    return index;
  }

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

  /**
   * Read the gbx9 index file.
   *
   * @param location location of the data file
   * @param dataModified last modified date of the data file
   * @param force rewrite? always, test, nocheck, never
   * @return true if index was successfully read, false if index must be (re)created
   */
  public abstract boolean readIndex(String location, long dataModified, CollectionUpdateType force);

  /**
   * Make the gbx9 index file.
   *
   * @param location location of the data file
   * @param dataRaf already opened data raf (leave open); if null, makeIndex opens and closes)
   * @return true
   * @throws IOException on io error
   */
  public abstract boolean makeIndex(String location, RandomAccessFile dataRaf) throws IOException;

  /**
   * The number of records in the index.
   * @return The number of records in the index.
   */
  public abstract int getNRecords();
}