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

package ucar.nc2.ui;

import ucar.ma2.*;
import ucar.nc2.NCdumpW;
import ucar.nc2.Structure;
import ucar.nc2.Variable;
import ucar.nc2.constants.CDM;
import ucar.nc2.ft.PointFeature;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.time.CalendarPeriod;
import ucar.nc2.time.CalendarTimeZone;
import ucar.nc2.ui.table.*;
import ucar.nc2.ui.widget.*;
import ucar.nc2.ui.widget.PopupMenu;
import ucar.nc2.util.HashMapLRU;
import ucar.nc2.util.Indent;
import ucar.util.prefs.PreferencesExt;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;

/**
 * This puts the data values of a 1D Structure or Sequence into a JTable.
 * The columns are the members of the Structure.
 *
 * @author caron
 */
public class StructureTable extends JPanel {
  private PreferencesExt prefs;
  private StructureTableModel dataModel;

  private JTable jtable;
  private PopupMenu popup;
  private FileManager fileChooser; // for exporting
  private TextHistoryPane dumpTA;
  private IndependentWindow dumpWindow;

  public StructureTable(PreferencesExt prefs) {
    this.prefs = prefs;

    jtable = new JTable();
    setLayout(new BorderLayout());
    jtable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

    ToolTipManager.sharedInstance().registerComponent(jtable);

    //JScrollPane sp =  new JScrollPane(jtable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    add(new JScrollPane(jtable), BorderLayout.CENTER);

    // other widgets
    dumpTA = new TextHistoryPane(false);
    dumpWindow = new IndependentWindow("Show Data", BAMutil.getImage("netcdfUI"), dumpTA);
    if (prefs != null)
      dumpWindow.setBounds((Rectangle) prefs.getBean("DumpWindowBounds", new Rectangle(300, 300, 600, 600)));
    else
      dumpWindow.setBounds(new Rectangle(300, 300, 600, 600));

    PreferencesExt fcPrefs = (prefs == null) ? null : (PreferencesExt) prefs.node("FileManager");
    fileChooser = new FileManager(null, null, "csv", "comma seperated values", fcPrefs);
  }

  private EventListenerList listeners = new EventListenerList();

  /**
   * Add listener: ListSelectionEvent sent when a new row is selected
   *
   * @param l the listener
   */
  public void addListSelectionListener(ListSelectionListener l) {
    listeners.add(javax.swing.event.ListSelectionListener.class, l);
  }

  /**
   * Remove listener
   *
   * @param l the listener
   */
  public void removeListSelectionListener(ListSelectionListener l) {
    listeners.remove(javax.swing.event.ListSelectionListener.class, l);
  }

  private void fireEvent(javax.swing.event.ListSelectionEvent event) {
    Object[] llist = listeners.getListenerList();
    // Process the listeners last to first
    for (int i = llist.length - 2; i >= 0; i -= 2)
      ((javax.swing.event.ListSelectionListener) llist[i + 1]).valueChanged(event);
  }

  public void addActionToPopupMenu(String title, AbstractAction act) {
    if (popup == null) popup = new PopupMenu(jtable, "Options");
    popup.addAction(title, act);
  }

  // clear the table

  public void clear() {
    if (dataModel != null)
      dataModel.clear();
  }

  // save state

  public void saveState() {
    fileChooser.save();
    if (prefs != null) prefs.getBean("DumpWindowBounds", dumpWindow.getBounds());
  }

  public void setStructure(Structure s) throws IOException {
    if (s.getDataType() == DataType.SEQUENCE)
      dataModel = new SequenceModel(s, true);
    else
      dataModel = new StructureModel(s);

    initTable(dataModel);
  }

  /**
   * Set the data as a collection of StructureData.
   *
   * @param structureData List of type StructureData
   * @throws IOException on io error
   */
  public void setStructureData(List<StructureData> structureData) throws IOException {
    dataModel = new StructureDataModel(structureData);
    initTable(dataModel);
  }

  public void setStructureData(ArrayStructure as) {
    dataModel = new ArrayStructureModel(as);
    initTable(dataModel);
  }

  public void setSequenceData(Structure s, ArraySequence seq) {
    dataModel = new ArraySequenceModel(s, seq);
    initTable(dataModel);
  }

  /**
   * Set the data as a collection of PointFeature.
   *
   * @param obsData List of type PointFeature
   * @throws IOException on io error
   */
  public void setPointFeatureData(List<PointFeature> obsData) throws IOException {
    dataModel = new PointFeatureDataModel(obsData);
    initTable(dataModel);
  }

