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

package ucar.nc2.dt.point;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Formatter;
import java.util.List;
import ucar.ma2.Array;
import ucar.ma2.ArrayChar;
import ucar.ma2.DataType;
import ucar.ma2.StructureData;
import ucar.nc2.Attribute;
import ucar.nc2.AttributeContainer;
import ucar.nc2.AttributeContainerMutable;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dt.DataIterator;
import ucar.nc2.dt.PointObsDataset;
import ucar.nc2.dt.PointObsDatatype;
import ucar.nc2.dt.TypedDatasetFactory;
import ucar.nc2.ft.DsgFeatureCollection;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
import ucar.nc2.ft.FeatureDatasetPoint;
import ucar.nc2.ft.PointFeature;
import ucar.nc2.ft.PointFeatureCollection;
import ucar.unidata.geoloc.EarthLocation;

/**
 * Write point obs data in CF obs format.
 *
 * @deprecated use WriterCFPointCollection
 * @author caron
 * @since Oct 22, 2008
 */
public class CFPointObWriter {
  private WriterCFPointDataset ncWriter;

  /**
   * Constructor
   *
   * @param stream write to this stream
   * @param globalAtts optional list of global attributes (may be null)
   * @param altUnits optional altitude units (set to null if no altitude variable)
   * @param dataVars the set of data variables: first the double values, then the String values
   * @throws IOException if write error
   */
  public CFPointObWriter(DataOutputStream stream, List<Attribute> globalAtts, String altUnits,
      List<PointObVar> dataVars, int numrec) throws IOException {
    ncWriter = new WriterCFPointDataset(stream, globalAtts, altUnits);

    List<VariableSimpleIF> vars = new ArrayList<VariableSimpleIF>(dataVars.size());
    for (PointObVar pvar : dataVars)
      vars.add(new PointObVarAdapter(pvar));

    ncWriter.writeHeader(vars, numrec);
  }

  /**
   * Add one data point to the file
   *
   * @param lat latitude value in units of degrees_north
   * @param lon longitude value in units of degrees_east
   * @param alt altitude value in units of altUnits (may be NaN)
   * @param time time value as a date
   * @param vals list of data values, matching dataVars in the constructor
   * @param svals list of String values, matching dataVars in the constructor
   * @throws IOException if write error
   */
  public void addPoint(double lat, double lon, double alt, Date time, double[] vals, String[] svals)
      throws IOException {
    ncWriter.writeRecord(lat, lon, alt, time, vals, svals);
  }

  /**
   * Call this when all done, output is flushed
   *
   * @throws IOException if write error
   */
  public void finish() throws IOException {
    ncWriter.finish();
  }

  private class PointObVarAdapter implements VariableSimpleIF {
    PointObVar pov;
    List<Attribute> atts = new ArrayList<Attribute>(2);

    PointObVarAdapter(PointObVar pov) {
      this.pov = pov;
    }

    public String getName() {
      return pov.getName();
    }

    public String getFullName() {
      return pov.getName();
    }

    public String getShortName() {
      return pov.getName();
    }

    public String getDescription() {
      return pov.getDesc();
    }

    public String getUnitsString() {
      return pov.getUnits();
    }

    public int getRank() {
      return (pov.getLen() > 1) ? 1 : 0;
    }

    public int[] getShape() {
      return (pov.getLen() > 1) ? new int[] {pov.getLen()} : new int[0];
    }

    public List<Dimension> getDimensions() {
      if (pov.getLen() > 1) {
        List<Dimension> dims = new ArrayList<Dimension>(1);
        String suffix =
            (pov.getDataType() == DataType.STRING) || (pov.getDataType() == DataType.CHAR) ? "_strlen" : "_len";
        dims.add(new Dimension(pov.getName() + suffix, pov.getLen(), false, false, false));
        return dims;
      } else
        return new ArrayList<Dimension>(0);
    }

    public DataType getDataType() {
      return (pov.getDataType() == DataType.STRING) ? DataType.CHAR : pov.getDataType();
    }

    @Override
    public AttributeContainer attributes() {
      return new AttributeContainerMutable(getName(), atts).toImmutable();
    }

    public List<Attribute> getAttributes() {
      if (atts == null)
        atts = new ArrayList<Attribute>(2);

      if (pov.getDesc() != null)
        atts.add(new Attribute(CDM.LONG_NAME, pov.getDesc()));
      if (pov.getUnits() != null)
        atts.add(new Attribute(CDM.UNITS, pov.getUnits()));
      return atts;
    }

    public Attribute findAttributeIgnoreCase(String name) {
      for (Attribute att : attributes())
        if (att.getShortName().equalsIgnoreCase(name))
          return att;
      return null;
    }

