/*
 * Copyright 1998-2015 University Corporation for Atmospheric Research/Unidata
 *
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package ucar.nc2.iosp.misc;

import ucar.nc2.IOServiceProvider;
import ucar.nc2.*;
import ucar.nc2.dataset.conv._Coordinate;
import ucar.nc2.dataset.AxisType;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import ucar.ma2.*;

import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Date;
import java.text.ParseException;

/**
 * UspLightning1
 *
 * @author caron
 */
public class UspLightning1 implements IOServiceProvider {

/*  USPLN data format:

Each 1 minute packet sent has an ASCII header, followed by a record for
each lightning detection during the past 1 minute.

Header
The ASCII header provides information on the creation time of the one
minute packet and ending date and time of the file.

Sample Header:
USPLN-LIGHTNING,2004-10-11T20:45:02,2004-10-11T20:45:02
Description:
Name of Product: USPLN-LIGHTNING
Creation of 1 min Packet (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:45:02
Ending of 1 min Packet (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:45:02

NOTE: All times in UTC

Strike Record Following the header, an individual record is provided for
each lightning strike in a comma delimited format.

Sample Strike Records:
2004-10-11T20:44:02,32.6785331,-105.4344587,-96.1,1
2004-10-11T20:44:05,21.2628231,-86.9596634,53.1,1
2004-10-11T20:44:05,21.2967119,-86.9702106,50.3,1
2004-10-11T20:44:06,19.9044769,-100.7082608,43.1,1
2004-10-11T20:44:11,21.4523434,-82.5202274,-62.8,1
2004-10-11T20:44:11,21.8155306,-82.6708778,80.9,1

Description:

Strike Date/Time (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:44:02

Strike Latitude (deg): 32.6785331
Strike Longitude (deg): -105.4344587
Strike Amplitude (kAmps, see note below): -96.1
Stroke Count (number of strokes per flash): 1

Note: At the present time USPLN data are only provided in stroke format,
so the stroke count will always be 1.

Notes about Decoding Strike Amplitude
The amplitude field is utilized to indicate the amplitude of strokes and
polarity of strokes for all Cloud-to- Ground Strokes.

For other types of detections this field is utilized to provide
information on the type of stroke detected.

The amplitude number for Cloud-to-Ground strokes provides the amplitude
of the stroke and the sign (+/-) provides the polarity of the stroke.

An amplitude of 0 indicates USPLN Cloud Flash Detections rather than
Cloud-to-Ground detections.

Cloud flash detections include cloud-to-cloud, cloud-to-air, and
intra-cloud flashes.

An amplitude of -999 or 999 indicates a valid cloud-to-ground stroke
detection in which an amplitude was not able to be determined. Typically
these are long-range detections.
*/

