/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.dt.radial;

import ucar.nc2.dt.*;
import ucar.nc2.dataset.*;
import ucar.nc2.constants.*;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.units.DateUnit;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.Variable;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.ma2.*;
import java.io.IOException;
import java.util.*;
import ucar.unidata.geoloc.EarthLocation;

public class NsslRadialAdapter extends AbstractRadialAdapter {
  private NetcdfDataset ds;
  private boolean isVolume;
  private double latv, lonv, elev;

  /////////////////////////////////////////////////
  public Object isMine(FeatureType wantFeatureType, NetcdfDataset ncd, Formatter errlog) {
    String format = ncd.getRootGroup().findAttributeString("format", null);
    if (format != null) {
      if (format.startsWith("nssl/netcdf"))
        return this;
    }

    Dimension az = ncd.findDimension("Azimuth");
    Dimension gt = ncd.findDimension("Gate");
    if ((null != az) && (null != gt)) {
      return this;
    }
    return null;
  }

  public FeatureDataset open(FeatureType ftype, NetcdfDataset ncd, Object analysis, ucar.nc2.util.CancelTask task,
      Formatter errlog) {
    return new NsslRadialAdapter(ncd);
  }

  public FeatureType getScientificDataType() {
    return FeatureType.RADIAL;
  }

  // needed for FeatureDatasetFactory
  public NsslRadialAdapter() {}

  /**
   * Constructor.
   *
   * @param ds must be from netcdf IOSP
   */
  public NsslRadialAdapter(NetcdfDataset ds) {
    super(ds);
    this.ds = ds;
    desc = "Netcdf/NCML 2 radar dataset";

    setEarthLocation();
    try {
      setTimeUnits();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    setStartDate();
    setEndDate();
    setBoundingBox();


    try {
      addCoordSystem(ds);
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

  private void addCoordSystem(NetcdfDataset ds) throws IOException {
    double ele = 0;
    Attribute attr = ds.findGlobalAttributeIgnoreCase("Elevation");
    if (attr != null)
      ele = attr.getNumericValue().doubleValue();

    // ncml agg add this sweep variable as agg dimension
    Variable sp = ds.findVariable("sweep");

    if (sp == null) {
      // add Elevation
      ds.addDimension(null, new Dimension("Elevation", 1));
      String lName = "elevation angle in degres: 0 = parallel to pedestal base, 90 = perpendicular";
      CoordinateAxis v = new CoordinateAxis1D(ds, null, "Elevation", DataType.DOUBLE, "Elevation", "degrees", lName);
      ds.setValues(v, 1, ele, 0);
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RadialElevation.toString()));
      ds.addVariable(null, v);

    } else {
      Array spdata = sp.read();
      float[] spd = (float[]) spdata.get1DJavaArray(float.class);
      int spsize = spd.length;

      // add Elevation
      ds.addDimension(null, new Dimension("Elevation", spsize));
      String lName = "elevation angle in degres: 0 = parallel to pedestal base, 90 = perpendicular";
      CoordinateAxis v = new CoordinateAxis1D(ds, null, "Elevation", DataType.DOUBLE, "Elevation", "degrees", lName);
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RadialElevation.toString()));
      ds.addVariable(null, v);


    }
    ds.addAttribute(null, new Attribute("IsRadial", 1));
    attr = ds.findGlobalAttributeIgnoreCase("vcp-value");
    String vcp;
    if (attr == null)
      vcp = "11";
    else
      vcp = attr.getStringValue();

