// $Id: N3iosp.java,v 1.10 2004/09/22 18:44:32 caron Exp $
/*
 * Copyright 1997-2004 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, strlenwrite to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2;

import ucar.ma2.*;
import ucar.unidata.io.RandomAccessFile;

import java.util.*;
import java.io.PrintStream;
import java.io.IOException;

/**
 * IOServiceProvider implementation abstract base class to read/write "version 3" netcdf files.
 *  AKA "file format version 1" files.
 *
 * @see N3raf concrete class
 */

abstract class N3iosp implements ucar.nc2.IOServiceProvider {

  // Default fill values, used unless _FillValue variable attribute is set.
  static protected final byte NC_FILL_BYTE = -127;
  static protected final byte NC_FILL_CHAR = 0;
  static protected final short NC_FILL_SHORT = (short) -32767;
  static protected final int NC_FILL_INT = -2147483647;
  static protected final float NC_FILL_FLOAT = 9.9692099683868690e+36f; /* near 15 * 2^119 */
  static protected final double NC_FILL_DOUBLE = 9.9692099683868690e+36;
  static protected final String FillValue = "_FillValue";

  public boolean isValidFile( ucar.unidata.io.RandomAccessFile raf) {
    return N3header.isValidFile( raf);
  }

  protected ucar.nc2.NetcdfFile ncfile;
  protected boolean readonly;

  protected ucar.unidata.io.RandomAccessFile raf;
  protected N3header headerParser;
  protected int numrecs, recsize;

  // used for writing
  protected int fileUsed = 0; // how much of the file is written to ?
  protected int recStart = 0; // where the record data starts

  protected boolean debug = false, debugSize = false, debugSPIO = false;
  protected boolean showHeaderBytes = false;

  //private ByteArrayOutputStream bos = new ByteArrayOutputStream( 10000);
  protected PrintStream out = System.out; // new PrintStream(bos);
  //String getInfo() { return bos.toString(); }

  // properties
  protected boolean useRecordStructure;

  public void setProperty( String name, String value) {
    if (name.equalsIgnoreCase("useRecordStructure"))
      useRecordStructure = value.equalsIgnoreCase("true");
  }

  //////////////////////////////////////////////////////////////////////////////////////
  // read existing file

  public void open(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile,
                   ucar.nc2.util.CancelTask cancelTask) throws IOException {
    this.raf = raf;
    this.ncfile = ncfile;

    // its a netcdf-3 file
    raf.order(RandomAccessFile.BIG_ENDIAN);
    headerParser = new N3header();
    headerParser.setUseRecordStructure( useRecordStructure);

    headerParser.read( raf, ncfile, null); // read header here
    numrecs = headerParser.numrecs;
    recsize = headerParser.recsize;

    _open( raf);
  }

  /////////////////////////////////////////////////////////////////////////////
  // data reading

  public Array readData(ucar.nc2.Variable v2, java.util.List sectionList) throws IOException, InvalidRangeException  {
    if (v2 instanceof Structure)
      return readRecordData( (Structure) v2, sectionList);

    N3header.Vinfo vinfo = (N3header.Vinfo) v2.getSPobject();
    DataType dataType = v2.getDataType();

    RegularIndexer index = new RegularIndexer( v2.getShape(), v2.getElementSize(), vinfo.begin, sectionList, v2.isUnlimited() ? recsize : -1);
    Object data = readData( index, dataType);
    return Array.factory( dataType.getPrimitiveClassType(), index.getWantShape(), data);
  }

  /**
   * Read data from record structure. For N3, this is the only possible structure, and there can
   *  be no nesting.
   * Read all variables for each record, for efficiency.
   *
   * @param s the record structure
   * @param sectionList the record range to read
   * @return an Array of StructureData. with all the data read in.
   *
   * @throws IOException
   * @throws InvalidRangeException
   */
  private ucar.ma2.Array readRecordData(ucar.nc2.Structure s, List sectionList) throws java.io.IOException, ucar.ma2.InvalidRangeException {

    // the result is inside an Array of StructureData
    Range recordRange = (Range) sectionList.get(0);
    Array result = Array.factory( StructureData.class, new int[] { recordRange.length()} );
    Index resultIndex = result.getIndex();

    // loop over records
    int count = 0;
    for (int recnum=recordRange.first(); recnum <= recordRange.last(); recnum += recordRange.stride()) {
      StructureData sdata = new StructureData(s);
      result.setObject( resultIndex.set(count++), sdata);

      // then loop over variables
      Iterator viter = s.getVariables().iterator();
      while (viter.hasNext()) {
        Variable v2 = (Variable) viter.next();
        N3header.Vinfo vinfo = (N3header.Vinfo) v2.getSPobject();
        Indexer index = new RegularIndexer( v2.getShape(), v2.getElementSize(), vinfo.begin+recnum*recsize, Range.factory( v2.getShape()), -1);
        Object data = readData( index, v2.getDataType());
        Array dataArray = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);

        sdata.addMember( v2, dataArray);
      }
    }