    public int compareTo(VariableSimpleIF o) {
      return -1;
    }
  }

  /**
   * Open a ucar.nc2.dt.PointObsDataset, write out in CF point format.
   *
   * @param fileIn open through TypedDatasetFactory.open(FeatureType.POINT, ..)
   * @param fileOut write to this netcdf-3 file
   * @param inMemory if true, read file into memory for efficiency
   * @return true on success
   * @throws IOException on read/write error
   */
  public static boolean rewritePointObsDataset(String fileIn, String fileOut, boolean inMemory) throws IOException {
    System.out.println("Rewrite2 .nc files from " + fileIn + " to " + fileOut + " inMemory= " + inMemory);

    long start = System.currentTimeMillis();

    // do it in memory for speed
    NetcdfFile ncfile = inMemory ? NetcdfFile.openInMemory(fileIn) : NetcdfFile.open(fileIn);
    NetcdfDataset ncd = new NetcdfDataset(ncfile);

    StringBuilder errlog = new StringBuilder();
    PointObsDataset pobsDataset = (PointObsDataset) TypedDatasetFactory.open(FeatureType.POINT, ncd, null, errlog);
    if (pobsDataset == null)
      return false;

    writePointObsDataset(pobsDataset, fileOut);
    pobsDataset.close();

    long took = System.currentTimeMillis() - start;
    System.out.println(" that took " + (took - start) + " msecs");
    return true;
  }

  /**
   * write data from a ucar.nc2.dt.PointObsDataset into CF point format.
   *
   * @param pobsDataset rewrite data from here
   * @param fileOut write to tehis netcdf-3 file
   * @throws IOException on read/write error
   */
  public static void writePointObsDataset(PointObsDataset pobsDataset, String fileOut) throws IOException {

    // see if we have an altitude
    String altUnits = null;
    DataIterator iterOne = pobsDataset.getDataIterator(-1);
    while (iterOne.hasNext()) {
      PointObsDatatype pobsData = (PointObsDatatype) iterOne.nextData();
      ucar.unidata.geoloc.EarthLocation loc = pobsData.getLocation();
      altUnits = Double.isNaN(loc.getAltitude()) ? null : "meters";
      break;
    }

    List<VariableSimpleIF> vars = pobsDataset.getDataVariables();
    List<PointObVar> nvars = new ArrayList<PointObVar>(vars.size());

    // put vars in order
    for (VariableSimpleIF v : vars) {
      if (v.getDataType().isNumeric())
        nvars.add(new PointObVar(v));
    }
    int ndoubles = vars.size();
    double[] dvals = new double[ndoubles];

    for (VariableSimpleIF v : vars) {
      if (v.getDataType().isString())
        nvars.add(new PointObVar(v));
    }
    String[] svals = new String[vars.size() - ndoubles];

    FileOutputStream fos = new FileOutputStream(fileOut);
    DataOutputStream out = new DataOutputStream(fos);
    CFPointObWriter writer = new CFPointObWriter(out, pobsDataset.getGlobalAttributes(), altUnits, nvars, -1);

    DataIterator iter = pobsDataset.getDataIterator(1000 * 1000);
    while (iter.hasNext()) {
      PointObsDatatype pobsData = (PointObsDatatype) iter.nextData();
      StructureData sdata = pobsData.getData();

      int dcount = 0;
      int scount = 0;
      for (PointObVar v : nvars) {
        if (v.getDataType().isNumeric()) {
          Array data = sdata.getArray(v.getName());
          data.resetLocalIterator();
          if (data.hasNext())
            dvals[dcount++] = data.nextDouble();

        } else if (v.getDataType().isString()) {
          ArrayChar data = (ArrayChar) sdata.getArray(v.getName());
          svals[scount++] = data.getString();
        }
      }

      ucar.unidata.geoloc.EarthLocation loc = pobsData.getLocation();
      writer.addPoint(loc.getLatitude(), loc.getLongitude(), loc.getAltitude(), pobsData.getObservationTimeAsDate(),
          dvals, svals);
    }

    writer.finish();
  }