    ds.addAttribute(null, new Attribute("VolumeCoveragePatternName", vcp));
    ds.finish();
  }

  public ucar.unidata.geoloc.EarthLocation getCommonOrigin() {
    return origin;
  }

  public String getRadarID() {
    Attribute ga = ds.findGlobalAttribute("radarName-value");
    if (ga != null)
      return ga.getStringValue();
    else
      return "XXXX";
  }

  public boolean isStationary() {
    return true;
  }

  public String getRadarName() {
    return ds.findGlobalAttribute("ProductStationName").getStringValue();
  }

  public String getDataFormat() {
    return "NetCDF Level II";
  }

  public void setIsVolume(NetcdfDataset nds) {
    String format = nds.getRootGroup().findAttributeString("volume", null);
    if (format == null) {
      isVolume = false;
      return;
    }
    isVolume = format.equals("true");
  }

  public boolean isVolume() {
    return isVolume;
  }


  protected void setEarthLocation() {
    Attribute ga = ds.findGlobalAttribute("Latitude");
    if (ga != null)
      latv = ga.getNumericValue().doubleValue();
    else
      latv = 0.0;

    ga = ds.findGlobalAttribute("Longitude");
    if (ga != null)
      lonv = ga.getNumericValue().doubleValue();
    else
      lonv = 0.0;

    ga = ds.findGlobalAttribute("Height");
    if (ga != null)
      elev = ga.getNumericValue().doubleValue();
    else
      elev = 0.0;

    origin = EarthLocation.create(latv, lonv, elev);
  }

  protected void setTimeUnits() throws Exception {
    for (CoordinateAxis axis : ds.getCoordinateAxes()) {
      if (axis.getAxisType() == AxisType.Time) {
        String units = axis.getUnitsString();
        dateUnits = new DateUnit(units);
        calDateUnits = CalendarDateUnit.of(null, units);
        return;
      }
    }
    parseInfo.append("*** Time Units not Found\n");
  }

  protected void setStartDate() {
    String start_datetime = ds.getRootGroup().findAttributeString("time_coverage_start", null);
    if (start_datetime != null)
      startDate = DateUnit.getStandardOrISO(start_datetime);
    else
      parseInfo.append("*** start_datetime not Found\n");
  }

  protected void setEndDate() {
    String end_datetime = ds.getRootGroup().findAttributeString("time_coverage_end", null);
    if (end_datetime != null)
      endDate = DateUnit.getStandardOrISO(end_datetime);
    else
      parseInfo.append("*** end_datetime not Found\n");
  }

  protected void addRadialVariable(NetcdfDataset nds, Variable var) {
    RadialVariable rsvar = null;
    int rnk = var.getRank();

    setIsVolume(nds);

    if (isVolume && rnk == 3) {
      rsvar = makeRadialVariable(nds, var);
    } else if (!isVolume && rnk == 2) {
      rsvar = makeRadialVariable(nds, var);
    }

    if (rsvar != null)
      dataVariables.add(rsvar);
  }

  protected RadialVariable makeRadialVariable(NetcdfDataset nds, Variable v0) {
    return new Netcdf2Variable(nds, v0);
  }


  public void clearDatasetMemory() {
    for (VariableSimpleIF rvar : getDataVariables()) {
      RadialVariable radVar = (RadialVariable) rvar;
      radVar.clearVariableMemory();
    }
  }

  public String getInfo() {
    String sbuff = "Netcdfs2Dataset\n" + super.getDetailInfo() + "\n\n" + parseInfo;
    return sbuff;
  }

  private class Netcdf2Variable extends MyRadialVariableAdapter implements RadialDatasetSweep.RadialVariable {
    ArrayList<Netcdf2Sweep> sweeps;
    int nsweeps;

    private Netcdf2Variable(NetcdfDataset nds, Variable v0) {
      super(v0.getShortName(), v0);

      sweeps = new ArrayList<>();
      nsweeps = 0;
      int[] shape = v0.getShape();
      int count = v0.getRank() - 1;

      int ngates = shape[count];
      count--;
      int nrays = shape[count];
      count--;

      if (shape.length == 3)
        nsweeps = shape[count];
      else
        nsweeps = 1;

      for (int i = 0; i < nsweeps; i++)
        sweeps.add(new Netcdf2Sweep(v0, i, nrays, ngates));

    }

    public String toString() {
      return name;
    }

    public float[] readAllData() throws IOException {
      Array allData;
      Sweep spn = sweeps.get(0);
      Variable v = spn.getsweepVar();
      try {
        allData = v.read();
      } catch (IOException e) {
        throw new IOException(e.getMessage());
      }
      return (float[]) allData.get1DJavaArray(float.class);
    }

    public int getNumSweeps() {
      return nsweeps;
    }


    public Sweep getSweep(int sweepNo) {
      return sweeps.get(sweepNo);
    }

    public void clearVariableMemory() {
      // doing nothing
    }

    //////////////////////////////////////////////////////////////////////
    private class Netcdf2Sweep implements RadialDatasetSweep.Sweep {
      double meanElevation = Double.NaN;
      double meanAzimuth = Double.NaN;
      int sweepno, nrays, ngates;
      Variable sweepVar;


      Netcdf2Sweep(Variable v, int sweepno, int rays, int gates) {
        this.sweepno = sweepno;
        this.nrays = rays;
        this.ngates = gates;
        this.sweepVar = v;
      }

      public Variable getsweepVar() {
        return sweepVar;
      }

      public float[] readData() throws java.io.IOException {

        Array allData;
        int[] shape = sweepVar.getShape();
        int[] origind = new int[sweepVar.getRank()];

        if (isVolume) {
          origind[0] = sweepno;
          origind[1] = 0;
          shape[0] = 1;
        }

        try {
          allData = sweepVar.read(origind, shape);
        } catch (InvalidRangeException e) {
          throw new IOException(e.getMessage());
        }
        int nradials = shape[0];
        int ngates = shape[1];
        IndexIterator dataIter = allData.getIndexIterator();
        for (int j = 0; j < nradials; j++) {
          for (int i = 0; i < ngates; i++) {
            float tp = dataIter.getFloatNext();
            if (tp == -32768.0f || tp == -99900.0f) {
              dataIter.setFloatCurrent(Float.NaN);
            }
          }
        }

        return (float[]) allData.get1DJavaArray(float.class);
      }

      /* read 1d data ngates */
      public float[] readData(int ray) throws java.io.IOException {

        Array rayData;
        int[] shape = sweepVar.getShape();
        int[] origind = new int[sweepVar.getRank()];

        if (isVolume) {
          origind[0] = sweepno;
          origind[1] = ray;
          shape[0] = 1;
          shape[1] = 1;
        } else {
          shape[0] = 1;
          origind[0] = ray;
        }

        try {
          rayData = sweepVar.read(origind, shape);
        } catch (ucar.ma2.InvalidRangeException e) {
          throw new IOException(e.getMessage());
        }
        int ngates = shape[1];
        IndexIterator dataIter = rayData.getIndexIterator();
        for (int i = 0; i < ngates; i++) {
          float tp = dataIter.getFloatNext();
          if (tp == -32768.0f || tp == -99900.0f) {
            dataIter.setFloatCurrent(Float.NaN);
          }
        }
        return (float[]) rayData.get1DJavaArray(float.class);
      }

      private void setMeanElevation() {

        if (isVolume) {
          try {
            Variable sp = ds.findVariable("sweep");
            Array spData = sp.read();
            float[] spArray = (float[]) spData.get1DJavaArray(float.class);
            meanElevation = spArray[sweepno];
          } catch (IOException e) {
            e.printStackTrace();
            meanElevation = 0.0;
          }
        } else {
          Attribute data = ds.findGlobalAttribute("Elevation");
          meanElevation = data.getNumericValue().doubleValue();
        }
      }

      public float getMeanElevation() {
        if (Double.isNaN(meanElevation))
          setMeanElevation();
        return (float) meanElevation;
      }

      public double meanDouble(Array a) {
        double sum = 0;
        int size = 0;

        IndexIterator iterA = a.getIndexIterator();
        while (iterA.hasNext()) {
          double s = iterA.getDoubleNext();

          if (!Double.isNaN(s)) {
            sum += s;
            size++;
          }
        }
        if (size > 0)
          return sum / size;
        else
          return Double.POSITIVE_INFINITY;
      }

      public int getGateNumber() {
        return ngates;
      }

      public int getRadialNumber() {
        return nrays;
      }

      public RadialDatasetSweep.Type getType() {
        return null;
      }

      public ucar.unidata.geoloc.EarthLocation getOrigin(int ray) {
        return origin;
      }

      public Date getStartingTime() {
        return startDate;
      }

      public Date getEndingTime() {
        return endDate;
      }

      public int getSweepIndex() {
        return sweepno;
      }

      private void setMeanAzimuth() {
        meanAzimuth = 0.0;
      }

      public float getMeanAzimuth() {
        if (Double.isNaN(meanAzimuth))
          setMeanAzimuth();
        return (float) meanAzimuth;
      }

      public boolean isConic() {
        return true;
      }

      public float getElevation(int ray) {
        return (float) meanElevation;
      }

      public float[] getElevation() {

        float[] dataValue = new float[nrays];
        for (int i = 0; i < nrays; i++) {
          dataValue[i] = (float) meanElevation;
        }
        return dataValue;
      }

      public float[] getAzimuth() throws IOException {
        Array aziData = ds.findVariable("Azimuth").read();
        return (float[]) aziData.get1DJavaArray(float.class);
      }


      public float getAzimuth(int ray) throws IOException {
        String aziName = "Azimuth";

        try {
          Array aziData = ds.findVariable(aziName).read();
          if (isVolume) {
            int[] aziOrigin = new int[2];
            aziOrigin[0] = sweepno;
            aziOrigin[1] = 0;
            int[] aziShape = {1, getRadialNumber()};
            aziData = aziData.section(aziOrigin, aziShape);
          }
          Index index = aziData.getIndex();
          return aziData.getFloat(index.set(ray));
        } catch (ucar.ma2.InvalidRangeException e) {
          throw new IOException(e);
        }
      }

      public float getRadialDistance(int gate) throws IOException {
        float gateStart = getRangeToFirstGate();
        Variable gateSize = ds.findVariable("GateWidth");
        float[] data = (float[]) gateSize.read().get1DJavaArray(float.class);
        float dist = gateStart + gate * data[0];

        return dist;
      }

      public float getTime(int ray) {
        return startDate.getTime();
      }

      public float getBeamWidth() {
        return 0.95f; // degrees, info from Chris Burkhart
      }

      public float getNyquistFrequency() {
        return 0; // LOOK this may be radial specific
      }

      public float getRangeToFirstGate() {
        Attribute firstGate = ds.findGlobalAttributeIgnoreCase("RangeToFirstGate");
        double gateStart = firstGate.getNumericValue().doubleValue();
        return (float) gateStart;
      }

      public float getGateSize() {
        try {
          return getRadialDistance(1) - getRadialDistance(0);
        } catch (IOException e) {
          e.printStackTrace();
          return 0.0f;
        }
      }

      public boolean isGateSizeConstant() {
        return true;
      }

      public void clearSweepMemory() {

      }
    } // Netcdf2Sweep class

  } // Netcdf2Variable

}