// $Id: GeoGrid.java,v 1.7 2004/11/10 17:00:28 caron Exp $
/*
 * Copyright 1997-2000 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2.dataset.grid;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.util.NamedObject;
import ucar.nc2.dataset.*;
import ucar.unidata.util.Format;
import ucar.unidata.geoloc.*;

import java.util.*;

/**
 * A georeferencing "gridded" VariableEnhanced, that has a GridCoordSys.
 * In VisAD data model, it is a sampled Field.
 * The dimension are put into canonical order: (t, z, y, x).
 *
 * <p> Implementation note: 
 * If the Horizontal axes are 2D, the x and y dimensions are arbitrarily chosen to be
 *   gcs.getXHorizAxis().getDimension(1), gcs.getXHorizAxis().getDimension(0) respectively.
 *
 * <p> Note: these classes should be considered experimental and will likely be refactored in the next release.
 *
 * @author caron
 * @version $Revision: 1.7 $ $Date: 2004/11/10 17:00:28 $
 */

public class GeoGrid implements NamedObject {

  private GridDataset dataset;
  private GridCoordSys gcs;
  private VariableEnhanced vs;
  private int xDimOrgIndex = -1, yDimOrgIndex = -1, zDimOrgIndex = -1, tDimOrgIndex = -1;
  private int xDimNewIndex = -1, yDimNewIndex = -1, zDimNewIndex = -1, tDimNewIndex = -1;
  private ArrayList mydims;

  private boolean debugArrayShape = false;

  /**
   * Constructor.
   * @param dataset belongs to this dataset
   * @param dsvar wraps this Variable
   * @param gcs has this grid coordinate system
   */
  public GeoGrid(GridDataset dataset, VariableEnhanced dsvar, GridCoordSys gcs) {
    this.dataset = dataset;
    this.vs = dsvar;
    this.gcs = gcs;

    if (gcs.isProductSet()) {
      xDimOrgIndex = findDimension( gcs.getXHorizAxis().getDimension(0));
      yDimOrgIndex = findDimension( gcs.getYHorizAxis().getDimension(0));

    } else { // 2D case
      yDimOrgIndex = findDimension( gcs.getXHorizAxis().getDimension(0));
      xDimOrgIndex = findDimension( gcs.getXHorizAxis().getDimension(1));
    }

    if (gcs.getVerticalAxis() != null) zDimOrgIndex = findDimension( gcs.getVerticalAxis().getDimension(0));
    if (gcs.getTimeAxis() != null) tDimOrgIndex = findDimension( gcs.getTimeAxis().getDimension(0));

    // construct canonical dimension list
    int count = 0;
    this.mydims = new ArrayList();
    if (tDimOrgIndex >= 0) {
      mydims.add( dsvar.getDimension( tDimOrgIndex));
      tDimNewIndex = count++;
    }
    if (zDimOrgIndex >= 0) {
      mydims.add( dsvar.getDimension( zDimOrgIndex));
      zDimNewIndex = count++;
    }
    if (yDimOrgIndex >= 0) {
      mydims.add( dsvar.getDimension( yDimOrgIndex));
      yDimNewIndex = count++;
    }
    if (xDimOrgIndex >= 0) {
      mydims.add( dsvar.getDimension( xDimOrgIndex));
      xDimNewIndex = count++;
    }
  }

  private int findDimension( Dimension want) {
    java.util.List dims = vs.getDimensions();
    for (int i=0; i < dims.size();i ++) {
      Dimension d = (Dimension) dims.get(i);
      if (d.equals( want))
        return i;
    }
    return -1;
  }

  /**
   * Returns an ArrayList containing the dimensions used by this geoGrid.
   * The dimension are put into canonical order: (t, z, y, x). Note that the z and t dimensions are optional.
   * If the Horizontal axes are 2D, the x and y dimensions are arbitrarily chosen to be
   *   gcs.getXHorizAxis().getDimension(1), gcs.getXHorizAxis().getDimension(0), respectively.

   * @return List with objects of type Dimension, in canonical order.
   */
  public java.util.List getDimensions() { return new ArrayList(mydims); }

