// $Id: Nexrad2IOServiceProvider.java,v 1.6 2004/12/07 01:29:31 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, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2.iosp.nexrad2;

import ucar.nc2.*;
import ucar.nc2.units.DateUnit;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.IndexIterator;
import ucar.ma2.Range;

import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;

/**
 * An IOServiceProvider for NEXRAD level II files.
 *
 * @author caron
 */
public class Nexrad2IOServiceProvider implements IOServiceProvider {

  public boolean isValidFile( RandomAccessFile raf) {
    try {
      raf.seek(0);
      byte[] b = new byte[8];
      raf.read(b);
      String test = new String( b);
      return test.equals( Level2VolumeScan.ARCHIVE2) || test.equals( Level2VolumeScan.AR2V0001) ;
    } catch (IOException ioe) {
      return false;
    }
  }

  private Level2VolumeScan volScan;
  private Dimension radialDim;

  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    volScan = new Level2VolumeScan( raf, cancelTask);
    radialDim = new Dimension("radial", Level2Record.MAX_RADIALS, true);
    ncfile.addDimension( null, radialDim);

    makeVariable( ncfile, Level2Record.REFLECTIVITY, "Reflectivity", "Reflectivity in dbZ", "R", volScan.getReflectivityGroups());
    Variable v = makeVariable( ncfile, Level2Record.VELOCITY, "Velocity", "Dopplar Velocity", "V", volScan.getVelocityGroups());
    makeVariableNoCoords( ncfile, Level2Record.SPECTRUM_WIDTH, "SpectrumWidth", "Dopplar Spectrum Width", v);

    if (volScan.getStationId() != null) {
      ncfile.addAttribute(null, new Attribute("Station", volScan.getStationId()));
      ncfile.addAttribute(null, new Attribute("StationName", volScan.getStationName()));
      ncfile.addAttribute(null, new Attribute("StationLatitude", new Double(volScan.getStationLatitude())));
      ncfile.addAttribute(null, new Attribute("StationLongitude", new Double(volScan.getStationLongitude())));
      ncfile.addAttribute(null, new Attribute("StationElevationInMeters", new Double(volScan.getStationElevation())));

          // add a radial coordinate transform (experimental)
      Variable ct = new Variable(ncfile, null, null, "radialCoordinateTransform");
      ct.setDataType(DataType.CHAR);
      ct.setDimensions(""); // scalar
      ct.addAttribute( new Attribute("transform_type", "Radial"));
      ct.addAttribute( new Attribute("center_latitude", new Double(volScan.getStationLatitude())));
      ct.addAttribute( new Attribute("center_longitude", new Double(volScan.getStationLongitude())));
      ct.addAttribute( new Attribute("center_elevation", new Double(volScan.getStationElevation())));
      ct.addAttribute( new Attribute("_CoordinateTransformType", "Radial"));
      ct.addAttribute( new Attribute("_CoordinateAxes", "elevationR azimuthR distanceR"));

      Array data = Array.factory(DataType.CHAR.getPrimitiveClassType(), new int[0], new char[] {' '});
      ct.setCachedData(data, true);
      ncfile.addVariable(null, ct);
    }

    ncfile.addAttribute(null, new Attribute("format", volScan.getDataFormat()));
    Date d = Level2Record.getDate(volScan.getTitleJulianDays(), volScan.getTitleMsecs());
    ncfile.addAttribute(null, new Attribute("base_date", DateUnit.getStandardDateOnlyString(d)));

    ncfile.addAttribute(null, new Attribute("start_datetime", DateUnit.getStandardDateString(volScan.getStartDate())));
    ncfile.addAttribute(null, new Attribute("end_datetime", DateUnit.getStandardDateString(volScan.getEndDate())));

    ncfile.addAttribute(null, new Attribute("VolumeCoveragePatternName",
        Level2Record.getVolumeCoveragePatternName(volScan.getVCP())));
    ncfile.addAttribute(null, new Attribute("VolumeCoveragePattern", new Integer(volScan.getVCP())));

    ncfile.addAttribute(null, new Attribute("Processing", "direct read of Nexrad Level 2 file into NetCDF-Java 2.2 API"));
    ncfile.addAttribute(null, new Attribute("DataType", "RADAR"));
    ncfile.addAttribute(null, new Attribute("Title", "Nexrad Level 2 Station "+volScan.getStationId()+" from "+
        DateUnit.getStandardDateString(volScan.getStartDate()) + " to " +
        DateUnit.getStandardDateString(volScan.getEndDate())));