  /**
   * Open a ucar.nc2.ft.PointFeatureCollection, write out in CF point format.
   *
   * @param fileIn open through TypedDatasetFactory.open(FeatureType.POINT, ..)
   * @param fileOut write to this netcdf-3 file
   * @param inMemory if true, read file into memory for efficiency
   * @return true on success
   * @throws IOException on read/write error
   */
  public static boolean rewritePointFeatureDataset(String fileIn, String fileOut, boolean inMemory) throws IOException {
    System.out.println("Rewrite2 .nc files from " + fileIn + " to " + fileOut + " inMemory= " + inMemory);

    long start = System.currentTimeMillis();

    // do it in memory for speed
    NetcdfFile ncfile = inMemory ? NetcdfFile.openInMemory(fileIn) : NetcdfFile.open(fileIn);
    NetcdfDataset ncd = new NetcdfDataset(ncfile);

    Formatter errlog = new Formatter();
    FeatureDataset fd = FeatureDatasetFactoryManager.wrap(FeatureType.ANY_POINT, ncd, null, errlog);
    if (fd == null)
      return false;

    if (fd instanceof FeatureDatasetPoint) {
      writePointFeatureCollection((FeatureDatasetPoint) fd, fileOut);
      fd.close();
      long took = System.currentTimeMillis() - start;
      System.out.println(" that took " + (took - start) + " msecs");
      return true;
    }

    return false;

  }

  /**
   * Write a ucar.nc2.ft.PointFeatureCollection in CF point format.
   *
   * @param pfDataset find the first PointFeatureCollection, and write all data from it
   * @param fileOut write to this netcdf-3 file
   * @return number of records written
   * @throws IOException on read/write error, or if no PointFeatureCollection in pfDataset
   */
  public static int writePointFeatureCollection(FeatureDatasetPoint pfDataset, String fileOut) throws IOException {
    // extract the PointFeatureCollection
    PointFeatureCollection pointFeatureCollection = null;
    List<DsgFeatureCollection> featureCollectionList = pfDataset.getPointFeatureCollectionList();
    for (DsgFeatureCollection featureCollection : featureCollectionList) {
      if (featureCollection instanceof PointFeatureCollection)
        pointFeatureCollection = (PointFeatureCollection) featureCollection;
    }
    if (null == pointFeatureCollection)
      throw new IOException("There is no PointFeatureCollection in  " + pfDataset.getLocation());

    long start = System.currentTimeMillis();

    FileOutputStream fos = new FileOutputStream(fileOut);
    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos, 10000));
    WriterCFPointDataset writer = null;

    /*
     * LOOK BAD
     * List<VariableSimpleIF> dataVars = new ArrayList<VariableSimpleIF>();
     * ucar.nc2.NetcdfFile ncfile = pfDataset.getNetcdfFile();
     * if ((ncfile == null) || !(ncfile instanceof NetcdfDataset)) {
     * dataVars.addAll(pfDataset.getDataVariables());
     * } else {
     * NetcdfDataset ncd = (NetcdfDataset) ncfile;
     * for (VariableSimpleIF vs : pfDataset.getDataVariables()) {
     * if (ncd.findCoordinateAxis(vs.getName()) == null)
     * dataVars.add(vs);
     * }
     * }
     */

    int count = 0;
    for (PointFeature pointFeature : pointFeatureCollection) {
      StructureData data = pointFeature.getDataAll();
      if (count == 0) {
        EarthLocation loc = pointFeature.getLocation(); // LOOK we dont know this until we see the obs
        String altUnits = Double.isNaN(loc.getAltitude()) ? null : "meters"; // LOOK units may be wrong
        writer = new WriterCFPointDataset(out, pfDataset.getGlobalAttributes(), altUnits);
        writer.writeHeader(pfDataset.getDataVariables(), -1);
      }
      writer.writeRecord(pointFeature, data);
      count++;
    }

    writer.finish();
    out.flush();
    out.close();

    long took = System.currentTimeMillis() - start;
    System.out.printf("Write %d records from %s to %s took %d msecs %n", count, pfDataset.getLocation(), fileOut, took);
    return count;
  }

  public static void main2(String args[]) throws IOException {
    String location = "R:/testdata/point/netcdf/madis.nc";
    File file = new File(location);
    rewritePointFeatureDataset(location, "C:/TEMP/" + file.getName(), true);
  }

  public static void main(String args[]) throws IOException {

    List<PointObVar> dataVars = new ArrayList<PointObVar>();
    dataVars.add(new PointObVar("test1", "units1", "desc1", DataType.CHAR, 4));
    dataVars.add(new PointObVar("test2", "units2", "desc3", DataType.CHAR, 4));

    // public CFPointObWriter(DataOutputStream stream, List<Attribute> globalAtts, String altUnits, List<PointObVar>
    // dataVars) throws IOException {

    FileOutputStream fos = new FileOutputStream("C:/temp/test.out");
    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos, 10000));

    CFPointObWriter writer = new CFPointObWriter(out, new ArrayList<Attribute>(), "meters", dataVars, 1);

    double[] dvals = new double[0];
    String[] svals = new String[] {"valu", "value"};
    writer.addPoint(1.0, 2.0, 3.0, new Date(), dvals, svals);
    writer.finish();

  }
}