  private void initTable(StructureTableModel m) {
    TableColumnModel tcm = new HidableTableColumnModel(m);
    jtable = new JTable(m, tcm);
    jtable.setRowSorter(new UndoableRowSorter<>(m));

    // Fixes this bug: http://stackoverflow.com/questions/6601994/jtable-boolean-cell-type-background
    ((JComponent) jtable.getDefaultRenderer(Boolean.class)).setOpaque(true);

    // Set the preferred column widths so that they're big enough to display all data without truncation.
    ColumnWidthsResizer resizer = new ColumnWidthsResizer(jtable);
    jtable.getModel().addTableModelListener(resizer);
    jtable.getColumnModel().addColumnModelListener(resizer);

    // Left-align every cell, including header cells.
    TableAligner aligner = new TableAligner(jtable, SwingConstants.LEADING);
    jtable.getColumnModel().addColumnModelListener(aligner);

    // Don't resize the columns to fit the available space. We do this because there may be a LOT of columns, and
    // auto-resize would cause them to be squished together to the point of uselessness. For an example, see
    // Q:/cdmUnitTest/ft/stationProfile/noaa-cap/XmadisXdataXLDADXprofilerXnetCDFX20100501_0200
    jtable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

    ListSelectionModel rowSM = jtable.getSelectionModel();
    rowSM.addListSelectionListener(e -> {
        if (e.getValueIsAdjusting()) { return; }   //Ignore extra messages.
        ListSelectionModel lsm = (ListSelectionModel) e.getSource();
        if (!lsm.isSelectionEmpty()) {
          fireEvent(e);
        }
    });

    if (m.wantDate) {
      jtable.getColumnModel().getColumn(0).setCellRenderer(new DateRenderer());
      jtable.getColumnModel().getColumn(1).setCellRenderer(new DateRenderer());
    }

    // reset popup
    popup = null;
    addActionToPopupMenu("Show", new AbstractAction() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
        showData();
      }
    });

    addActionToPopupMenu("Export", new AbstractAction() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
        export();
      }
    });

    addActionToPopupMenu("Show Internal", new AbstractAction() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
        showDataInternal();
      }
    });

    // add any subtables from inner Structures
    for (Structure s : m.subtables) {
      addActionToPopupMenu("Data Table for " + s.getShortName(), new SubtableAbstractAction(s));
    }

    removeAll();

    // Create a button that will popup a menu containing options to configure the appearance of the table.
    JButton cornerButton = new JButton(new TableAppearanceAction(jtable));
    cornerButton.setHideActionText(true);
    cornerButton.setContentAreaFilled(false);

    // Install the button in the upper-right corner of the table's scroll pane.
    JScrollPane scrollPane = new JScrollPane(jtable);
    scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, cornerButton);
    scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    // This keeps the corner button visible even when the table is empty (or all columns are hidden).
    scrollPane.setColumnHeaderView(new JViewport());
    scrollPane.getColumnHeader().setPreferredSize(jtable.getTableHeader().getPreferredSize());

    add(scrollPane, BorderLayout.CENTER);

    revalidate();
  }

  // display subtables
  private static HashMap<String, IndependentWindow> windows = new HashMap<>();

  private class SubtableAbstractAction extends AbstractAction {
    Structure s;
    StructureTable dataTable;
    IndependentWindow dataWindow;

    SubtableAbstractAction(Structure s) {
      this.s = s;
      dataTable = new StructureTable(null);
      dataWindow = windows.get(s.getFullName());
      if (dataWindow == null) {
        dataWindow = new IndependentWindow("Data Table", BAMutil.getImage("netcdfUI"), dataTable);
        windows.put(s.getFullName(), dataWindow);
      } else {
        dataWindow.setComponent(dataTable);
      }
    }

    public void actionPerformed(java.awt.event.ActionEvent e) {
      StructureData sd = getSelectedStructureData();
      if (sd == null) return;
      StructureMembers.Member m = sd.findMember(s.getShortName());
      if (m == null)
        throw new IllegalStateException("cant find member = " + s.getShortName());

      if (m.getDataType() == DataType.STRUCTURE) {
        ArrayStructure as = sd.getArrayStructure(m);
        dataTable.setStructureData(as);

      } else if (m.getDataType() == DataType.SEQUENCE) {
        ArraySequence seq = sd.getArraySequence(m);
        dataTable.setSequenceData(s, seq);

      } else throw new IllegalStateException("data type = " + m.getDataType());

      dataWindow.show();
    }
  }

  private void export() {
    String filename = fileChooser.chooseFilename();
    if (filename == null) return;
    try {
      PrintWriter pw = new PrintWriter(new File(filename),
              CDM.utf8Charset.name());

      TableModel model = jtable.getModel();
      for (int col = 0; col < model.getColumnCount(); col++) {
        if (col > 0) pw.print(",");
        pw.print(model.getColumnName(col));
      }
      pw.println();

      for (int row = 0; row < model.getRowCount(); row++) {
        for (int col = 0; col < model.getColumnCount(); col++) {
          if (col > 0) pw.print(",");
          pw.print(model.getValueAt(row, col).toString());
        }
        pw.println();
      }
      pw.close();
      JOptionPane.showMessageDialog(this, "File successfully written");
    } catch (IOException ioe) {
      JOptionPane.showMessageDialog(this, "ERROR: " + ioe.getMessage());
      ioe.printStackTrace();
    }

  }

  private void showData() {
    StructureData sd = getSelectedStructureData();
    if (sd == null) return;

    StringWriter sw = new StringWriter(10000);
    try {
      NCdumpW.printStructureData(new PrintWriter(sw), sd);
    } catch (IOException e) {
      String mess = e.getMessage();
      sw.write(mess);
    }
    dumpTA.setText(sw.toString());
    dumpWindow.setVisible(true);
  }

  private void showDataInternal() {
    StructureData sd = getSelectedStructureData();
    if (sd == null) return;

    Formatter f = new Formatter();
    sd.showInternalMembers(f, new Indent(2));
    f.format("%n");
    sd.showInternal(f, new Indent(2));
    dumpTA.setText(f.toString());
    dumpWindow.setVisible(true);
  }

  private StructureData getSelectedStructureData() {
    int viewRowIdx = jtable.getSelectedRow();
    if (viewRowIdx < 0) return null;
    int modelRowIndex = jtable.convertRowIndexToModel(viewRowIdx);

    try {
      return dataModel.getStructureData(modelRowIndex);
    } catch (InvalidRangeException e) {
      e.printStackTrace();
    } catch (IOException e) {
      JOptionPane.showMessageDialog(this, "ERROR: " + e.getMessage());
      e.printStackTrace();
    }
    return null;
  }

  public Object getSelectedRow() {
    int viewRowIdx = jtable.getSelectedRow();
    if (viewRowIdx < 0) return null;
    int modelRowIndex = jtable.convertRowIndexToModel(viewRowIdx);

    try {
      return dataModel.getRow(modelRowIndex);
    } catch (InvalidRangeException | IOException e) {
      JOptionPane.showMessageDialog(this, "ERROR: " + e.getMessage());
      e.printStackTrace();
    }
    return null;
  }

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

  private abstract class StructureTableModel extends AbstractTableModel {
    protected HashMapLRU rowHash = new HashMapLRU(500, 500); // cache 500 rows
    protected StructureMembers members;
    protected boolean wantDate = false;
    protected List<Structure> subtables = new ArrayList<>();

    // subclasses implement these

    abstract public StructureData getStructureData(int row) throws InvalidRangeException, IOException;

    // remove all data

    abstract public void clear();

    // if we know how to extract the date for this data, add two extra columns

    abstract public CalendarDate getObsDate(int row);

    abstract public CalendarDate getNomDate(int row);

    public Object getRow(int row) throws InvalidRangeException, IOException {
      return getStructureData(row);
    }

    public int getColumnCount() {
      if (members == null) return 0;
      return members.getMembers().size() + (wantDate ? 2 : 0);
    }

    public String getColumnName(int columnIndex) {
      // if (columnIndex == 0)
      //   return "hash";
      if (wantDate && (columnIndex == 0))
        return "obsDate";
      if (wantDate && (columnIndex == 1))
        return "nomDate";
      int memberCol = wantDate ? columnIndex - 2 : columnIndex;
      return members.getMember(memberCol).getName();
    }

    // get row data if in the cache, otherwise read it

    public StructureData getStructureDataHash(int row) throws InvalidRangeException, IOException {
      StructureData sd = (StructureData) rowHash.get(row);
      if (sd == null) {
        sd = getStructureData(row);
        rowHash.put(row, sd);
      }
      return sd;
    }

    public Object getValueAt(int row, int column) {
      /* if (column == 0) {
        try {
          return Long.toHexString( getStructureData(row).hashCode());
        } catch (Exception e) {
          return "ERROR";
        }
      } */
      if (wantDate && (column == 0))
        return getObsDate(row);
      if (wantDate && (column == 1))
        return getNomDate(row);

      StructureData sd;
      try {
        sd = getStructureDataHash(row);
      } catch (InvalidRangeException e) {
        e.printStackTrace();
        return "ERROR " + e.getMessage();
      } catch (IOException e) {
        e.printStackTrace();
        return "ERROR " + e.getMessage();
      }

      String colName = getColumnName(column);
      return sd.getArray(colName);
    }

    String enumLookup(StructureMembers.Member m, Number val) {
      return "sorry";
    }

  }

  ////////////////////////////////////////////////////////////////////////
  // handles Structures

  private class StructureModel extends StructureTableModel {
    private Structure struct;

    StructureModel(Structure s) {
      this.struct = s;
      this.members = s.makeStructureMembers();
      for (Variable v : s.getVariables()) {
        if (v instanceof Structure)
          subtables.add((Structure) v);
      }
    }

    public CalendarDate getObsDate(int row) {
      return null;
    }

    public CalendarDate getNomDate(int row) {
      return null;
    }

    public int getRowCount() {
      if (struct == null) return 0;
      return (int) struct.getSize();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return struct.readStructure(row);
    }

    public void clear() {
      struct = null;
      fireTableDataChanged();
    }

    String enumLookup(StructureMembers.Member m, Number val) {
      Variable v = struct.findVariable(m.getName());
      return v.lookupEnumString(val.intValue());
    }

  }

  // handles Sequences

  private class SequenceModel extends StructureModel {
    protected List<StructureData> sdataList;

    SequenceModel(Structure seq, boolean readData) {
      super(seq);

      if (readData) {
        sdataList = new ArrayList<>();
        try {
          try (StructureDataIterator iter = seq.getStructureIterator()) {
            while (iter.hasNext())
              sdataList.add(iter.next());
          }

        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    public CalendarDate getObsDate(int row) {
      return null;
    }

    public CalendarDate getNomDate(int row) {
      return null;
    }

    public int getRowCount() {
      return sdataList.size();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return sdataList.get(row);
    }

    // LOOK does this have to override ?

    public Object getValueAt(int row, int column) {
      StructureData sd = sdataList.get(row);

      /* if (column == 0) {
        try {
          return Long.toHexString( sd.hashCode());
        } catch (Exception e) {
          return "ERROR";
        }
      } */
      return sd.getScalarObject(sd.getStructureMembers().getMember(column));
    }

    public void clear() {
      sdataList = new ArrayList<>();
      fireTableDataChanged();
    }
  }

  private class ArraySequenceModel extends SequenceModel {

    ArraySequenceModel(Structure s, ArraySequence seq) {
      super(s, false);

      this.members = seq.getStructureMembers();

      sdataList = new ArrayList<>();
      try {
        try (StructureDataIterator iter = seq.getStructureDataIterator()) {
          while (iter.hasNext())
            sdataList.add(iter.next());  // LOOK lame -read at all once
        }

      } catch (IOException e) {
        JOptionPane.showMessageDialog(null, "ERROR: " + e.getMessage());
        e.printStackTrace();
      }
    }
  }

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

  private class StructureDataModel extends StructureTableModel {
    private List<StructureData> structureData;

    StructureDataModel(List<StructureData> structureData) {
      this.structureData = structureData;
      if (structureData.size() > 0) {
        StructureData sd = structureData.get(0);
        this.members = sd.getStructureMembers();
      }
    }

    public CalendarDate getObsDate(int row) {
      return null;
    }

    public CalendarDate getNomDate(int row) {
      return null;
    }

    public int getRowCount() {
      return structureData.size();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return structureData.get(row);
    }

    public void clear() {
      structureData = new ArrayList<>(); // empty list
      fireTableDataChanged();
    }

  }

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

  private class ArrayStructureModel extends StructureTableModel {
    private ArrayStructure as;

    ArrayStructureModel(ArrayStructure as) {
      this.as = as;
      this.members = as.getStructureMembers();
    }

    public CalendarDate getObsDate(int row) {
      return null;
    }

    public CalendarDate getNomDate(int row) {
      return null;
    }

    public int getRowCount() {
      return (as == null) ? 0 : (int) as.getSize();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return as.getStructureData(row);
    }

    public void clear() {
      as = null;
      fireTableDataChanged();
    }

  }

  /*

  private class TrajectoryModel extends StructureTableModel {
    private TrajectoryObsDatatype traj;

    TrajectoryModel(TrajectoryObsDatatype traj) throws IOException {
      this.traj = traj;
      StructureData sd;
      if (traj.getNumberPoints() > 0) {
        try {
          sd = traj.getData(0);
          this.members = sd.getStructureMembers();
        } catch (InvalidRangeException e) {
          JOptionPane.showMessageDialog(null, "ERROR: " + e.getMessage());
          throw new IOException(e.getMessage());
        }
      }
      wantDate = true;
    }

    public Date getObsDate(int row) {
      try {
        return traj.getTime(row);
      } catch (IOException e) {
        return null;
      }
    }

    public Date getNomDate(int row) {
      return null;
    }

    public Date getDate(int row) {
      try {
        return traj.getTime(row);
      } catch (IOException e) {
        return null;
      }
    }

    public int getRowCount() {
      return (traj == null) ? 0 : traj.getNumberPoints();
    }

    public Object getRow(int row) throws InvalidRangeException, IOException {
      return traj.getPointObsData(row); // PointObsDatatype
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return traj.getData(row);
    }

    public void clear() {
      traj = null;
      fireTableDataChanged();
    }

  }

  private class PointObsDataModel extends StructureTableModel {
    private List<PointObsDatatype> obsData;

    PointObsDataModel(List<PointObsDatatype> obsData) throws IOException {
      wantDate = true;

      this.obsData = obsData;
      if (obsData.size() > 0) {
        StructureData sd;
        try {
          sd = getStructureData(0);
        } catch (InvalidRangeException e) {
          JOptionPane.showMessageDialog(null, "ERROR: " + e.getMessage());
          throw new IOException(e.getMessage());
        }
        this.members = sd.getStructureMembers();
      }
    }

    public Date getObsDate(int row) {
      PointObsDatatype obs = obsData.get(row);
      return obs.getObservationTimeAsDate();
    }

    public Date getNomDate(int row) {
      PointObsDatatype obs = obsData.get(row);
      return obs.getNominalTimeAsDate();
    }

    public int getRowCount() {
      return obsData.size();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      PointObsDatatype obs = obsData.get(row);
      return obs.getData();
    }

    public Object getRow(int row) throws InvalidRangeException, IOException {
      return obsData.get(row); // PointObsDatatype
    }

    public void clear() {
      obsData = new ArrayList<PointObsDatatype>(); // empty list
      fireTableDataChanged();
    }
  }  */

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

  private class PointFeatureDataModel extends StructureTableModel {
    private List<PointFeature> obsData;

    PointFeatureDataModel(List<PointFeature> obsData) throws IOException {
      wantDate = true;

      this.obsData = obsData;
      if (obsData.size() > 0) {
        StructureData sd;
        try {
          sd = getStructureData(0);
        } catch (InvalidRangeException e) {
          JOptionPane.showMessageDialog(null, "ERROR: " + e.getMessage());
          throw new IOException(e.getMessage());
        }
        this.members = sd.getStructureMembers();
      }
    }

    public CalendarDate getObsDate(int row) {
      PointFeature obs = obsData.get(row);
      return obs.getObservationTimeAsCalendarDate();
    }

    public CalendarDate getNomDate(int row) {
      PointFeature obs = obsData.get(row);
      return obs.getNominalTimeAsCalendarDate();
    }

    public int getRowCount() {
      return obsData.size();
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      PointFeature obs = obsData.get(row);
      return obs.getFeatureData();
    }

    public Object getRow(int row) throws InvalidRangeException, IOException {
      return obsData.get(row); // PointObsDatatype
    }

    public void clear() {
      obsData = new ArrayList<>(); // empty list
      fireTableDataChanged();
    }
  }

  /* private class SequenceTableModel extends TableModelWithDesc {
    private Sequence seq;
    private Structure struct;
    private ArrayObject.D1 data;

    SequenceTableModel( Sequence s) throws IOException {
      this.seq = s;
      this.struct = s.getInternalStructure();
      // for now, we have to read entire sequence into memory !!
      data = (ArrayObject.D1) seq.read();
    }

    public int getRowCount() {
      return (seq == null) ? 0 : (int) data.getSize();
    }
    public int getColumnCount() {
      return (seq == null) ? 0 : struct.getVariables().size();
    }

    public String getColumnName(int columnIndex) {
      return (String) struct.getVariableNames().get( columnIndex);
    }

    public String getColumnDesc(int columnIndex) {
      return "test "+columnIndex;
    }

    public Object getValueAt(int row, int column) {
      StructureData sd = (StructureData) data.get( row);
      List arrays = sd.getMembers();
      StructureData.Member gr = (StructureData.Member) arrays.get( column);
      Array a = gr.data;
      Index ima = a.getIndex();
      return a.getObject(ima);
    }
  } */

  /**
   * Renderer for Date type
   */
  static class DateRenderer extends DefaultTableCellRenderer {
    private CalendarDateFormatter newForm, oldForm;
    private CalendarDate cutoff;

    DateRenderer() {
      super();

      oldForm = new CalendarDateFormatter("yyyy MMM dd HH:mm", CalendarTimeZone.UTC);
      newForm = new CalendarDateFormatter("MMM dd, HH:mm", CalendarTimeZone.UTC);

      CalendarDate now =  CalendarDate.present();
      cutoff = now.add(-1, CalendarPeriod.Field.Year); // "now" time format within a year
    }

    public void setValue(Object value) {
      if (value == null)
        setText("");
      else {
        CalendarDate date = (CalendarDate) value;
        if (date.isBefore(cutoff))
          setText(oldForm.toString(date));
        else
          setText(newForm.toString(date));
      }
    }
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.gis;

import ucar.nc2.ui.util.Renderer;
import ucar.unidata.geoloc.*;

import ucar.util.prefs.ui.Debug;

import java.awt.Color;
import java.awt.Shape;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;

/**
 * Superclass for rendering collections of GisFeatures.
 *
 * @author John Caron
 */
public abstract class GisFeatureRenderer implements Renderer {
  private Color color = Color.blue;                   // default color of polylines
  protected ProjectionImpl displayProject = null;     // the current display Projection
  protected ArrayList shapeList = null;

  ////// this is what the subclasses have to implement (besides the constructor)
    /**
     * Preferred map area on opening for first time.
     * @return lat/lon bounding box that specifies preferred area.
     */
  public abstract LatLonRect getPreferredArea();
  protected abstract java.util.List getFeatures();      // collection of AbstractGisFeature

    // what projection is the data in? set to null if no Projection (no conversion)
    // assumes data projection doesnt change
  protected abstract ProjectionImpl getDataProjection();

  ///////////
  public java.awt.Color getColor() { return color; }
  public void setColor( Color color) { this.color = color; }
  public void setProjection(ProjectionImpl project) {
      displayProject = project;
      shapeList = null;
      //System.out.println("GisFeatureRenderer setProjection "+displayProject);

      //if (Debug.isSet("event.barf") && (displayProject instanceof LatLonProjection))
      //  throw new IllegalArgumentException();
  }

    /**
     * Draws all the features that are within the graphics clip rectangle,
     * using the previously set displayProjection.
     *
     * @param g the Graphics2D context on which to draw
     * @param pixelAT  transforms "Normalized Device" to Device coordinates
     */
  public void draw(java.awt.Graphics2D g, AffineTransform pixelAT) {
    g.setColor(color);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    g.setStroke(new java.awt.BasicStroke(0.0f));

    Rectangle2D clipRect = (Rectangle2D) g.getClip();
    Iterator siter = getShapes(g, pixelAT);
    while (siter.hasNext()) {
      Shape s = (Shape) siter.next();
      Rectangle2D shapeBounds = s.getBounds2D();
      if (shapeBounds.intersects(clipRect))
        g.draw(s);
    }
  }

  // get the set of shapes to draw, convert projections if need be
  protected Iterator getShapes(java.awt.Graphics2D g, AffineTransform normal2device) {
    if (shapeList != null)
      return shapeList.iterator();

    if(Debug.isSet("projection/LatLonShift"))
      System.out.println("projection/LatLonShift GisFeatureRenderer.getShapes called");

    ProjectionImpl dataProject = getDataProjection();

    // a list of GisFeatureAdapter-s
    List featList = getFeatures();

    shapeList = new ArrayList(featList.size());

    Iterator iter = featList.iterator();

    while (iter.hasNext())
        {
        AbstractGisFeature feature = (AbstractGisFeature) iter.next();
        Shape shape;
        if (dataProject == null)
            shape = feature.getShape();
        else if (dataProject.isLatLon())  {
            // always got to run it through if its lat/lon
            shape = feature.getProjectedShape(displayProject);
            //System.out.println("getShapes dataProject.isLatLon() "+displayProject);
        } else if (dataProject == displayProject) {
            shape = feature.getShape();
            //System.out.println("getShapes dataProject == displayProject");
        } else {
            shape = feature.getProjectedShape(dataProject, displayProject);
            //System.out.println("getShapes dataProject != displayProject");
        }

        shapeList.add(shape);
        }

    return shapeList.iterator();
  }

}


package ucar.nc2.ogc.waterml;

import net.opengis.waterml.x20.DocumentMetadataType;
import org.joda.time.DateTime;
import ucar.nc2.ogc.MarshallingUtil;

/**
 * Created by cwardgar on 2014/03/13.
 */
public abstract class NcDocumentMetadataType {
    // wml2:Collection/wml2:metadata/wml2:DocumentMetadata
    public static DocumentMetadataType initDocumentMetadata(DocumentMetadataType documentMetadata) {
        // @gml:id
        String id = MarshallingUtil.createIdForType(DocumentMetadataType.class);
        documentMetadata.setId(id);

        // wml2:generationDate
        DateTime generationDate = MarshallingUtil.fixedGenerationDate;
        if (generationDate == null) {
            generationDate = new DateTime();  // Initialized to "now".
        }
        documentMetadata.setGenerationDate(generationDate.toGregorianCalendar());

        return documentMetadata;
    }

    private NcDocumentMetadataType() { }
}


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

package ucar.nc2.grib.collection;

import javax.annotation.Nullable;
import org.jdom2.Element;
import thredds.client.catalog.Catalog;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.inventory.CollectionUpdateType;
import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.grib.*;
import ucar.nc2.grib.coord.Coordinate;
import ucar.nc2.grib.coord.CoordinateEns;
import ucar.nc2.grib.coord.CoordinateRuntime;
import ucar.nc2.grib.coord.CoordinateTime;
import ucar.nc2.grib.coord.CoordinateTime2D;
import ucar.nc2.grib.coord.CoordinateTimeAbstract;
import ucar.nc2.grib.coord.CoordinateTimeIntv;
import ucar.nc2.grib.coord.CoordinateVert;
import ucar.nc2.grib.coord.EnsCoordValue;
import ucar.nc2.grib.coord.TimeCoordIntvValue;
import ucar.nc2.grib.coord.VertCoordType;
import ucar.nc2.grib.coord.VertCoordValue;
import ucar.nc2.grib.grib2.Grib2Utils;
import ucar.nc2.iosp.AbstractIOServiceProvider;
import ucar.nc2.time.Calendar;
import ucar.nc2.time.CalendarPeriod;
import ucar.nc2.util.CancelTask;
import ucar.unidata.geoloc.projection.RotatedPole;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.Parameter;

import java.io.IOException;
import java.util.Formatter;
import java.util.List;

/**
 * Grib Collection IOSP, version 2. Handles both collections and single GRIB files. Immutable after
 * open() is called.
 *
 * @author caron
 * @since 4/6/11
 */
public abstract class GribIosp extends AbstractIOServiceProvider {
  public static int debugIndexOnlyCount = 0;  // count number of data accesses

  // store custom tables in here
  protected final FeatureCollectionConfig config = new FeatureCollectionConfig();

  public void setParamTable(Element paramTable) {
    config.gribConfig.paramTable = paramTable;
  }

  public void setLookupTablePath(String lookupTablePath) {
    config.gribConfig.lookupTablePath = lookupTablePath;
  }

  public void setParamTablePath(String paramTablePath) {
    config.gribConfig.paramTablePath = paramTablePath;
  }

  @Override
  @Nullable
  public Object sendIospMessage(Object special) {
    if (special instanceof String) {
      String s = (String) special;
      if (s.startsWith("gribParameterTableLookup")) {
        int pos = s.indexOf("=");
        if (pos > 0) {
          config.gribConfig.lookupTablePath = s.substring(pos + 1).trim();
        }

      } else if (s.startsWith("gribParameterTable")) {
        int pos = s.indexOf("=");
        if (pos > 0) {
          config.gribConfig.paramTablePath = s.substring(pos + 1).trim();
        }
      }
      return null;
    }

    if (special instanceof org.jdom2.Element) {  // the root element will be <iospParam>
      Element root = (org.jdom2.Element) special;
      config.gribConfig.configFromXml(root, Catalog.ncmlNS);
      return null;
    }

    return super.sendIospMessage(special);
  }

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

  protected final boolean isGrib1;
  protected final org.slf4j.Logger logger;

  protected GribCollectionImmutable gribCollection;
  protected GribCollectionImmutable.GroupGC gHcs;
  protected GribCollectionImmutable.Type gtype; // only used if gHcs was set
  protected boolean isPartitioned;
  protected boolean owned; // if Iosp is owned by GribCollection; affects close() LOOK get rid of this
  protected ucar.nc2.grib.GribTables gribTable;

  public GribIosp(boolean isGrib1, org.slf4j.Logger logger) {
    this.isGrib1 = isGrib1;
    this.logger = logger;
  }

  protected abstract ucar.nc2.grib.GribTables createCustomizer() throws IOException;

  protected abstract String makeVariableName(GribCollectionImmutable.VariableIndex vindex);

  protected abstract String makeVariableLongName(GribCollectionImmutable.VariableIndex vindex);

  protected abstract String makeVariableUnits(GribCollectionImmutable.VariableIndex vindex);

  protected abstract String getVerticalCoordDesc(int vc_code);

  protected abstract GribTables.Parameter getParameter(
      GribCollectionImmutable.VariableIndex vindex);

  @Override
  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask)
      throws IOException {
    super.open(raf, ncfile, cancelTask);

    if (gHcs != null) { // just use the one group that was set in the constructor
      this.gribCollection = gHcs.getGribCollection();
      if (this.gribCollection instanceof PartitionCollectionImmutable) {
        isPartitioned = true;
      }
      gribTable = createCustomizer();

      addGroup(ncfile, ncfile.getRootGroup(), gHcs, gtype, false);

    } else if (gribCollection == null) { // may have been set in the constructor

      this.gribCollection = GribCdmIndex
          .openGribCollectionFromRaf(raf, config, CollectionUpdateType.testIndexOnly, logger);
      if (gribCollection == null) {
        throw new IllegalStateException("Not a GRIB data file or index file " + raf.getLocation());
      }

      isPartitioned = (this.gribCollection instanceof PartitionCollectionImmutable);
      gribTable = createCustomizer();

      boolean useDatasetGroup = gribCollection.getDatasets().size() > 1;
      for (GribCollectionImmutable.Dataset ds : gribCollection.getDatasets()) {
        Group topGroup;
        if (useDatasetGroup) {
          topGroup = new Group(ncfile, null, ds.getType().toString());
          ncfile.addGroup(null, topGroup);
        } else {
          topGroup = ncfile.getRootGroup();
        }

        Iterable<GribCollectionImmutable.GroupGC> groups = ds.getGroups();
        boolean useGroups = ds.getGroupsSize() > 1;
        for (GribCollectionImmutable.GroupGC g : groups) {
          addGroup(ncfile, topGroup, g, ds.getType(), useGroups);
        }
      }
    }

    for (Attribute att : gribCollection.getGlobalAttributes().getAttributes()) {
      ncfile.addAttribute(null, att);
    }
  }

  private void addGroup(NetcdfFile ncfile, Group parent, GribCollectionImmutable.GroupGC group,
      GribCollectionImmutable.Type gctype, boolean useGroups) {

    Group g;
    if (useGroups) {
      g = new Group(ncfile, parent, group.getId());
      g.addAttribute(new Attribute(CDM.LONG_NAME, group.getDescription()));
      try {
        ncfile.addGroup(parent, g);
      } catch (Exception e) {
        logger.warn("Duplicate Group - skipping");
        return;
      }
    } else {
      g = parent;
    }

    makeGroup(ncfile, g, group, gctype);
  }

  private void makeGroup(NetcdfFile ncfile, Group g, GribCollectionImmutable.GroupGC group,
      GribCollectionImmutable.Type gctype) {
    GdsHorizCoordSys hcs = group.getGdsHorizCoordSys();
    String grid_mapping = hcs.getName() + "_Projection";

    String horizDims;

    boolean isRotatedLatLon = !isGrib1 && hcs.proj instanceof RotatedPole;
    boolean isLatLon2D =
        !isGrib1 && Grib2Utils.isCurvilinearOrthogonal(hcs.template, gribCollection.getCenter());
    boolean isLatLon =
        isGrib1 ? hcs.isLatLon() : Grib2Utils.isLatLon(hcs.template, gribCollection.getCenter());

    if (isRotatedLatLon) {
      Variable hcsV = ncfile
          .addVariable(g, new Variable(ncfile, g, null, grid_mapping, DataType.INT, ""));
      hcsV.setCachedData(Array.factory(DataType.INT, new int[0], new int[]{0}));
      for (Parameter p : hcs.proj.getProjectionParameters()) {
        hcsV.addAttribute(new Attribute(p));
      }
      horizDims = "rlat rlon";
      ncfile.addDimension(g, new Dimension("rlat", hcs.ny));
      ncfile.addDimension(g, new Dimension("rlon", hcs.nx));
      Variable rlat = ncfile
          .addVariable(g, new Variable(ncfile, g, null, "rlat", DataType.FLOAT, "rlat"));
      rlat.addAttribute(new Attribute(CF.STANDARD_NAME, CF.GRID_LATITUDE));
      rlat.addAttribute(new Attribute(CDM.UNITS, CDM.RLATLON_UNITS));
      rlat.setCachedData(Array.makeArray(DataType.FLOAT, hcs.ny, hcs.starty, hcs.dy));
      Variable rlon = ncfile
          .addVariable(g, new Variable(ncfile, g, null, "rlon", DataType.FLOAT, "rlon"));
      rlon.addAttribute(new Attribute(CF.STANDARD_NAME, CF.GRID_LONGITUDE));
      rlon.addAttribute(new Attribute(CDM.UNITS, CDM.RLATLON_UNITS));
      rlon.setCachedData(Array.makeArray(DataType.FLOAT, hcs.nx, hcs.startx, hcs.dx));
    } else if (isLatLon2D) { // CurvilinearOrthogonal - lat and lon fields must be present in the file
      horizDims = "lat lon";

      // LOOK - assume same number of points for all grids
      ncfile.addDimension(g, new Dimension("lon", hcs.nx));
      ncfile.addDimension(g, new Dimension("lat", hcs.ny));

    } else if (isLatLon) {
      // make horiz coordsys coordinate variable
      Variable hcsV = ncfile
          .addVariable(g, new Variable(ncfile, g, null, grid_mapping, DataType.INT, ""));
      hcsV.setCachedData(Array.factory(DataType.INT, new int[0], new int[]{0}));
      for (Parameter p : hcs.proj.getProjectionParameters()) {
        hcsV.addAttribute(new Attribute(p));
      }

      horizDims = "lat lon";
      ncfile.addDimension(g, new Dimension("lon", hcs.nx));
      ncfile.addDimension(g, new Dimension("lat", hcs.ny));

      Variable cv = ncfile
          .addVariable(g, new Variable(ncfile, g, null, "lat", DataType.FLOAT, "lat"));
      cv.addAttribute(new Attribute(CDM.UNITS, CDM.LAT_UNITS));
      if (hcs.getGaussianLats() != null) {
        cv.setCachedData(hcs.getGaussianLats());
        cv.addAttribute(new Attribute(CDM.GAUSSIAN, "true"));
      } else {
        cv.setCachedData(Array.makeArray(DataType.FLOAT, hcs.ny, hcs.starty, hcs.dy));
      }

      cv = ncfile.addVariable(g, new Variable(ncfile, g, null, "lon", DataType.FLOAT, "lon"));
      cv.addAttribute(new Attribute(CDM.UNITS, CDM.LON_UNITS));
      cv.setCachedData(Array.makeArray(DataType.FLOAT, hcs.nx, hcs.startx, hcs.dx));

    } else {
      // make horiz coordsys coordinate variable
      Variable hcsV = ncfile
          .addVariable(g, new Variable(ncfile, g, null, grid_mapping, DataType.INT, ""));
      hcsV.setCachedData(Array.factory(DataType.INT, new int[0], new int[]{0}));
      for (Parameter p : hcs.proj.getProjectionParameters()) {
        hcsV.addAttribute(new Attribute(p));
      }

      horizDims = "y x";
      ncfile.addDimension(g, new Dimension("x", hcs.nx));
      ncfile.addDimension(g, new Dimension("y", hcs.ny));

      Variable cv = ncfile.addVariable(g, new Variable(ncfile, g, null, "x", DataType.FLOAT, "x"));
      cv.addAttribute(new Attribute(CF.STANDARD_NAME, CF.PROJECTION_X_COORDINATE));
      cv.addAttribute(new Attribute(CDM.UNITS, "km"));
      cv.setCachedData(Array.makeArray(DataType.FLOAT, hcs.nx, hcs.startx, hcs.dx));

      cv = ncfile.addVariable(g, new Variable(ncfile, g, null, "y", DataType.FLOAT, "y"));
      cv.addAttribute(new Attribute(CF.STANDARD_NAME, CF.PROJECTION_Y_COORDINATE));
      cv.addAttribute(new Attribute(CDM.UNITS, "km"));
      cv.setCachedData(Array.makeArray(DataType.FLOAT, hcs.ny, hcs.starty, hcs.dy));
    }

    boolean singleRuntimeWasMade = false;

    for (Coordinate coord : group.coords) {
      Coordinate.Type ctype = coord.getType();
      switch (ctype) {
        case runtime:
          if (gctype.isTwoD() || coord.getNCoords() == 1) {
            makeRuntimeCoordinate(ncfile, g, (CoordinateRuntime) coord);
          }
          break;
        case timeIntv:
          makeTimeCoordinate1D(ncfile, g, (CoordinateTimeIntv) coord);
          break;
        case time:
          makeTimeCoordinate1D(ncfile, g, (CoordinateTime) coord);
          break;
        case vert:
          makeVerticalCoordinate(ncfile, g, (CoordinateVert) coord);
          break;
        case ens:
          makeEnsembleCoordinate(ncfile, g, (CoordinateEns) coord);
          break;
        case time2D:
          if (gctype.isUniqueTime()) {
            makeUniqueTimeCoordinate2D(ncfile, g, (CoordinateTime2D) coord);
          } else {
            makeTimeCoordinate2D(ncfile, g, (CoordinateTime2D) coord, gctype);
          }
          break;
      }
    }

    /* int ccount = 0;
    for (EnsCoord ec : gHcs.ensCoords) {
      int n = ec.getSize();
      String ecName = "ens" + ccount;
      ncfile.addDimension(g, new Dimension(ecName, n));
      Variable v = new Variable(ncfile, g, null, ecName, DataType.INT, ecName);
      ncfile.addVariable(g, v);
      ccount++;
      v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Ensemble.toString()));

      int[] data = new int[n];
      int count = 0;
      for (EnsCoord.Coord ecc : ec.getCoords())
        data[count++] = ecc.getEnsMember();
      v.setCachedData(Array.factory(DataType.INT, new int[]{n}, data));
    } */

    for (GribCollectionImmutable.VariableIndex vindex : group.variList) {
      try (Formatter dimNames = new Formatter();
          Formatter coordinateAtt = new Formatter()) {

        // do the times first
        Coordinate run = vindex.getCoordinate(Coordinate.Type.runtime);
        Coordinate time = vindex.getCoordinateTime();
        if (time == null) {
          throw new IllegalStateException("No time coordinate = " + vindex);
        }

        boolean isRunScaler = (run != null) && run.getSize() == 1;

        switch (gctype) {
          case SRC:     // GC: Single Runtime Collection                          [ntimes]           (run, 2D)  scalar runtime
            assert isRunScaler;
            dimNames.format("%s ", time.getName());
            coordinateAtt.format("%s %s ", run.getName(), time.getName());
            break;

          case MRUTP:             // PC: Multiple Runtime Unique Time Partition  [ntimes]
          case MRUTC:             // GC: Multiple Runtime Unique Time Collection  [ntimes]
            // case MRSTC:             // GC: Multiple Runtime Single Time Collection  [nruns, 1]
            // case MRSTP:             // PC: Multiple Runtime Single Time Partition   [nruns, 1]         (run, 2D)  ignore the run, its generated from the 2D in
            dimNames.format("%s ", time.getName());
            coordinateAtt.format("ref%s %s ", time.getName(), time.getName());
            break;

          case MRC:               // GC: Multiple Runtime Collection              [nruns, ntimes]    (run, 2D) use Both
          case TwoD:              // PC: TwoD time partition                      [nruns, ntimes]
            assert run != null : "GRIB MRC or TWOD does not have run coordinate";
            if (isRunScaler) {
              dimNames.format("%s ", time.getName());
            } else {
              dimNames.format("%s %s ", run.getName(), time.getName());
            }
            coordinateAtt.format("%s %s ", run.getName(), time.getName());
            break;

          case Best:              // PC: Best time partition                      [ntimes]          (time)   reftime is generated in makeTimeAuxReference()
          case BestComplete:      // PC: Best complete time partition             [ntimes]
            dimNames.format("%s ", time.getName());
            coordinateAtt.format("ref%s %s ", time.getName(), time.getName());
            break;

          default:
            throw new IllegalStateException("Uknown GribCollection TYpe = " + gctype);
        }

        // do other (vert, ens) coordinates
        for (Coordinate coord : vindex.getCoordinates()) {
          if (coord instanceof CoordinateTimeAbstract || coord instanceof CoordinateRuntime) {
            continue;
          }
          String name = coord.getName().toLowerCase();
          dimNames.format("%s ", name);
          coordinateAtt.format("%s ", name);
        }
        // do horiz coordinates
        dimNames.format("%s", horizDims);
        coordinateAtt.format("%s ", horizDims);

        String vname = makeVariableName(vindex);
        Variable v = new Variable(ncfile, g, null, vname, DataType.FLOAT, dimNames.toString());
        ncfile.addVariable(g, v);

        String desc = makeVariableLongName(vindex);
        v.addAttribute(new Attribute(CDM.LONG_NAME, desc));
        v.addAttribute(new Attribute(CDM.UNITS, makeVariableUnits(vindex)));

        GribTables.Parameter gp = getParameter(vindex);
        if (gp != null) {
          if (gp.getDescription() != null) {
            v.addAttribute(new Attribute(CDM.DESCRIPTION, gp.getDescription()));
          }
          if (gp.getAbbrev() != null) {
            v.addAttribute(new Attribute(CDM.ABBREV, gp.getAbbrev()));
          }
          v.addAttribute(new Attribute(CDM.MISSING_VALUE, gp.getMissing()));
          if (gp.getFill() != null) {
            v.addAttribute(new Attribute(CDM.FILL_VALUE, gp.getFill()));
          }
        } else {
          v.addAttribute(new Attribute(CDM.MISSING_VALUE, Float.NaN));
        }

        // horiz coord system
        if (isLatLon2D) { // special case of "LatLon Orthogononal"
          String s = searchCoord(Grib2Utils.getLatLon2DcoordType(desc), group.variList);
          if (s == null) { // its a 2D lat/lon coordinate
            v.setDimensions(horizDims); // LOOK make this 2D and munge the units
            String units = desc.contains("Latitude of") ? CDM.LAT_UNITS : CDM.LON_UNITS;
            v.addAttribute(new Attribute(CDM.UNITS, units));

          } else { // its a variable using the coordinates described by s
            coordinateAtt.format("%s ", s);
          }
        } else {
          v.addAttribute(new Attribute(CF.GRID_MAPPING, grid_mapping));
        }
        v.addAttribute(new Attribute(CF.COORDINATES, coordinateAtt.toString()));

        // statistical interval type
        if (vindex.getIntvType() >= 0) {
          // LOOK find the time coordinate
          GribStatType statType = gribTable.getStatType(vindex.getIntvType());
          if (statType != null) {
            v.addAttribute(new Attribute(Grib.GRIB_STAT_TYPE, statType.toString()));
            CF.CellMethods cm = GribStatType.getCFCellMethod(statType);
            Coordinate timeCoord = vindex.getCoordinate(Coordinate.Type.timeIntv);
            if (cm != null && timeCoord != null) {
              v.addAttribute(
                  new Attribute(CF.CELL_METHODS, timeCoord.getName() + ": " + cm.toString()));
            }
          } else {
            v.addAttribute(new Attribute(Grib.GRIB_STAT_TYPE, vindex.getIntvType()));
          }
        }

        gribCollection.addVariableAttributes(v, vindex);
        v.setSPobject(vindex);
      }
    }
  }

  private void makeRuntimeCoordinate(NetcdfFile ncfile, Group g, CoordinateRuntime rtc) {
    int n = rtc.getSize();
    boolean isScalar = (n == 1);      // this is the case of runtime[1]
    String tcName = rtc.getName();
    String dims = isScalar ? null : rtc.getName();  // null means scalar
    if (!isScalar) {
      ncfile.addDimension(g, new Dimension(tcName, n));
    }

    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, dims));
    v.addAttribute(new Attribute(CDM.UNITS, rtc.getUnit()));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME_REFERENCE));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_RUNTIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    /* String vsName = tcName + "_ISO";
    Variable vs = ncfile.addVariable(g, new Variable(ncfile, g, null, vsName, DataType.STRING, dims));
    vs.addAttribute(new Attribute(CDM.UNITS, "ISO8601"));
    v.addAttribute(new Attribute(CDM.LONG_NAME, "GRIB reference time"));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    // coordinate values
    String[] dataS = new String[n];
    int count = 0;
    for (CalendarDate val : rtc.getRuntimesSorted()) {
      dataS[count++] = val.toString();
    }
    vs.setCachedData(Array.factory(DataType.STRING, isScalar ? new int[0] : new int[]{n}, dataS)); */

    // lazy eval
    v.setSPobject(new Time2Dinfo(Time2DinfoType.reftime, null, rtc));
  }

  private enum Time2DinfoType {off, offU, intv, intvU, bounds, boundsU, is1Dtime, isUniqueRuntime, reftime, timeAuxRef}

  private static class Time2Dinfo {

    final Time2DinfoType which;
    final CoordinateTime2D time2D;
    final Coordinate time1D;

    private Time2Dinfo(Time2DinfoType which, CoordinateTime2D time2D, Coordinate time1D) {
      this.which = which;
      this.time2D = time2D;
      this.time1D = time1D;
    }
  }

  // time coordinates are unique
  // time(nruns, ntimes) -> time(ntimes) with dependent reftime(ntime) coordinate
  private void makeUniqueTimeCoordinate2D(NetcdfFile ncfile, Group g, CoordinateTime2D time2D) {
    CoordinateRuntime runtime = time2D.getRuntimeCoordinate();

    int countU = 0;
    for (int run = 0; run < time2D.getNruns(); run++) {
      CoordinateTimeAbstract timeCoord = time2D.getTimeCoordinate(run);
      countU += timeCoord.getSize();
    }
    int ntimes = countU;
    String tcName = time2D.getName();

    ncfile.addDimension(g, new Dimension(tcName, ntimes));
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, tcName));
    String units = runtime.getUnit(); // + " since " + runtime.getFirstDate();
    v.addAttribute(new Attribute(CDM.UNITS, units));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_VALID_TIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    // the data is not generated until asked for to save space
    if (!time2D.isTimeInterval()) {
      v.setSPobject(new Time2Dinfo(Time2DinfoType.offU, time2D, null));
    } else {
      v.setSPobject(new Time2Dinfo(Time2DinfoType.intvU, time2D, null));
      // bounds for intervals
      String bounds_name = tcName + "_bounds";
      Variable bounds = ncfile.addVariable(g,
          new Variable(ncfile, g, null, bounds_name, DataType.DOUBLE, tcName + " 2"));
      v.addAttribute(new Attribute(CF.BOUNDS, bounds_name));
      bounds.addAttribute(new Attribute(CDM.UNITS, units));
      bounds.addAttribute(new Attribute(CDM.LONG_NAME, "bounds for " + tcName));
      bounds.setSPobject(new Time2Dinfo(Time2DinfoType.boundsU, time2D, null));
    }

    if (runtime.getNCoords() != 1) {
      // for this case we have to generate a separate reftime, because have to use the same dimension
      String refName = "ref" + tcName;
      if (g.findVariable(refName) == null) {
        Variable vref = ncfile
            .addVariable(g, new Variable(ncfile, g, null, refName, DataType.DOUBLE, tcName));
        vref.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME_REFERENCE));
        vref.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_RUNTIME));
        vref.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));
        vref.addAttribute(new Attribute(CDM.UNITS, units));
        vref.setSPobject(new Time2Dinfo(Time2DinfoType.isUniqueRuntime, time2D, null));
      }
    }
  }

  /* non unique time case
  3) time(nruns, ntimes) with reftime(nruns)
   */
  private void makeTimeCoordinate2D(NetcdfFile ncfile, Group g, CoordinateTime2D time2D,
      GribCollectionImmutable.Type gctype) {
    CoordinateRuntime runtime = time2D.getRuntimeCoordinate();

    int ntimes = time2D.getNtimes();
    String tcName = time2D.getName();
    String dims = runtime.getName() + " " + tcName;
    int dimLength = ntimes;

    ncfile.addDimension(g, new Dimension(tcName, dimLength));
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, dims));
    String units = runtime.getUnit(); // + " since " + runtime.getFirstDate();
    v.addAttribute(new Attribute(CDM.UNITS, units));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_VALID_TIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    // the data is not generated until asked for to save space
    if (!time2D.isTimeInterval()) {
      v.setSPobject(new Time2Dinfo(Time2DinfoType.off, time2D, null));
    } else {
      v.setSPobject(new Time2Dinfo(Time2DinfoType.intv, time2D, null));
      // bounds for intervals
      String bounds_name = tcName + "_bounds";
      Variable bounds = ncfile
          .addVariable(g, new Variable(ncfile, g, null, bounds_name, DataType.DOUBLE, dims + " 2"));
      v.addAttribute(new Attribute(CF.BOUNDS, bounds_name));
      bounds.addAttribute(new Attribute(CDM.UNITS, units));
      bounds.addAttribute(new Attribute(CDM.LONG_NAME, "bounds for " + tcName));
      bounds.setSPobject(new Time2Dinfo(Time2DinfoType.bounds, time2D, null));
    }

  }

  private Array makeLazyCoordinateData(Variable v2, Time2Dinfo info) {
    if (info.time2D != null) {
      return makeLazyTime2Darray(v2, info);
    } else {
      return makeLazyTime1Darray(v2, info);
    }
  }

  // only for the 2d times
  private Array makeLazyTime1Darray(Variable v2, Time2Dinfo info) {
    int length = info.time1D.getSize();
    double[] data = new double[length];
    for (int i = 0; i < length; i++) {
      data[i] = Double.NaN;
    }

    // coordinate values
    switch (info.which) {
      case reftime:
        CoordinateRuntime rtc = (CoordinateRuntime) info.time1D;
        int count = 0;
        for (double val : rtc.getOffsetsInTimeUnits()) {
          data[count++] = val;
        }
        return Array.factory(DataType.DOUBLE, v2.getShape(), data);

      case timeAuxRef:
        CoordinateTimeAbstract time = (CoordinateTimeAbstract) info.time1D;
        count = 0;
        List<Double> masterOffsets = gribCollection.getMasterRuntime().getOffsetsInTimeUnits();
        for (int masterIdx : time.getTime2runtime()) {
          data[count++] = masterOffsets.get(masterIdx - 1);
        }
        return Array.factory(DataType.DOUBLE, v2.getShape(), data);

      default:
        throw new IllegalStateException("makeLazyTime1Darray must be reftime or timeAuxRef");
    }
  }

  // only for the 2d times
  private Array makeLazyTime2Darray(Variable coord, Time2Dinfo info) {
    CoordinateTime2D time2D = info.time2D;
    CalendarPeriod timeUnit = time2D.getTimeUnit();

    int nruns = time2D.getNruns();
    int ntimes = time2D.getNtimes();

    int length = (int) coord.getSize();
    if (info.which == Time2DinfoType.bounds) {
      length *= 2;
    }

    double[] data = new double[length];
    for (int i = 0; i < length; i++) {
      data[i] = Double.NaN;
    }
    int count;

    // coordinate values
    switch (info.which) {
      case off:
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTime coordTime = (CoordinateTime) time2D.getTimeCoordinate(runIdx);
          int timeIdx = 0;
          for (int val : coordTime.getOffsetSorted()) {
            data[runIdx * ntimes + timeIdx] = timeUnit.getValue() * val + time2D.getOffset(runIdx);
            timeIdx++;
          }
        }
        break;

      case offU:
        count = 0;
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTime coordTime = (CoordinateTime) time2D.getTimeCoordinate(runIdx);
          for (int val : coordTime.getOffsetSorted()) {
            data[count++] = timeUnit.getValue() * val + time2D.getOffset(runIdx);
          }
        }
        break;

      case intv:
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTimeIntv timeIntv = (CoordinateTimeIntv) time2D.getTimeCoordinate(runIdx);
          int timeIdx = 0;
          for (TimeCoordIntvValue tinv : timeIntv.getTimeIntervals()) {
            data[runIdx * ntimes + timeIdx] = timeUnit.getValue() * tinv.getBounds2() + time2D
                .getOffset(runIdx); // use upper bounds for coord value
            timeIdx++;
          }
        }
        break;

      case intvU:
        count = 0;
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTimeIntv timeIntv = (CoordinateTimeIntv) time2D.getTimeCoordinate(runIdx);
          for (TimeCoordIntvValue tinv : timeIntv.getTimeIntervals()) {
            data[count++] = timeUnit.getValue() * tinv.getBounds2() + time2D
                .getOffset(runIdx); // use upper bounds for coord value
          }
        }
        break;

      case is1Dtime:
        CoordinateRuntime runtime = time2D.getRuntimeCoordinate();
        count = 0;
        for (double val : runtime.getOffsetsInTimeUnits()) { // convert to udunits
          data[count++] = val;
        }
        break;

      case isUniqueRuntime:  // the aux runtime coordinate
        CoordinateRuntime runtimeU = time2D.getRuntimeCoordinate();
        List<Double> runOffsets = runtimeU.getOffsetsInTimeUnits();
        count = 0;
        for (int run = 0; run < time2D.getNruns(); run++) {
          CoordinateTimeAbstract timeCoord = time2D.getTimeCoordinate(run);
          for (int time = 0; time < timeCoord.getNCoords(); time++) {
            data[count++] = runOffsets.get(run);
          }
        }
        break;

      case bounds:
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTimeIntv timeIntv = (CoordinateTimeIntv) time2D.getTimeCoordinate(runIdx);
          int timeIdx = 0;
          for (TimeCoordIntvValue tinv : timeIntv.getTimeIntervals()) {
            data[runIdx * ntimes * 2 + timeIdx] =
                timeUnit.getValue() * tinv.getBounds1() + time2D.getOffset(runIdx);
            data[runIdx * ntimes * 2 + timeIdx + 1] =
                timeUnit.getValue() * tinv.getBounds2() + time2D.getOffset(runIdx);
            timeIdx += 2;
          }
        }
        break;

      case boundsU:
        count = 0;
        for (int runIdx = 0; runIdx < nruns; runIdx++) {
          CoordinateTimeIntv timeIntv = (CoordinateTimeIntv) time2D.getTimeCoordinate(runIdx);
          for (TimeCoordIntvValue tinv : timeIntv.getTimeIntervals()) {
            data[count++] = timeUnit.getValue() * tinv.getBounds1() + time2D.getOffset(runIdx);
            data[count++] = timeUnit.getValue() * tinv.getBounds2() + time2D.getOffset(runIdx);
          }
        }
        break;

      default:
        throw new IllegalStateException();
    }

    return Array.factory(DataType.DOUBLE, coord.getShape(), data);
  }

  private void makeTimeCoordinate1D(NetcdfFile ncfile, Group g,
      CoordinateTime coordTime) { //}, CoordinateRuntime runtime) {
    int ntimes = coordTime.getSize();
    String tcName = coordTime.getName();
    String dims = coordTime.getName();
    ncfile.addDimension(g, new Dimension(tcName, ntimes));
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, dims));
    String units = coordTime.getTimeUdUnit();
    v.addAttribute(new Attribute(CDM.UNITS, units));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_VALID_TIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    double[] data = new double[ntimes];
    int count = 0;

    // coordinate values
    for (int val : coordTime.getOffsetSorted()) {
      data[count++] = val;
    }
    v.setCachedData(Array.factory(DataType.DOUBLE, new int[]{ntimes}, data));

    makeTimeAuxReference(ncfile, g, tcName, units, coordTime);
  }

  private void makeTimeAuxReference(NetcdfFile ncfile, Group g, String timeName, String units,
      CoordinateTimeAbstract time) {
    if (time.getTime2runtime() == null) {
      return;
    }
    String tcName = "ref" + timeName;
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, timeName));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME_REFERENCE));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_RUNTIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));
    v.addAttribute(new Attribute(CDM.UNITS, units));

    // lazy evaluation
    v.setSPobject(new Time2Dinfo(Time2DinfoType.timeAuxRef, null, time));
  }

  private void makeTimeCoordinate1D(NetcdfFile ncfile, Group g,
      CoordinateTimeIntv coordTime) { // }, CoordinateRuntime runtime) {
    int ntimes = coordTime.getSize();
    String tcName = coordTime.getName();
    String dims = coordTime.getName();
    ncfile.addDimension(g, new Dimension(tcName, ntimes));
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, tcName, DataType.DOUBLE, dims));
    String units = coordTime.getTimeUdUnit();
    v.addAttribute(new Attribute(CDM.UNITS, units));
    v.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    v.addAttribute(new Attribute(CDM.LONG_NAME, Grib.GRIB_VALID_TIME));
    v.addAttribute(new Attribute(CF.CALENDAR, Calendar.proleptic_gregorian.toString()));

    double[] data = new double[ntimes];
    int count = 0;

    // use upper bounds for coord value
    for (TimeCoordIntvValue tinv : coordTime.getTimeIntervals()) {
      data[count++] = tinv.getBounds2();
    }
    v.setCachedData(Array.factory(DataType.DOUBLE, new int[]{ntimes}, data));

    // bounds
    String bounds_name = tcName + "_bounds";
    Variable bounds = ncfile
        .addVariable(g, new Variable(ncfile, g, null, bounds_name, DataType.DOUBLE, dims + " 2"));
    v.addAttribute(new Attribute(CF.BOUNDS, bounds_name));
    bounds.addAttribute(new Attribute(CDM.UNITS, units));
    bounds.addAttribute(new Attribute(CDM.LONG_NAME, "bounds for " + tcName));

    data = new double[ntimes * 2];
    count = 0;
    for (TimeCoordIntvValue tinv : coordTime.getTimeIntervals()) {
      data[count++] = tinv.getBounds1();
      data[count++] = tinv.getBounds2();
    }
    bounds.setCachedData(Array.factory(DataType.DOUBLE, new int[]{ntimes, 2}, data));

    makeTimeAuxReference(ncfile, g, tcName, units, coordTime);
  }

  private void makeVerticalCoordinate(NetcdfFile ncfile, Group g, CoordinateVert vc) {
    int n = vc.getSize();
    String vcName = vc.getName().toLowerCase();

    ncfile.addDimension(g, new Dimension(vcName, n));
    Variable v = ncfile
        .addVariable(g, new Variable(ncfile, g, null, vcName, DataType.FLOAT, vcName));
    if (vc.getUnit() != null) {
      v.addAttribute(new Attribute(CDM.UNITS, vc.getUnit()));
      String desc = getVerticalCoordDesc(vc.getCode());
      if (desc != null) {
        v.addAttribute(new Attribute(CDM.LONG_NAME, desc));
      }
      v.addAttribute(
          new Attribute(CF.POSITIVE, vc.isPositiveUp() ? CF.POSITIVE_UP : CF.POSITIVE_DOWN));
    }

    v.addAttribute(new Attribute("Grib_level_type", vc.getCode()));
    VertCoordType vu = vc.getVertUnit();
    if (vu != null) {
      if (vu.getDatum() != null) {
        v.addAttribute(new Attribute("datum", vu.getDatum()));
      }
    }

    if (vc.isLayer()) {
      float[] data = new float[n];
      int count = 0;
      for (VertCoordValue val : vc.getLevelSorted()) {
        data[count++] = (float) (val.getValue1() + val.getValue2()) / 2;
      }
      v.setCachedData(Array.factory(DataType.FLOAT, new int[]{n}, data));

      Variable bounds = ncfile.addVariable(g,
          new Variable(ncfile, g, null, vcName + "_bounds", DataType.FLOAT, vcName + " 2"));
      v.addAttribute(new Attribute(CF.BOUNDS, vcName + "_bounds"));
      String vcUnit = vc.getUnit();
      if (vcUnit != null) {
        bounds.addAttribute(new Attribute(CDM.UNITS, vcUnit));
      }
      bounds.addAttribute(new Attribute(CDM.LONG_NAME, "bounds for " + vcName));

      data = new float[2 * n];
      count = 0;
      for (VertCoordValue level : vc.getLevelSorted()) {
        data[count++] = (float) level.getValue1();
        data[count++] = (float) level.getValue2();
      }
      bounds.setCachedData(Array.factory(DataType.FLOAT, new int[]{n, 2}, data));

    } else {
      float[] data = new float[n];
      int count = 0;
      for (VertCoordValue val : vc.getLevelSorted()) {
        data[count++] = (float) val.getValue1();
      }
      v.setCachedData(Array.factory(DataType.FLOAT, new int[]{n}, data));
    }
  }

  private void makeEnsembleCoordinate(NetcdfFile ncfile, Group g, CoordinateEns ec) {
    int n = ec.getSize();
    String ecName = ec.getName().toLowerCase();
    ncfile.addDimension(g, new Dimension(ecName, n));

    Variable v = new Variable(ncfile, g, null, ecName, DataType.INT, ecName);
    ncfile.addVariable(g, v);
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Ensemble.toString()));

    int[] data = new int[n];
    int count = 0;
    for (EnsCoordValue ecc : ec.getEnsSorted()) {
      data[count++] = ecc.getEnsMember();
    }
    v.setCachedData(Array.factory(DataType.INT, new int[]{n}, data));
  }

  @Nullable
  private String searchCoord(Grib2Utils.LatLonCoordType type, List<GribCollectionImmutable.VariableIndex> list) {
    if (type == null) {
      return null;
    }

    GribCollectionImmutable.VariableIndex lat, lon;
    switch (type) {
      case U:
        lat = findParameter(list, 198);
        lon = findParameter(list, 199);
        return (lat != null && lon != null) ? makeVariableName(lat) + " " + makeVariableName(lon)
            : null;
      case V:
        lat = findParameter(list, 200);
        lon = findParameter(list, 201);
        return (lat != null && lon != null) ? makeVariableName(lat) + " " + makeVariableName(lon)
            : null;
      case P:
        lat = findParameter(list, 202);
        lon = findParameter(list, 203);
        return (lat != null && lon != null) ? makeVariableName(lat) + "  " + makeVariableName(lon)
            : null;
    }
    return null;
  }

  @Nullable
  private GribCollectionImmutable.VariableIndex findParameter(List<GribCollectionImmutable.VariableIndex> list, int p) {
    for (GribCollectionImmutable.VariableIndex vindex : list) {
      if ((vindex.getDiscipline() == 0) && (vindex.getCategory() == 2) && (vindex.getParameter() == p)) {
        return vindex;
      }
    }
    return null;
  }

  @Override
  public void close() throws java.io.IOException {
    if (!owned && gribCollection != null) // LOOK klugerino
    {
      gribCollection.close();
    }
    gribCollection = null;
    super.close();
  }

  @Override
  public String getDetailInfo() {
    Formatter f = new Formatter();
    f.format("%s", super.getDetailInfo());
    if (gribCollection != null) {
      gribCollection.showIndex(f);
    }
    return f.toString();
  }

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

  @Override
  public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {
    long start = System.currentTimeMillis();

    // see if its time2D - then generate data on the fly
    if (v2.getSPobject() instanceof Time2Dinfo) {
      Time2Dinfo info = (Time2Dinfo) v2.getSPobject();
      Array data = makeLazyCoordinateData(v2, info);
      Section sectionFilled = Section.fill(section, v2.getShape());
      return data.sectionNoReduce(sectionFilled.getRanges());
    }

    try {
      Array result;
      GribCollectionImmutable.VariableIndex vindex = (GribCollectionImmutable.VariableIndex) v2
          .getSPobject();
      GribDataReader dataReader = GribDataReader.factory(gribCollection, vindex);
      SectionIterable sectionIter = new SectionIterable(section, v2.getShape());
      result = dataReader.readData(sectionIter);

      long took = System.currentTimeMillis() - start;
      return result;

    } catch (IOException ioe) {
      logger.error("Failed to readData ", ioe);
      throw ioe;
    }
  }

  /* private Array readDataFromCollectionNew(Variable v, Section section) throws IOException, InvalidRangeException {
    GribCollectionImmutable.VariableIndex vindex = (GribCollectionImmutable.VariableIndex) v.getSPobject();
    GribDataReader dataReader = GribDataReader.factory(gribCollection, vindex);
    return dataReader.readDataFromCollection(vindex, section, v.getShape());
  }

  private Array readDataFromCollection(Variable v, Section section, WritableByteChannel channel) throws IOException, InvalidRangeException {
    GribCollectionImmutable.VariableIndex vindex = (GribCollectionImmutable.VariableIndex) v.getSPobject();

    // first time, read records and keep in memory
    vindex.readRecords();

    int sectionLen = section.getRank();
    Range yRange = section.getRange(sectionLen - 2);
    Range xRange = section.getRange(sectionLen - 1);

    Section sectionWanted = section.subSection(0, sectionLen - 2); // all but x, y
    Section.Iterator iterWanted = sectionWanted.getIterator(v.getShape());
    int[] indexWanted = new int[sectionLen - 2];                              // place to put the iterator result

    // collect all the records that need to be read
    // this assumes that the coordinates reflect the vindex.sparseArray exactly
    GribDataReader dataReader = GribDataReader.factory(gribCollection, vindex);
    int count = 0;
    while (iterWanted.hasNext()) {
      int sourceIndex = iterWanted.next(indexWanted);
      dataReader.addRecord(sourceIndex, count++);
    }

    // sort by file and position, then read
    GribDataReader.DataReceiverIF dataReceiver = (channel == null) ? new GribDataReader.DataReceiver(section) : new GribDataReader.ChannelReceiver(channel, yRange, xRange);
    dataReader.read(dataReceiver);
    return dataReceiver.getArray();
  }

  private Array readDataFromPartition(Variable v, Section section, WritableByteChannel channel) throws IOException, InvalidRangeException {
    PartitionCollectionImmutable.VariableIndexPartitioned vindexP = (PartitionCollectionImmutable.VariableIndexPartitioned) v.getSPobject(); // the variable in the partition collection

    int sectionLen = section.getRank();
    Range yRange = section.getRange(sectionLen - 2);  // last 2
    Range xRange = section.getRange(sectionLen - 1);
    Section sectionWanted = section.subSection(0, sectionLen - 2); // all but x, y
    Section.Iterator iterWanted = sectionWanted.getIterator(v.getShape());  // iterator over wanted indices in vindexP
    int[] indexWanted = new int[sectionLen - 2];                              // place to put the iterator result
    int[] useIndex = indexWanted;

    // collect all the records that need to be read
    GribDataReader dataReader = GribDataReader.factory(gribCollection, vindexP);
    int resultPos = 0;
    while (iterWanted.hasNext()) {
      iterWanted.next(indexWanted);   // returns the vindexP index in indexWanted array

      // for 1D TP, second index is implictly 0
      if (vindexP.getType() == GribCollectionImmutable.Type.TP) {
        int[] indexReallyWanted = new int[indexWanted.length+1];
        indexReallyWanted[0] = indexWanted[0];
        indexReallyWanted[1] = 0;
        System.arraycopy(indexWanted, 1, indexReallyWanted, 2, indexWanted.length-1);
        useIndex = indexReallyWanted;
      }

      PartitionCollectionImmutable.DataRecord record = vindexP.getDataRecord(useIndex);
      if (record == null) {
        // vindexP.getDataRecord(indexWanted); // debug
        resultPos++;
        continue;
      }
      record.resultIndex = resultPos;
      dataReader.addPartitionedRecord(record);
      resultPos++;
    }

    // sort by file and position, then read
    GribDataReader.DataReceiverIF dataReceiver = (channel == null) ? new GribDataReader.DataReceiver(section) : new GribDataReader.ChannelReceiver(channel, yRange, xRange);
    dataReader.readPartitioned(dataReceiver);

    return dataReceiver.getArray();
  }

  /* LOOK this is by Variable - might want to do over variables, so only touch a file once, if multiple variables in a file
  @Override
  public long streamToByteChannel(ucar.nc2.Variable v2, Section section, WritableByteChannel channel)
          throws java.io.IOException, ucar.ma2.InvalidRangeException {

    long start = System.currentTimeMillis();

    /* if (isPartitioned)
      streamDataFromPartition(v2, section, channel);
    else
    readDataFromCollection(v2, section, channel);

    long took = System.currentTimeMillis() - start;
    return 0;
  } */

  ///////////////////////////////////////
  // debugging back door
  public abstract Object getLastRecordRead();

  public abstract void clearLastRecordRead();

  public abstract Object getGribCustomizer();
}


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