  private static final String MAGIC = "USPLN-LIGHTNING";

  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    raf.seek(0);
    int n = MAGIC.length();
    byte[] b = new byte[n];
    raf.read(b);
    String got = new String(b);
    return got.equals(MAGIC);
  }

  private ArrayInt.D1 dateArray;
  private ArrayDouble.D1 latArray;
  private ArrayDouble.D1 lonArray;
  private ArrayDouble.D1 ampArray;
  private ArrayInt.D1 nstrokesArray;

  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    int n;

    try {
      n = readAllData(raf);
    } catch (ParseException e) {
      e.printStackTrace();
      throw new IOException("bad data");
    }
    raf.close();

    Dimension recordDim = new Dimension("record", n, true);
    ncfile.addDimension(null, recordDim);

    Variable date = new Variable(ncfile, null, null, "date");
    date.setDimensions("record");
    date.setDataType(DataType.INT);
    String timeUnit = "seconds since 1970-01-01 00:00:00";
    date.addAttribute(new Attribute("long_name", "date of strike"));
    date.addAttribute(new Attribute("units", timeUnit));
    date.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));
    date.setCachedData(dateArray, false);
    ncfile.addVariable(null, date);

    Variable lat = new Variable(ncfile, null, null, "lat");
    lat.setDimensions("record");
    lat.setDataType(DataType.DOUBLE);
    lat.addAttribute(new Attribute("long_name", "latitude"));
    lat.addAttribute(new Attribute("units", "degrees_north"));
    lat.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
    lat.setCachedData(latArray, false);
    ncfile.addVariable(null, lat);

    Variable lon = new Variable(ncfile, null, null, "lon");
    lon.setDimensions("record");
    lon.setDataType(DataType.DOUBLE);
    lon.addAttribute(new Attribute("long_name", "longitude"));
    lon.addAttribute(new Attribute("units", "degrees_east"));
    lon.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
    lon.setCachedData(lonArray, false);
    ncfile.addVariable(null, lon);

    Variable amp = new Variable(ncfile, null, null, "strikeAmplitude");
    amp.setDimensions("record");
    amp.setDataType(DataType.DOUBLE);
    amp.addAttribute(new Attribute("long_name", "amplitude of strike"));
    amp.addAttribute(new Attribute("units", "kAmps"));
    amp.addAttribute(new Attribute("missing_value", new Double(999)));
    amp.setCachedData(ampArray, false);
    ncfile.addVariable(null, amp);

    Variable nstrokes = new Variable(ncfile, null, null, "strokeCount");
    nstrokes.setDimensions("record");
    nstrokes.setDataType(DataType.INT);
    nstrokes.addAttribute(new Attribute("long_name", "number of strokes per flash"));
    nstrokes.addAttribute(new Attribute("units", ""));
    nstrokes.setCachedData(nstrokesArray, false);
    ncfile.addVariable(null, nstrokes);

    ncfile.addAttribute(null, new Attribute("title", "USPN Lightning Data"));
    ncfile.addAttribute(null, new Attribute("history", "Read directly by Netcdf Java IOSP"));

    ncfile.addAttribute(null, new Attribute("Conventions", "Unidata Observation Dataset v1.0"));
    ncfile.addAttribute(null, new Attribute("cdm_data_type", "Point"));
    ncfile.addAttribute(null, new Attribute("observationDimension", "record"));

    MAMath.MinMax mm = MAMath.getMinMax(dateArray);
    ncfile.addAttribute(null, new Attribute("time_coverage_start", ((int) mm.min) + " " + timeUnit));
    ncfile.addAttribute(null, new Attribute("time_coverage_end", ((int) mm.max) + " " + timeUnit));

    mm = MAMath.getMinMax(latArray);
    ncfile.addAttribute(null, new Attribute("geospatial_lat_min", new Double(mm.min)));
    ncfile.addAttribute(null, new Attribute("geospatial_lat_max", new Double(mm.max)));

    mm = MAMath.getMinMax(lonArray);
    ncfile.addAttribute(null, new Attribute("geospatial_lon_min", new Double(mm.min)));
    ncfile.addAttribute(null, new Attribute("geospatial_lon_max", new Double(mm.max)));

    ncfile.finish();
  }

  // 2006-10-23T17:59:39,18.415434,-93.480526,-26.8,1
  int readAllData(RandomAccessFile raf) throws IOException, NumberFormatException, ParseException {
    ArrayList records = new ArrayList();

    java.text.SimpleDateFormat isoDateTimeFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    isoDateTimeFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));

    //java.io.RandomAccessFile jraf = raf.getRandomAccessFile();

    raf.seek(0);
    int count = 0;
    while (true) {
      String line = raf.readLine();
      if (line == null) break;
      if (line.startsWith(MAGIC)) continue;

      StringTokenizer stoker = new StringTokenizer(line, ",\r\n");
      while (stoker.hasMoreTokens()) {
        Date d = isoDateTimeFormat.parse(stoker.nextToken());
        double lat = Double.parseDouble(stoker.nextToken());
        double lon = Double.parseDouble(stoker.nextToken());
        double amp = Double.parseDouble(stoker.nextToken());
        String tok = stoker.nextToken();
        int nstrikes = Integer.parseInt(tok);

        Strike s = new Strike(d, lat, lon, amp, nstrikes);
        records.add(s);
        if (count < 10)
          System.out.println(count + " " + isoDateTimeFormat.format(d) + " " + s);
      }

      count++;
    }

    System.out.println("processed " + count + " records");

    int n = records.size();
    int[] shape = new int[]{n};
    dateArray = (ArrayInt.D1) Array.factory(DataType.INT, shape);
    latArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    lonArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    ampArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    nstrokesArray = (ArrayInt.D1) Array.factory(DataType.INT, shape);

    for (int i = 0; i < records.size(); i++) {
      Strike strike = (Strike) records.get(i);
      dateArray.set(i, strike.d);
      latArray.set(i, strike.lat);
      lonArray.set(i, strike.lon);
      ampArray.set(i, strike.amp);
      nstrokesArray.set(i, strike.n);
    }

    return n;
  }

  private class Strike {
    int d;
    double lat, lon, amp;
    int n;

    Strike(Date d, double lat, double lon, double amp, int n) {
      this.d = (int) (d.getTime() / 1000);
      this.lat = lat;
      this.lon = lon;
      this.amp = amp;
      this.n = n;
    }

    public String toString() {
      return lat + " " + lon + " " + amp + " " + n;
    }

  }

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

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

  public Array readNestedData(Variable v2, List section) throws IOException, InvalidRangeException {
    return null;
  }

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

  public boolean syncExtend() throws IOException {
    return false;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public boolean sync() throws IOException {
    return false;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public void setSpecial(Object special) {
    //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.
  }

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

  public static void main(String args[]) throws IOException, IllegalAccessException, InstantiationException {
    NetcdfFile.registerIOProvider(UspLightning1.class);
    NetcdfFile ncfile = NetcdfFile.open("R:/testdata/lightning/uspln/uspln_20061023.18");
    System.out.println("ncfile = \n" + ncfile);
  }

}