    return result;
  }

  /**
   * Read data from a Variable that is nested in the record Structure.
   *
   * @param v2 a nested Variable.
   * @param sectionList List of type Range specifying the section of data to read. There must be a Range for each
   *  Dimension in each parent, as well as in the Variable itself. Must be in order from outer to inner.
   * @param flatten if true, remove containing structures.
   * @return the requested data in a memory-resident Array
   */
  public ucar.ma2.Array readNestedData(ucar.nc2.Variable v2, java.util.List sectionList, boolean flatten)
         throws java.io.IOException, ucar.ma2.InvalidRangeException {

    if (flatten) {
      return readNestedDataFlatten(v2, sectionList);
    }

    // If flatten is false, the results are inside an Array of StructureData
    Range recordRange = (Range) sectionList.get(0);
    Array result = Array.factory( StructureData.class, new int[] { recordRange.length()} );
    Index resultIndex = result.getIndex();

    // the inner section
    ArrayList innerSection = new ArrayList( sectionList);
    innerSection.remove(0);
    int[] innerShape = Range.getShape( innerSection);

    // loop over records
    N3header.Vinfo vinfo = (N3header.Vinfo) v2.getSPobject();
    int count = 0;
    for (int recnum=recordRange.first(); recnum <= recordRange.last(); recnum += recordRange.stride()) {
      StructureData sdata = new StructureData( v2.getParentStructure());
      result.setObject( resultIndex.set(count++), sdata);

      // get the data for just this variable, in just the current record
      Indexer index = new RegularIndexer( v2.getShape(), v2.getElementSize(), vinfo.begin+recnum*recsize, innerSection, -1);
      Object data = readData( index, v2.getDataType());
      Array dataArray = Array.factory( v2.getDataType().getPrimitiveClassType(), innerShape, data);

      sdata.addMember( v2, dataArray);
    }

    return result;
  }

   /* If flatten is true, remove the Arrays of StructureData that wrap the data, and return an Array of the
    * same type as the Variable. The shape of the returned Array will be an accumulation of all the shapes of the
    * Structures containing the variable.
    * this is the same case as when we dont make the record variables into a Structure. */
  private Array readNestedDataFlatten(ucar.nc2.Variable v2, java.util.List sectionList) throws IOException, InvalidRangeException  {
    N3header.Vinfo vinfo = (N3header.Vinfo) v2.getSPobject();
    DataType dataType = v2.getDataType();

    // construct the full shape for use by RegularIndexer
    int[] varShape = v2.getShape();
    int[] fullShape = new int[ v2.getRank()+1];
    fullShape[0] = numrecs;
    for (int i=0; i<v2.getRank(); i++ ) {
      fullShape[i+1] = varShape[i];
    }

    Indexer index = new RegularIndexer( fullShape, v2.getElementSize(), vinfo.begin, sectionList, recsize);
    Object dataObject = readData( index, dataType);
    return Array.factory( dataType.getPrimitiveClassType(), Range.getShape(sectionList), dataObject);
  }

  // convert byte array to char array
  static protected char[] convertByteToChar( byte[] byteArray) {
    int size = byteArray.length;
    char[] cbuff = new char[size];
    for (int i=0; i<size; i++)
      cbuff[i] = (char) byteArray[i];
    return cbuff;
  }

   // convert char array to byte array
  static protected byte[] convertCharToByte( char[] from) {
    int size = from.length;
    byte[] to = new byte[size];
    for (int i=0; i<size; i++)
      to[i] = (byte) from[i];
    return to;
  }

  //////////////////////////////////////////////////////////////////////////////////////
  // create new file

  protected boolean fill;
  protected HashMap dimHash = new HashMap(50);

  public void create(String filename, ucar.nc2.NetcdfFile ncfile, boolean fill) throws IOException {
    this.ncfile = ncfile;
    this.fill = fill;
    this.readonly = false;

    // finish any structures
    ncfile.finish();

    raf = new ucar.unidata.io.RandomAccessFile(filename, "rw");
    raf.order(RandomAccessFile.BIG_ENDIAN);

    headerParser = new N3header();
    headerParser.create(raf, ncfile, fill, null);

    recsize = headerParser.recsize;   // record size
    recStart = headerParser.recStart; // record variables start here
    fileUsed = headerParser.recStart; // track what is actually used

    if (fill) fillerup( headerParser.dataStart, fileUsed);
    _create( raf);

    //System.out.println("createDone= "+ncfile);
  }

  //////////////////////////////////////////////////////////////////////////////////////
  // write

  public void writeData(Variable v2, java.util.List sectionList, Array values) throws java.io.IOException, InvalidRangeException {

    N3header.Vinfo vinfo = (N3header.Vinfo) v2.getSPobject();
    DataType dataType = v2.getDataType();

    if (v2.isUnlimited()) {
      Range firstRange = (Range) sectionList.get(0);
      setNumrecs( firstRange.last()+1);
    }

    Indexer index = new RegularIndexer( v2.getShape(), v2.getElementSize(), vinfo.begin, sectionList, v2.isUnlimited() ? recsize : -1);
    writeData( values, index, dataType);
  }

  protected void setNumrecs(int n) throws IOException {
    if (n <= numrecs) return;

    if (debugSize) System.out.println("extend records to = "+n);
    if (fill)
      fillerup( recStart + recsize * numrecs, recStart + recsize * n);
    fileUsed = recStart + recsize * n;

      // number of records
    headerParser.setNumrecs( n);
    this.numrecs = n;

    // need to let unlimited dimension know of new shape LOOK Allow more than one?
    Iterator iter = ncfile.getDimensions().iterator();
    while (iter.hasNext()) {
      Dimension dim = (Dimension) iter.next();
      if (dim.isUnlimited())
       dim.setLength(n);
    }

    // need to let all unlimited variables know of new shape LOOK need to do fill value!
    iter = ncfile.getVariables().iterator();
    while (iter.hasNext()) {
      Variable v = (Variable) iter.next();
      if (v.isUnlimited()) {
        v.shape[0] = n;
      }
    }
  }

  // fill buffer with fill value
  // LOOK this is wrong
  protected void fillerup( int start, int end) throws IOException {
    int len = end - start;
    byte val = (byte) 0;
    raf.seek( start);
    for (int i=0; i<len; i++)
      raf.write( val);
  }


  public void flush() throws java.io.IOException {
    raf.flush();
  }

  public void close() throws java.io.IOException {
    raf.close();
  }

  /** Debug info for this object. */
  public String toStringDebug(Object o) { return null; }


  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // stuff we need the subclass to implement

   /**
    * Read data subset from file for a variable, create primitive array.
    * @param index handles skipping around in the file.
    * @param dataType dataType of the variable
    * @return primitive array with data read in
    */
  abstract protected Object readData( Indexer index, DataType dataType) throws IOException;

   /**
    * Write data subset to file for a variable, create primitive array.
    * @param aa write data in this Array.
    * @param index handles skipping around in the file.
    * @param dataType dataType of the variable
    */
  abstract protected void writeData( Array aa, Indexer index, DataType dataType) throws IOException;

  abstract protected void _open(ucar.unidata.io.RandomAccessFile raf) throws java.io.IOException;

  abstract protected void _create(ucar.unidata.io.RandomAccessFile raf) throws java.io.IOException;


}

/* Change History:
   $Log: N3iosp.java,v $
   Revision 1.10  2004/09/22 18:44:32  caron
   move common to ucar.unidata

   Revision 1.9  2004/09/22 13:46:35  caron
   *** empty log message ***

   Revision 1.8  2004/09/09 22:47:39  caron
   station updates

   Revision 1.7  2004/08/26 17:55:10  caron
   no message

   Revision 1.6  2004/08/17 19:20:04  caron
   2.2 alpha (2)

   Revision 1.5  2004/08/17 00:09:13  caron
   *** empty log message ***

   Revision 1.4  2004/08/16 20:53:45  caron
   2.2 alpha (2)

   Revision 1.3  2004/07/12 23:40:17  caron
   2.2 alpha 1.0 checkin

   Revision 1.2  2004/07/06 19:28:10  caron
   pre-alpha checkin

   Revision 1.1.1.1  2003/12/04 21:05:27  caron
   checkin 2.2

 */