package ucar.nc2.iosp.bufr.writer;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import ucar.nc2.constants.CDM;
import ucar.nc2.iosp.bufr.*;
import ucar.unidata.io.InMemoryRandomAccessFile;
import ucar.unidata.io.RandomAccessFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Formatter;

/**
 * Reads BUFR files and splits them into separate files based on Message.hashCode()
 *
 * @author caron
 * @since 6/12/13
 */
public class BufrSplitter {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BufrSplitter.class);
  private static final ucar.unidata.io.KMPMatch matcher = new ucar.unidata.io
          .KMPMatch(new byte[]{'B', 'U', 'F', 'R'});

  File dirOut;
  MessageDispatchDDS dispatcher;
  Formatter out;

  public BufrSplitter(String dirName, Formatter out) throws IOException {
    this.out = out;
    dirOut = new File(dirName);
    if (dirOut.exists() && !dirOut.isDirectory())  {
      throw new IllegalArgumentException(dirOut+" must be a directory");
    } else if (!dirOut.exists()) {
      if (!dirOut.mkdirs())
        throw new IllegalArgumentException(dirOut+" filed to create");
    }
    dispatcher = new MessageDispatchDDS(null, dirOut);
  }

  // LOOK - needs to be a directory, or maybe an MFILE collection
  public void execute(String filename) throws IOException {
    File input = new File(filename);
    out.format("BufrSplitter on %s length=%d%n", input.getPath(), input.length());
    try (InputStream is = new FileInputStream(input)) {
      processStream(is);
    }
  }

  public void exit() {
    dispatcher.exit(out);
  }

  //////////////////////////////////////////////////////
  // Step 1 - read and extract a Bufr Message

  int total_msgs = 0;
  int bad_msgs = 0;

  // process all the bytes in the stream
  public void processStream(InputStream is) throws IOException {
    int pos = -1;
    Buffer b = null;
    while (true) {
      b = (pos < 0) ? readBuffer(is) : readBuffer(is, b, pos);
      pos = processBuffer(b, is);
      if (b.done) break;
    }
  }

  /**
   *
   * @param b buffer of input data
   * @param is InputStream to read
   * @return pos in the buffer we got to
   * @throws IOException
   */
  private int processBuffer(Buffer b, InputStream is) throws IOException {
    int start = 0;
    while (start < b.have) {
      int matchPos = matcher.indexOf(b.buff, start, b.have - start);

      // didnt find "BUFR" match
      if (matchPos < 0) {
        if (start == 0) // discard all but last 3 bytes
          return b.have - 3;
        else
          return start; // indicates part of the buffer thats not processed
      }

      // do we have the length already read ??
      if (matchPos + 6 >= b.have) {
        return start; // this will save the end of the buffer and read more in.
      }

      // read BUFR message length
      int b1 = (b.buff[matchPos + 4] & 0xff);
      int b2 = (b.buff[matchPos + 5] & 0xff);
      int b3 = (b.buff[matchPos + 6] & 0xff);
      int messLen = b1 << 16 | b2 << 8 | b3;
      System.out.println("match at=" + matchPos + " len= " + messLen);

      // create a task for this message
      //int headerLen = matchPos - start;
      MessageTask task = new MessageTask(messLen);
      task.extractHeader(start, matchPos, b);

      // copy message bytes into it
      int last = matchPos + messLen;
      if (last > b.have) {
        task.have = b.have - matchPos;
        System.arraycopy(b.buff, matchPos, task.mess, 0, task.have);

        // read the rest of the message
        if (!readBuffer(is, task.mess, task.have, task.len - task.have)) {
          logger.warn("Failed to read remaining BUFR message");
          break;
        }

      } else {
        task.have = task.len;
        System.arraycopy(b.buff, matchPos, task.mess, 0, task.have);
      }

      boolean ok = true;

      // check on ending
      for (int i = task.len - 4; i < task.len; i++) {
        int bb = task.mess[i];
        if (bb != 55) {
          //System.out.println("Missing End of BUFR message at pos=" + i + " " + bb);
          ok = false;
          bad_msgs++;
        }
      }

      if (ok) processMessageTask(task);
      total_msgs++;

      start = matchPos + messLen + 1;
    }

    return -1;
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  // extract and parse a BUFR message so that we know its DDS type

  private static class MessageTask {
    byte[] mess;
    int len, have;
    String header;

    MessageTask(int messlen) {
      this.len = messlen;
      this.mess = new byte[messlen];
      this.have = 0;
    }

    void extractHeader(int startScan, int messagePos, Buffer buff) {
      int sizeHeader = messagePos - startScan;
      if (sizeHeader > 30) sizeHeader = 30;
      byte[] headerb = new byte[sizeHeader];
      int startHeader = messagePos - sizeHeader;
      System.arraycopy(buff.buff, startHeader, headerb, 0, sizeHeader);

      // cleanup
      int start;
      for (start = 0; start < headerb.length; start++) {
        byte b = headerb[start];
        if ((b == 73) || (b == 74)) // I or J
          break;
      }

      byte[] bb = new byte[sizeHeader];
      int count = 0;
      for (int i = start; i < headerb.length; i++) {
        byte b = headerb[i];
        if (b >= 32 && b < 127) // ascii only
          bb[count++] = b;
      }
      header = new String(bb, 0, count, CDM.utf8Charset);
    }

  }

  void processMessageTask(MessageTask mtask) {
    //out.format("    %d start process %n", mtask.id);
    try {
      Message m = getMessage(new InMemoryRandomAccessFile("BUFR", mtask.mess));
      if (null == m) return;
      m.setHeader(mtask.header);
      m.setRawBytes(mtask.mess);

      // decide what to do with the message
      dispatcher.dispatch(m);

    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public Message getMessage(RandomAccessFile raf) throws IOException {
    raf.seek(4);
    BufrIndicatorSection is = new BufrIndicatorSection(raf);
    BufrIdentificationSection ids = new BufrIdentificationSection(raf, is);
    BufrDataDescriptionSection dds = new BufrDataDescriptionSection(raf);

    long dataPos = raf.getFilePointer();
    int dataLength = BufrNumbers.uint3(raf);
    BufrDataSection dataSection = new BufrDataSection(dataPos, dataLength);

    if ((is.getBufrEdition() > 4) || (is.getBufrEdition() < 2)) {
      logger.warn("Edition " + is.getBufrEdition() + " is not supported");
      bad_msgs++;
      return null;
    }

    return new Message(raf, is, ids, dds, dataSection);
  }

  /////////////////////////////////////////////////////////////////////////////
  // efficiently read from an input stream

  private boolean showRead = false;
  private long bytesRead = 0;
  private int BUFFSIZE = 15000;

  private class Buffer {
    byte[] buff;
    int have;
    boolean done;

    Buffer() {
      buff = new byte[BUFFSIZE];
      have = 0;
      done = false;
    }
  }

  // read into dest byte array, until buffer is full or end of stream
  private boolean readBuffer(InputStream is, byte[] dest, int start, int want) throws IOException {
    int done = 0;
    while (done < want) {
      int got = is.read(dest, start + done, want - done);
      if (got < 0)
        return false;
      done += got;
    }

    if (showRead) System.out.println("Read buffer at " + bytesRead + " len=" + done);
    bytesRead += done;
    return true;
  }

  // read into new Buffer, until buffer is full or end of stream
  private Buffer readBuffer(InputStream is) throws IOException {
    Buffer b = new Buffer();
    int want = BUFFSIZE;
    while (b.have < want) {
      int got = is.read(b.buff, b.have, want - b.have);
      if (got < 0) {
        b.done = true;
        break;
      }
      b.have += got;
    }
    if (showRead) System.out.println("Read buffer at " + bytesRead + " len=" + b.have);
    bytesRead += b.have;
    return b;
  }

  // read into new Buffer, until buffer is full or end of stream
  private Buffer readBuffer(InputStream is, Buffer prev, int pos) throws IOException {
    Buffer b = new Buffer();

    // copy remains of last buffer here
    int remain = prev.have - pos;
    //if (remain > BUFFSIZE /2)
    //  out.format(" remain = "+remain+" bytesRead="+bytesRead);

    System.arraycopy(prev.buff, pos, b.buff, 0, remain);
    b.have = remain;

    int want = BUFFSIZE;
    while (b.have < want) {
      int got = is.read(b.buff, b.have, want - b.have);
      if (got < 0) {
        b.done = true;
        break;
      }
      b.have += got;
    }

    if (showRead) System.out.println("Read buffer at " + bytesRead + " len=" + b.have);
    bytesRead += b.have;
    return b;
  }

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

  private static class CommandLine {
    @Parameter(names = {"--fileSpec"}, description = "File specification", required = true)
    public File fileSpec;

    @Parameter(names = {"--dirOut"}, description = "Output directory", required = true)
    public File dirOut;

    @Parameter(names = {"-h", "--help"}, description = "Display this help and exit", help = true)
    public boolean help = false;

    private final JCommander jc;

    public CommandLine(String progName, String[] args) throws ParameterException {
      this.jc = new JCommander(this, args);  // Parses args and uses them to initialize *this*.
      jc.setProgramName(progName);           // Displayed in the usage information.
    }

    public void printUsage() {
      jc.usage();
    }
  }

  public static void main(String[] args) throws Exception {
    String progName = "BufrSpitter";

    try {
      CommandLine cmdLine = new CommandLine(progName, args);

      if (cmdLine.help) {
        cmdLine.printUsage();
        return;
      }

      BufrSplitter splitter = new BufrSplitter(cmdLine.dirOut.getAbsolutePath(), new Formatter(System.out));
      splitter.execute(cmdLine.fileSpec.getAbsolutePath());
      splitter.exit();
    } catch (ParameterException e) {
      System.err.println(e.getMessage());
      System.err.printf("Try \"%s --help\" for more information.%n", progName);
    }
  }
}


/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2010, OPeNDAP, Inc.
// Copyright (c) 2002,2003 OPeNDAP, Inc.
//
// Author: James Gallagher <jgallagher@opendap.org>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms,
// with or without modification, are permitted provided
// that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
//
// - Neither the name of the OPeNDAP nor the names of its contributors may
//   be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/////////////////////////////////////////////////////////////////////////////

package opendap.dap;

import java.io.*;

/**
 * The HeaderInputStream filters the input to only read lines of text until
 * the "Data:" line.  This is required because overzealous buffering in the
 * DDSParser will read the data as well as the DDS otherwise.
 *
 * @author jehamby
 * @version $Revision: 15901 $
 * @see DConnect
 */
public class HeaderInputStream extends FilterInputStream {
    /**
     * Each line is buffered here.
     */
    private byte lineBuf[];

    /**
     * Number of bytes remaining in buffer.
     */
    private int bytesRemaining;

    /**
     * Current buffer offset.
     */
    private int currentOffset;

    /**
     * End sequence to look for: "\nData:\n"
     */
    private byte[] endSequence = {(byte) '\n', (byte) 'D', (byte) 'a', (byte) 't',
            (byte) 'a', (byte) ':', (byte) '\n'};

    /**
     * Flag when end sequence has been found
     */
    private boolean endFound;

    /**
     * Construct a new HeaderInputStream.
     */
    public HeaderInputStream(InputStream in) {
        super(in);
        lineBuf = new byte[4096];
        bytesRemaining = currentOffset = 0;
        endFound = false;

        //LogStream.out.print("EndSequence: ");
        //for(int i=0; i<endSequence.length ;i++)
        //    LogStream.out.print(endSequence[i]+"  ");
        //LogStream.out.print("\n");
    }

    /**
     * Return the number of bytes in the buffer.
     */
    public int available() {
        return bytesRemaining;
    }

    /**
     * Returns that we don't support the mark() and reset() methods.
     */
    public boolean markSupported() {
        return false;
    }

    /**
     * Reads a single byte of data
     */
    public int read() throws IOException {
        // if the buffer is empty, get more bytes
        if (bytesRemaining == 0 && !endFound)
            getMoreBytes();
        // if the buffer is still empty, return EOF
        if (bytesRemaining == 0)
            return -1;
        else {
            bytesRemaining--;
            return lineBuf[currentOffset++];
        }
    }

    /**
     * Get more bytes into buffer.  Stop when endSequence is found.
     */
    private void getMoreBytes() throws IOException {
        currentOffset = 0;   // reset current array offset to 0
        int bytesRead = 0;   // bytes read so far
        int lookingFor = 0;  // character in endSequence to look for
        for (; bytesRead < lineBuf.length; bytesRead++) {
            int c = in.read();
            if (c == -1)
                break;  // break on EOL and return what we have so far

            lineBuf[bytesRead] = (byte) c;
            if (lineBuf[bytesRead] == endSequence[lookingFor]) {
                lookingFor++;
                if (lookingFor == endSequence.length) {
                    endFound = true;
                    break;
                }
            } else if (lineBuf[bytesRead] == endSequence[0]) { // CHANGED JC
                lookingFor = 1;
            } else {
                lookingFor = 0;
            }
        }
        bytesRemaining = bytesRead;  // number of bytes we've read
    }

    /**
     * Reads up to len bytes of data from this input stream into an array of
     * bytes. This method blocks until some input is available.
     */
    public int read(byte b[], int off, int len) throws IOException {
        if (len <= 0) {
            return 0;
        }

        int c = read();
        if (c == -1)
            return -1;
        b[off] = (byte) c;

        // We've read one byte successfully, let's try for more
        int i = 1;
        try {
            for (; i < len; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte) c;
            }
        } catch (IOException e) {
        }
        return i;
    }

    /**
     * Skips over and discards n bytes of data from the input stream.
     */
    public long skip(long n) {
        if (bytesRemaining >= n) {
            bytesRemaining -= n;
            return n;
        } else {
            int oldBytesRemaining = bytesRemaining;
            bytesRemaining = 0;
            return oldBytesRemaining;
        }
    }
}


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

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.iosp.AbstractIOServiceProvider;
import ucar.atd.dorade.*;

import java.io.*;
import java.util.*;

/**
 * IOServiceProvider implementation abstract base class to read/write Dorade files
 */

public class Doradeiosp extends AbstractIOServiceProvider {

  protected Doradeheader headerParser;

  public DoradeSweep mySweep = null;

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

  public String getFileTypeId() {
    return "DORADE";
  }

  public String getFileTypeDescription() {
    return "DOppler RAdar Data Exchange Format";
  }

  /////////////////////////////////////////////////////////////////////////////
  // reading

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

    super.open(raf, ncfile, cancelTask);

    mySweep = new DoradeSweep(raf.getRandomAccessFile());
    headerParser = new Doradeheader();
    headerParser.read(mySweep, ncfile, null);

    ncfile.finish();
  }

  public Array readData(ucar.nc2.Variable v2, Section section) throws IOException, InvalidRangeException {

    Array outputData;
    int nSensor = mySweep.getNSensors();
    int nRays = mySweep.getNRays();

    if (v2.getShortName().equals("elevation")) {
      float[] elev = mySweep.getElevations();
      outputData = readData1(v2, section, elev);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), elev);
    } else if (v2.getShortName().equals("rays_time")) {
      Date[] dd = mySweep.getTimes();
      if (dd == null)
        throw new IllegalStateException("missing dates for "+v2.getShortName());
      double[] d = new double[dd.length];
      for (int i = 0; i < dd.length; i++)
        d[i] = (double) dd[i].getTime();
      outputData = readData2(v2, section, d);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), d);
    } else if (v2.getShortName().equals("azimuth")) {
      float[] azim = mySweep.getAzimuths();
      outputData = readData1(v2, section, azim);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), azim);
    } else if (v2.getShortName().startsWith("latitudes_")) {
      float[] allLats = new float[nSensor * nRays];
      float[] lats;
      for (int i = 0; i < nSensor; i++) {
        lats = mySweep.getLatitudes(i);
        System.arraycopy(lats, 0, allLats, i * nRays, nRays);
      }
      outputData = readData1(v2, section, allLats);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), allLats);
    } else if (v2.getShortName().startsWith("longitudes_")) {
      float[] allLons = new float[nSensor * nRays];
      float[] lons;
      for (int i = 0; i < nSensor; i++) {
        lons = mySweep.getLongitudes(i);
        System.arraycopy(lons, 0, allLons, i * nRays, nRays);
      }
      outputData = readData1(v2, section, allLons);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), lons);
    } else if (v2.getShortName().startsWith("altitudes_")) {
      float[] allAlts = new float[nSensor * nRays];
      float[] alts;
      for (int i = 0; i < nSensor; i++) {
        alts = mySweep.getAltitudes(i);
        System.arraycopy(alts, 0, allAlts, i * nRays, nRays);
      }
      outputData = readData1(v2, section, allAlts);
      //outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), alts);
    } else if (v2.getShortName().startsWith("distance_")) {
      float[] dist;
      int j = 0;
      for (int i = 0; i < nSensor; i++) {
        String t = "" + i;
        if (v2.getShortName().endsWith(t)) {
          j = i;
          break;
        }
      }
      int nc = mySweep.getNCells(j);
      Array data = Array.makeArray(DataType.FLOAT, nc,
          (double) mySweep.getRangeToFirstCell(j), (double) mySweep.getCellSpacing(j));
      dist = (float[]) data.get1DJavaArray(DataType.FLOAT);
      outputData = readData1(v2, section, dist);

    } else if (v2.isScalar()) {

      float d = 0.0f;

      if (v2.getShortName().equals("Range_to_First_Cell")) {
        d = mySweep.getRangeToFirstCell(0);
      } else if (v2.getShortName().equals("Cell_Spacing")) {
        d = mySweep.getCellSpacing(0);
      } else if (v2.getShortName().equals("Fixed_Angle")) {
        d = mySweep.getFixedAngle();
      } else if (v2.getShortName().equals("Nyquist_Velocity")) {
        d = mySweep.getUnambiguousVelocity(0);
      } else if (v2.getShortName().equals("Unambiguous_Range")) {
        d = mySweep.getunambiguousRange(0);
      } else if (v2.getShortName().equals("Radar_Constant")) {
        d = mySweep.getradarConstant(0);
      } else if (v2.getShortName().equals("rcvr_gain")) {
        d = mySweep.getrcvrGain(0);
      } else if (v2.getShortName().equals("ant_gain")) {
        d = mySweep.getantennaGain(0);
      } else if (v2.getShortName().equals("sys_gain")) {
        d = mySweep.getsystemGain(0);
      } else if (v2.getShortName().equals("bm_width")) {
        d = mySweep.gethBeamWidth(0);
      }
      float[] dd = new float[1];
      dd[0] = d;
      outputData = Array.factory(v2.getDataType(), v2.getShape(), dd);
    } else {
      Range radialRange = section.getRange(0);
      Range gateRange = section.getRange(1);

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

      DoradePARM dp = mySweep.lookupParamIgnoreCase(v2.getShortName());
      if (dp == null)
        throw new IllegalStateException("Cant find param "+v2.getShortName());
      int ncells = dp.getNCells();
      float[] rayValues = new float[ncells];
      /*
  float[] allValues = new float[nRays * ncells];
  for (int r = 0; r < nRays; r++) {
      try {
          rayValues = mySweep.getRayData(dp, r, rayValues);
      } catch (DoradeSweep.DoradeSweepException ex) {
           ex.printStackTrace();
      }
      System.arraycopy(rayValues, 0, allValues, r*ncells, ncells);
  }    */
      for (int radialIdx : radialRange) {
        try {
          rayValues = mySweep.getRayData(dp, radialIdx, rayValues);
        } catch (DoradeSweep.DoradeSweepException ex) {
          ex.printStackTrace();
        }
        for (int gateIdx : gateRange) {
          ii.setFloatNext(rayValues[gateIdx]);
        }

      }
      return data;
      //outputData = Array.factory( v2.getDataType(), v2.getShape(), allValues);
    }

    return outputData;
  }

  public Array readData1(Variable v2, Section section, float[] values) {
    Array data = Array.factory(v2.getDataType(), section.getShape());
    IndexIterator ii = data.getIndexIterator();
    Range radialRange = section.getRange(0);     // radial range can also be gate range

    for (int radialIdx : radialRange) {
      ii.setFloatNext(values[radialIdx]);
    }

    return data;
  }

  public Array readData2(Variable v2, Section section, double[] values) {
    Array data = Array.factory(v2.getDataType(), section.getShape());
    IndexIterator ii = data.getIndexIterator();
    Range radialRange = section.getRange(0);

    for (int radialIdx : radialRange) {
      ii.setDoubleNext(values[radialIdx]);
    }

    return data;
  }

}