  /** get the ith dimension
   * @param i : which dimension
   * @return ith Dimension
   */
  public Dimension getDimension(int i) {
    if ((i < 0) || (i >= mydims.size())) return null;
    return (Dimension) mydims.get(i);
  }

  /** get the time Dimension, or null if none */
  public Dimension getTimeDimension() { return tDimNewIndex < 0 ? null : getDimension( tDimNewIndex); }
  /** get the z Dimension, or null if none */
  public Dimension getZDimension() { return zDimNewIndex < 0 ? null : getDimension( zDimNewIndex); }
  /** get the y Dimension, */
  public Dimension getYDimension() { return yDimNewIndex < 0 ? null : getDimension( yDimNewIndex); }
  /** get the x Dimension */
  public Dimension getXDimension() { return xDimNewIndex < 0 ? null : getDimension( xDimNewIndex); }

  /** get the time Dimension index in the geogrid (canonical order), or -1 if none */
  public int getTimeDimensionIndex() { return tDimNewIndex; }
  /** get the z Dimension index in the geogrid (canonical order), or -1 if none */
  public int getZDimensionIndex() { return zDimNewIndex; }
  /** get the y Dimension index in the geogrid (canonical order) */
  public int getYDimensionIndex() { return yDimNewIndex; }
  /** get the x Dimension index in the geogrid (canonical order) */
  public int getXDimensionIndex() { return xDimNewIndex; }

  /**
   * Convenience function; look up Attribute by name.
   * @param name the name of the attribute
   * @return the attribute, or null if not found
   */
  public Attribute findAttributeIgnoreCase(String name) {
    return vs.findAttributeIgnoreCase(name);
  }

  /** get the GridDataset this GeoGrid belongs to. */
  // public GridDataset getDataset() { return dataset; }
  // Variable getVariable() { return vs; }

  /** get the rank */
  public int getRank() { return vs.getRank(); }
  /** get the name of the geoGrid.*/
  public String getName() { return vs.getName(); }
  /** get the GridCoordSys for this GeoGrid. */
  public GridCoordSys  getCoordinateSystem() { return gcs; }
  /** get the Projection. */
  public ProjectionImpl  getProjection() { return gcs.getProjection(); }

    /** ArrayList of thredds.util.NamedObject, from the  GridCoordSys. */
  public ArrayList getLevels() { return gcs.getLevels(); }

  /** ArrayList of thredds.util.NamedObject, from the  GridCoordSys. */
  public ArrayList getTimes() { return gcs.getTimes(); }

  /** get the standardized description, see VariableStandardized.getDescription() */
  public String getDescription() { return vs.getDescription(); }
  /** get the unit as a string, see VariableStandardized.getUnitString() */
  public String getUnitsString() { return vs.getUnitsString(); }
  //public ucar.unidata.geoloc.ProjectionImpl getProjection() { return gcs.getProjection(); }

    /** true if there may be missing data, see VariableStandardized.hasMissing() */
  public boolean hasMissingData() { return vs.hasMissing(); }

  /** if val is missing data, see VariableStandardized.isMissingData() */
  public boolean isMissingData(double val) { return vs.isMissing(val); }

  /**
   * Convert (in place) all values in the given array that are considered
   * as "missing" to Float.NaN, according to vs.isMissing(val).
   *
   * @param values input array
   * @return input array, with missing values converted to NaNs.
   */
  public float[] setMissingToNaN (float[] values) {
    if (!vs.hasMissing()) return values;
    final int length = values.length;
    for (int i = 0; i < length; i++) {
      double value = values[i];
      if (vs.isMissing( value ))
        values[i] = Float.NaN;
    }
    return values;
  }