    // done
    ncfile.finish();
  }

  public Variable makeVariable(NetcdfFile ncfile, int datatype, String shortName, String longName, String abbrev, List groups) throws IOException {
    int nscans = groups.size();

    // get representative record
    List firstGroup =  (List) groups.get(0);
    Level2Record firstRecord = (Level2Record) firstGroup.get(0);
    int ngates = firstRecord.getGateCount(datatype);

    String scanDimName = "scan"+abbrev;
    String gateDimName = "gate"+abbrev;
    Dimension scanDim = new Dimension(scanDimName, nscans, true);
    Dimension gateDim = new Dimension(gateDimName, ngates, true);
    ncfile.addDimension( null, scanDim);
    ncfile.addDimension( null, gateDim);

    ArrayList dims = new ArrayList();
    dims.add( scanDim);
    dims.add( radialDim);
    dims.add( gateDim);

    Variable v = new Variable(ncfile, null, null, shortName);
    v.setDataType(DataType.FLOAT);
    v.setDimensions(dims);
    ncfile.addVariable(null, v);

    v.addAttribute( new Attribute("units", Level2Record.getDatatypeUnits(datatype)));
    v.addAttribute( new Attribute("long_name", longName));
    v.addAttribute( new Attribute("missing_value", new Float(Float.NaN)));

    ArrayList dim2 = new ArrayList();
    dim2.add( scanDim);
    dim2.add( radialDim);

    // add elevation coordinate variable
    String timeCoordName = "time"+abbrev;
    Variable timeVar = new Variable(ncfile, null, null, timeCoordName);
    timeVar.setDataType(DataType.INT);
    timeVar.setDimensions(dim2);
    ncfile.addVariable(null, timeVar);

    timeVar.addAttribute( new Attribute("long_name", "time since base date"));
    timeVar.addAttribute( new Attribute("units", Level2Record.getUdunitString(volScan.getTitleJulianDays())));

    // add elevation coordinate variable
    String elevCoordName = "elevation"+abbrev;
    Variable elevVar = new Variable(ncfile, null, null, elevCoordName);
    elevVar.setDataType(DataType.FLOAT);
    elevVar.setDimensions(dim2);
    ncfile.addVariable(null, elevVar);

    elevVar.addAttribute( new Attribute("units", "degrees"));
    elevVar.addAttribute( new Attribute("long_name", "elevation angle in degres: 0 = parallel to pedestal base, 90 = perpendicular"));

    // add azimuth coordinate variable
    String aziCoordName = "azimuth"+abbrev;
    Variable aziVar = new Variable(ncfile, null, null, aziCoordName);
    aziVar.setDataType(DataType.FLOAT);
    aziVar.setDimensions(dim2);
    ncfile.addVariable(null, aziVar);

    aziVar.addAttribute( new Attribute("units", "degrees"));
    aziVar.addAttribute( new Attribute("long_name", "azimuth angle in degrees: 0 = true north, 90 = east"));

    // add gate coordinate variable
    String gateCoordName = "distance"+abbrev;
    Variable gateVar = new Variable(ncfile, null, null, gateCoordName);
    gateVar.setDataType(DataType.FLOAT);
    gateVar.setDimensions(gateDimName);
    Array data = NetcdfDataset.makeArray( DataType.FLOAT, ngates,
        (double) firstRecord.getGateStart(datatype), (double) firstRecord.getGateSize(datatype));
    gateVar.setCachedData( data, false);
    ncfile.addVariable(null, gateVar);

    gateVar.addAttribute( new Attribute("units", "m"));
    gateVar.addAttribute( new Attribute("long_name", "radial distance to start of gate"));

    makeCoordinateData( timeVar, elevVar, aziVar, groups);

    // back to the data variable
    String coordinates = timeCoordName+" "+elevCoordName +" "+ aziCoordName+" "+gateCoordName;
    v.addAttribute( new Attribute("_CoordinateAxes", coordinates));

    // make the record map
    int nradials = radialDim.getLength();
    Level2Record[][] map = new Level2Record[nscans][nradials];
    for (int i = 0; i < groups.size(); i++) {
      Level2Record[] mapScan = map[i];
      List group = (List) groups.get(i);
      for (int j = 0; j < group.size(); j++) {
        Level2Record r =  (Level2Record) group.get(j);
        int radial = r.radial_num-1;
        mapScan[radial] = r;
      }
    }

    Vgroup vg = new Vgroup(datatype, map);
    v.setSPobject( vg);

    return v;
   }

  private void makeVariableNoCoords(NetcdfFile ncfile, int datatype, String shortName, String longName, Variable from) throws IOException {

    Variable v = new Variable(ncfile, null, null, shortName);
    v.setDataType(DataType.FLOAT);
    v.setDimensions( from.getDimensions());
    ncfile.addVariable(null, v);

    Attribute fromAtt = from.findAttribute("_CoordinateAxes");
    v.addAttribute( new Attribute("units", Level2Record.getDatatypeUnits(datatype)));
    v.addAttribute( new Attribute("long_name", longName));
    v.addAttribute( new Attribute("missing_value", new Float(Float.NaN)));
    v.addAttribute( new Attribute("_CoordinateAxes", fromAtt));

    Vgroup vgFrom = (Vgroup) from.getSPobject();
    Vgroup vg = new Vgroup(datatype, vgFrom.map);
    v.setSPobject( vg);
  }

  private void makeCoordinateData(Variable time, Variable elev, Variable azi, List groups) {
    Array timeData = Array.factory( time.getDataType().getPrimitiveClassType(), time.getShape());
    IndexIterator timeDataIter = timeData.getIndexIterator();

    Array elevData = Array.factory( elev.getDataType().getPrimitiveClassType(), elev.getShape());
    IndexIterator elevDataIter = elevData.getIndexIterator();

    Array aziData = Array.factory( azi.getDataType().getPrimitiveClassType(), azi.getShape());
    IndexIterator aziDataIter = aziData.getIndexIterator();

    int last_msecs = Integer.MIN_VALUE;
    for (int i = 0; i < groups.size(); i++) {
      List group = (List) groups.get(i);
      for (int j = 0; j < group.size(); j++) {
        Level2Record r =  (Level2Record) group.get(j);
        timeDataIter.setIntNext( r.data_msecs);
        elevDataIter.setFloatNext( r.getElevation());
        aziDataIter.setFloatNext( r.getAzimuth());

        // debugg
        if (r.data_msecs < last_msecs) System.out.println("this = "+r.data_msecs+" last= "+last_msecs);
        last_msecs = r.data_msecs;
      }
    }

    time.setCachedData( timeData, false);
    elev.setCachedData( elevData, false);
    azi.setCachedData( aziData, false);
  }

  public Array readData(Variable v2, List section) throws IOException, InvalidRangeException {
    Vgroup vgroup = (Vgroup) v2.getSPobject();

    Range scanRange = (Range) section.get(0);
    Range radialRange = (Range) section.get(1);
    Range gateRange = (Range) section.get(2);

    Array data = Array.factory(v2.getDataType().getPrimitiveClassType(), Range.getShape( section));
    IndexIterator ii = data.getIndexIterator();

    for (int i=scanRange.first(); i<=scanRange.last(); i+= scanRange.stride()) {
      Level2Record[] mapScan = vgroup.map[i];
      readOneScan(mapScan, radialRange, gateRange, vgroup.datatype, ii);
    }

    return data;
  }

  private void readOneScan(Level2Record[] mapScan, Range radialRange, Range gateRange, int datatype, IndexIterator ii) throws IOException {
    for (int i=radialRange.first(); i<=radialRange.last(); i+= radialRange.stride()) {
      Level2Record r = mapScan[i];
      readOneRadial(r, datatype, gateRange, ii);
    }
  }

  private void readOneRadial(Level2Record r, int datatype, Range gateRange, IndexIterator ii) throws IOException {
    if (r == null) {
      for (int i=gateRange.first(); i<=gateRange.last(); i+= gateRange.stride())
        ii.setFloatNext( Float.NaN);
      return;
    }
    r.readData(volScan.raf, datatype, gateRange, ii);
  }

  private class Vgroup {
    Level2Record[][] map;
    int datatype;

    Vgroup( int datatype, Level2Record[][] map) {
      this.datatype = datatype;
      this.map = map;
    }
  }

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

  public Array readNestedData(Variable v2, List section, boolean flatten) throws IOException, InvalidRangeException {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public void create(String filename, NetcdfFile ncfile, boolean fill) throws IOException {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  public void writeData(Variable v2, List section, Array values) throws IOException, InvalidRangeException {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  public void flush() throws IOException {
    //To change body of implemented methods use File | Settings | File Templates.
  }

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

  public void setProperty(String name, String value) {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  public String toStringDebug(Object o) {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
  }
}

/* Change History:
   $Log: Nexrad2IOServiceProvider.java,v $
   Revision 1.6  2004/12/07 01:29:31  caron
   redo convention parsing, use _Coordinate encoding.

   Revision 1.5  2004/11/16 23:35:38  caron
   no message

   Revision 1.4  2004/11/04 00:38:18  caron
   no message

   Revision 1.3  2004/10/23 21:36:11  caron
   no message

   Revision 1.2  2004/10/22 01:01:40  caron
   another round

   Revision 1.1  2004/10/20 23:23:14  caron
   add nexrad2 iosp

*/