  /**
   * Get the minimum and the maximum data value of the previously read Array,
   *  skipping missing values as defined by isMissingData(double val).
   * @param a Array to get min/max values
   * @return both min and max value.
   */
  public MAMath.MinMax getMinMaxSkipMissingData( Array a) {
    if (!hasMissingData())
      return MAMath.getMinMax( a);

    IndexIterator iter = a.getIndexIterator();
    double max = Double.MIN_VALUE;
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (isMissingData(val))
        continue;
      if (val > max)
        max = val;
      if (val < min)
        min = val;
    }
    return new MAMath.MinMax(min, max);
  }

  /**
   * Reads in the data "volume" at the given time index.
   * If its a product set, put into canonical order (z-y-x).
   * If not a product set, reorder to (z,i,j), where i, j are from the
   * original
   *
   * @param t time index; ignored if no time axis.
   * @return data[z,y,x] or data[y,x] if no z axis.
   */
  public Array readVolumeData(int t) throws java.io.IOException {
    //if (gcs.isProductSet())
      return getDataSlice(t, -1, -1, -1);
    /* else { // 2D XY
      int rank = vs.getRank();
      int[] shape = vs.getShape();
      int [] start = new int[rank];

      CoordinateAxis taxis = gcs.getTimeAxis();
      if (taxis != null) {
        if ((t >= 0) && (t < taxis.getSize()))
          shape[ tDim] = 1;  // fix t
          start[ tDim] = t;
      }

      if (debugArrayShape) {
        System.out.println("getDataVolume shape = ");
        for (int i=0; i<rank; i++)
          System.out.println("   start = "+start[i]+" shape = "+ shape[i]);
      }

      Array dataVolume;
      try {
        dataVolume = vs.read( start, shape);
      } catch (Exception e) {
        System.out.println("Exception: GeoGridImpl.getdataSlice() on dataset "+getName());
        e.printStackTrace();
        throw new java.io.IOException(e.getMessage());
      }

      // LOOK: no reordering FIX
      return dataVolume.reduce();
    } */
  }

  /**
   * Reads a Y-X "horizontal slice" at the given time and vertical index.
   * If its a product set, put into canonical order (y-x).
   *
   * @param t time index; ignored if no time axis.
   * @param z vertical index; ignored if no z axis.
   * @return data[y,x]
   */
  public Array readYXData(int t, int z) throws java.io.IOException {
    return getDataSlice(t, z, -1, -1);
  }


  /**
   * Reads a Z-Y "vertical slice" at the given time and x index.
   * If its a product set, put into canonical order (z-y).
   *
   * @param t time index; ignored if no time axis.
   * @param x x index; ignored if no x axis.
   * @return data[z,y]
   */
  public Array readZYData(int t, int x) throws java.io.IOException {
    return getDataSlice(t, -1, -1, x);
  }

  /**
   * This reads in an arbitrary data slice, with the data in
   * canonical order (t-z-y-x). If any dimension does not exist, ignore it.
   *
   * @param t if < 0, get all of time dim; if valid index, fix sliceto that value.
   * @param z if < 0, get all of z dim; if valid index, fix slice to that value.
   * @param y if < 0, get all of y dim; if valid index, fix slice to that value.
   * @param x if < 0, get all of x dim; if valid index, fix slice to that value.
   *
   * @return data[t,z,y,x], eliminating missing or fixed dimension.
   */
  public Array getDataSlice(int t, int z, int y, int x) throws java.io.IOException {
    int rank = vs.getRank();
    int [] start = new int[rank];
    int [] shape = new int[rank];
    for (int i=0; i<rank; i++) {
      start[i] = 0;
      shape[i] = 1;
    }
    Dimension xdim = getXDimension( );
    Dimension ydim = getYDimension( );
    Dimension zdim = getZDimension( );
    Dimension tdim = getTimeDimension( );

      // construct the shape of the data volume to be read
    if (tdim != null) {
      if ((t >= 0) && (t < tdim.getLength()))
        start[ tDimOrgIndex] = t;  // fix t
      else {
        shape[ tDimOrgIndex] = tdim.getLength(); // all of t
      }
    }

    if (zdim != null) {
      if ((z >= 0) && (z < zdim.getLength()))
        start[ zDimOrgIndex] = z;  // fix z
      else {
        shape[ zDimOrgIndex] = zdim.getLength(); // all of z
      }
    }

    if (ydim != null) {
      if ((y >= 0) && (y < ydim.getLength()))
        start[ yDimOrgIndex] = y;  // fix y
      else {
        shape[ yDimOrgIndex] = ydim.getLength(); // all of y
      }
    }

    if (xdim != null) {
      if ((x >= 0) && (x < xdim.getLength())) // all of x
        start[ xDimOrgIndex] = x;  // fix x
      else {
        shape[ xDimOrgIndex] = xdim.getLength(); // all of x
      }
    }

    if (debugArrayShape) {
      System.out.println("read shape from org variable = ");
      for (int i=0; i<rank; i++)
        System.out.println("   start = "+start[i]+" shape = "+ shape[i] +" name = "+vs.getDimension(i).getName());
    }

    // read it
    Array dataVolume;
    try {
      dataVolume = vs.read( start, shape);
    } catch (Exception e) {
      System.out.println("Exception: GeoGrid.getdataSlice() on dataset "+getName());
      e.printStackTrace();
      throw new java.io.IOException(e.getMessage());
    }

    // LOOK: the real problem is the lack of named dimensions in the Array object
    // figure out correct permutation for canonical ordering for permute
    ArrayList oldDims = new ArrayList( vs.getDimensions());
    int [] permuteIndex = new int[dataVolume.getRank()];
    int count = 0;
    if (oldDims.contains(tdim)) permuteIndex[count++] = oldDims.indexOf(tdim);
    if (oldDims.contains(zdim)) permuteIndex[count++] = oldDims.indexOf(zdim);
    if (oldDims.contains(ydim)) permuteIndex[count++] = oldDims.indexOf(ydim);
    if (oldDims.contains(xdim)) permuteIndex[count++] = oldDims.indexOf(xdim);

    if (debugArrayShape) {
      System.out.println("oldDims = ");
      for (int i=0; i<oldDims.size(); i++)
        System.out.println("   oldDim = "+((Dimension)oldDims.get(i)).getName());
      System.out.println("permute dims = ");
      for (int i=0; i<permuteIndex.length; i++)
        System.out.println("   oldDim index = "+permuteIndex[i]);
    }

    // permute to the order t,z,y,x
    dataVolume = dataVolume.permute(permuteIndex);

    // eliminate fixed dimensions, but not all dimensions of length 1.
    count = 0;
    if (tdim != null) {
      if (t >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (zdim != null) {
      if (z >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (ydim != null) {
      if (y >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (xdim != null) {
      if (x >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    return dataVolume;
  }

    /**
   * This reads in an arbitrary data slice, putting the indices into
   * canonical order (t-z-y-x). If any dimension does not exist, ignore it.
   *
   * @param t if < 0, get all of time dim; if valid index, fix sliceto that value.
   * @param z if < 0, get all of z dim; if valid index, fix slice to that value.
   * @param y if < 0, get all of y dim; if valid index, fix slice to that value.
   * @param x if < 0, get all of x dim; if valid index, fix slice to that value.
   *
   * @return data[t,z,y,x], eliminating missing or fixed dimension.
   *
   *
  Array getDataSlice(int t, int z, int y, int x) throws java.io.IOException {
    int rank = vs.getRank();

    int [] start = new int[rank];
    int [] shape = new int[rank];
    int [] dimOrg = new int[rank];
    for (int i=0; i<rank; i++) {

      start[i] = 0;
      shape[i] = 1;
    }
    CoordinateAxis xaxis = gcs.getXHorizAxis();
    CoordinateAxis yaxis = gcs.getYHorizAxis();
    CoordinateAxis zaxis = gcs.getVerticalAxis();
    CoordinateAxis taxis = gcs.getTimeAxis();

      // construct the shape of the data volume
      // we also construct the "original dimensions" array so that we can later permute
      // it into canonical sequence.
    if (taxis != null) {
      if ((t >= 0) && (t < taxis.getSize()))
        start[ tDim] = t;  // fix t
      else {
        shape[ tDim] = (int) taxis.getSize(); // all of t
        dimOrg[ tDim] = 4;
      }
    }

    if (zaxis != null) {
      if ((z >= 0) && (z < zaxis.getSize()))
        start[ zDim] = z;  // fix z
      else {
        shape[ zDim] = (int)  zaxis.getSize(); // all of z
        dimOrg[ zDim] = 3;
      }
    }

    if (yaxis != null) {
      if ((y >= 0) && (y < yaxis.getSize()))
        start[ yDim] = y;  // fix y
      else {
        shape[ yDim] = (int) yaxis.getSize();
        dimOrg[ yDim] = 2;
      }
    }

    if (xaxis != null) {
      if ((x >= 0) && (x < xaxis.getSize()))
        start[ xDim] = x;  // fix x
      else {
        shape[ xDim] = (int) xaxis.getSize();
        dimOrg[ xDim] = 1;
      }
    }

    if (debugArrayShape) {
      System.out.println("read shape = ");
      for (int i=0; i<rank; i++)
        System.out.println("   start = "+start[i]+" shape = "+ shape[i]);
    }

    Array dataVolume;
    try {
      dataVolume = vs.read( start, shape);
    } catch (Exception e) {
      System.out.println("Exception: GeoGridImpl.getdataSlice() on dataset "+getName());
      e.printStackTrace();
      throw new java.io.IOException(e.getMessage());
    }

    // reduce the dimensions that were fixed
    int countReduced = 0;
    for (int i=0; i<rank; i++) {
      if (dimOrg[i] == 0) {
        dataVolume = dataVolume.reduce(i-countReduced);
        countReduced++;
      }
    }

    if (debugArrayShape) {
      System.out.println("array shape = ");
      for (int i=0; i<dataVolume.getRank(); i++)
        System.out.println("   shape = "+dataVolume.getShape()[i]);
    }

    // put in logical "canonical ordering": data(t,z,y,x)
    int rankNew =  dataVolume.getRank();
    int [] dimNew = new int[rankNew];
    int count = 0;
    for (int i=0; i<rank; i++)
      if (dimOrg[i] != 0) // eliminate fixed dimensions
        dimNew[count++] = dimOrg[i];

      // bubble sort !! LOOK FIX THIS
    for (int i=0; i<rankNew; i++)
      for (int j=i+1; j<rankNew; j++)
        if (dimNew[i] < dimNew[j]) {
          dataVolume = dataVolume.transpose(i,j);
          int hold = dimNew[i];
          dimNew[i] = dimNew[j];
          dimNew[j] = hold;
      }

    if (debugArrayShape) {
      System.out.println("logical array shape (t,z,y,x) = ");
      for (int i=0; i<dataVolume.getRank(); i++)
        System.out.println("   shape = "+dataVolume.getShape()[i]);
    }

    return dataVolume;
  } */

  /* private Array getDataVolume(int t) throws java.io.IOException {
    int rank = vs.getRank();
    int[] shape = vs.getShape();
    int [] start = new int[rank];

    CoordinateAxis taxis = gcs.getTimeAxis();
    if (taxis != null) {
      if ((t >= 0) && (t < taxis.getSize()))
        start[ tDim] = t;  // fix t
    }

    if (debugArrayShape) {
      System.out.println("getDataVolume shape = ");
      for (int i=0; i<rank; i++)
        System.out.println("   start = "+start[i]+" shape = "+ shape[i]);
    }

    Array dataVolume;
    try {
      dataVolume = vs.read( start, shape);
    } catch (Exception e) {
      System.out.println("Exception: GeoGridImpl.getdataSlice() on dataset "+getName());
      e.printStackTrace();
      throw new java.io.IOException(e.getMessage());
    }

    // LOOK: no reordering FIX
    return dataVolume.reduce();
  } */


  /////////////////////////////////////////////////////////////////////////////////
  /**
   * Instances which have same name and coordinate system are equal.
   */
  public boolean equals(Object oo) {
    if (this == oo) return true;
    if ( !(oo instanceof GeoGrid))
      return false;

    GeoGrid d = (GeoGrid) oo;
    // if (!dataset.getName().equals(d.getDataset().getName())) return false;
    if (!getName().equals(d.getName())) return false;
    if (!getCoordinateSystem().equals(d.getCoordinateSystem())) return false;

    return true;
  }

  /**
   * Override Object.hashCode() to be consistent with equals.
   */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      // result = 37*result + dataset.getName().hashCode();
      result = 37*result + getName().hashCode();
      result = 37*result + getCoordinateSystem().hashCode();
      hashCode = result;
    }
    return hashCode;
  }
  private volatile int hashCode = 0; // Bloch, item 8

  /** string representation */
  public String toString() { return getName(); }

  /** nicely formatted information */
  public String getInfo() {
    StringBuffer buf = new StringBuffer(200);
    buf.setLength(0);
    buf.append(getName());
    Format.tab(buf, 15, true);
    buf.append(getUnitsString());
    Format.tab(buf, 30, true);
    buf.append(hasMissingData());
    Format.tab(buf, 40, true);
    buf.append(getDescription());
    return buf.toString();
  }

}


/**
 * $Log: GeoGrid.java,v $
 * Revision 1.7  2004/11/10 17:00:28  caron
 * no message
 *
 * Revision 1.6  2004/11/10 16:47:14  caron
 * no message
 *
 * Revision 1.5  2004/11/07 03:00:50  caron
 * *** empty log message ***
 *
 * Revision 1.4  2004/10/06 19:03:41  caron
 * clean up javadoc
 * change useV3 -> useRecordsAsStructure
 * remove id, title, from NetcdfFile constructors
 * add "in memory" NetcdfFile
 *
 * Revision 1.3  2004/09/22 18:43:04  caron
 * move common to ucar.unidata; projections use Parameter, no tAttribute
 *
 * Revision 1.2  2004/09/22 13:46:37  caron
 * *** empty log message ***
 *
 * Revision 1.1  2004/08/16 20:53:50  caron
 * 2.2 alpha (2)
 *
 * Revision 1.8  2003/09/02 22:45:53  caron
 * bug in getDataSlice
 *
 * Revision 1.7  2003/06/24 21:58:45  caron
 * fix problem when axis had length 1
 *
 * Revision 1.6  2003/06/09 15:23:16  caron
 * add nc2.ui
 *
 * Revision 1.5  2003/06/05 23:06:08  caron
 * add Dimension reordering
 *
 * Revision 1.4  2003/06/03 20:58:20  caron
 * readVolumeData() works for ordered 2D case
 *
 * Revision 1.3  2003/06/03 20:06:12  caron
 * fix javadocs
 *
 * Revision 1.2  2003/05/14 21:33:51  caron
 * clean up, add new getXXData methods
 *
 * Revision 1.1  2003/04/08 15:06:29  caron
 * nc2 version 2.1
 *
 * Revision 1.1  2002/12/13 00:53:09  caron
 * pass 2
 *
 * Revision 1.1.1.1  2002/02/26 17:24:42  caron
 * import sources
 *
 * Revision 1.9  2001/05/10 18:48:59  caron
 * add StandardQuantity
 *
 * Revision 1.8  2001/05/01 15:15:59  caron
 * varStandard.hasMissing()
 *
 * Revision 1.7  2001/02/21 21:29:05  caron
 * missing data
 *
 * Revision 1.6  2000/10/03 21:10:14  caron
 * add CoordAxisVert, better javadoc
 *
 */

