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

package thredds.ui.catalog;

import thredds.client.catalog.*;
import thredds.client.catalog.tools.DatasetHtmlWriter;
import ucar.nc2.ui.widget.*;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.ComboBox;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URISyntaxException;
import java.util.Formatter;

/**
 * A Swing widget for THREDDS clients to access and choose from Dataset Inventory catalogs.
 * State is maintained in a ucar.util.Preferences store.
 *

 * A list of catalogs is kept in a ComboBox, and the user can choose from them and add new ones.
 * When a catalog is chosen, its contents are displayed in a CatalogTreeView. As the datasets
 * are browsed, the metadata is displayed in an HtmlBrowser widget.
 *

 * When a new dataset is selected, a java.beans.PropertyChangeEvent is thrown, see
 * addPropertyChangeListener.
 *

 * Use Example:
 *
 *

    // create widgets
    catalogChooser = new thredds.ui.CatalogChooser( prefs);
    catalogChooserDialog = catalogChooser.makeDialog(rootPaneContainer, "Open THREDDS dataset", true);

      // listen for selection
    catalogChooser.addPropertyChangeListener(  new java.beans.PropertyChangeListener() {
      public void propertyChange( java.beans.PropertyChangeEvent e) {
        if (e.getPropertyName().equals("Dataset")) {
          ..
        }
     }
   });

      // popup dialog
    catalogChooserDialog.show();
 *

 *
 * You can use the CatalogChooser alone, wrap it into a JDialog for popping up, or
 * use a ThreddsDatasetChooser instead, for a more complete interface.
 *
 * @see ThreddsDatasetChooser
 *
 * @author John Caron
 */

public class CatalogChooser extends JPanel {
  private static final String HDIVIDER = "HSplit_Divider";
  private static final String FILECHOOSER_DEFAULTDIR = "FileChooserDefaultDir";
  private PreferencesExt prefs;

  private String eventType = null;

    // ui
  private ComboBox catListBox;
  private CatalogTreeView tree;
  private HtmlBrowser htmlViewer;
  private FileManager fileChooser;

  private JSplitPane split;
  private JLabel statusLabel;
  private JPanel buttPanel;
  private JLabel sourceText;
  private RootPaneContainer parent = null;

  private boolean datasetEvents = true;
  private boolean catrefEvents = false;
  private String currentURL = "";

  //private boolean catgenShow = true;
  private FileManager catgenFileChooser;

  private boolean debugEvents = false;
  //private boolean debugTree = false;
  private boolean showHTML = false;

  /**
   * Constructor, with control over whether a comboBox of previous catalogs is shown.
   *
   * @param prefs           persistent storage, may be null.
   * @param showComboChooser comboBox persists catalog URLs
   * @param showOpenButton  show the "open" button.
   * @param showFileChooser show a FileChooser (must have showComboChooser true)
   */
  public CatalogChooser(PreferencesExt prefs, boolean showComboChooser,
                        boolean showOpenButton, boolean showFileChooser) {
    this.prefs = prefs;

    JPanel topPanel = null;

    if (showComboChooser) {

      // combo box holds the catalogs
      catListBox = new ComboBox(prefs);

      // top panel buttons
      JButton connectButton = new JButton("Connect");
      connectButton.setToolTipText("read this catalog");
      connectButton.addActionListener(e -> {
          String catalogURL = (String) catListBox.getSelectedItem();
          tree.setCatalog(catalogURL.trim()); // will get "Catalog" property change event if ok
      });

      JPanel topButtons = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
      topButtons.add(connectButton);

      topPanel = new JPanel(new BorderLayout());
      topPanel.add(new JLabel("Catalog URL"), BorderLayout.WEST);
      topPanel.add(catListBox, BorderLayout.CENTER);
      topPanel.add(topButtons, BorderLayout.EAST);

      if (showFileChooser) {
         // add a file chooser
        PreferencesExt fcPrefs = (PreferencesExt) prefs.node("FileManager");
        FileFilter[] filters = new FileFilter[] {new FileManager.XMLExtFilter()};
        fileChooser = new FileManager(null, null, filters, fcPrefs);

        AbstractAction fileAction =  new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            String filename = fileChooser.chooseFilename();
            if (filename == null) return;
            tree.setCatalog("file:"+filename);
          }
        };
        BAMutil.setActionProperties( fileAction, "FileChooser", "open Local catalog...", false, 'L', -1);
        BAMutil.addActionToContainer( topButtons, fileAction);

        // a file chooser used for catgen on a directory
        PreferencesExt catgenPrefs = (PreferencesExt) prefs.node("CatgenFileManager");
        catgenFileChooser = new FileManager(null, null, null, catgenPrefs);
        catgenFileChooser.getFileChooser().setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES);
        catgenFileChooser.getFileChooser().setDialogTitle( "Run CatGen on Directory");

        AbstractAction srcEditAction = new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            TextGetPutPane sourceEditor = new TextGetPutPane(null);
            IndependentWindow sourceEditorWindow = new IndependentWindow( "Source", BAMutil.getImage("threddsIcon.png"), sourceEditor);
            sourceEditorWindow.setBounds(new Rectangle(50, 50, 725, 450));
            sourceEditorWindow.show();
          }
        };
        BAMutil.setActionProperties(srcEditAction, "Edit", "Source Editor", false, 'E', -1);
        BAMutil.addActionToContainer(topButtons, srcEditAction);
      }
    }

    // the catalog tree
    tree = new CatalogTreeView();
    tree.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
      public void propertyChange(java.beans.PropertyChangeEvent e) {
        if (debugEvents)
          System.out.println("CatalogChooser propertyChange name=" +e.getPropertyName() + "=");

        if (e.getPropertyName().equals("Catalog")) {
          String catalogURL = (String) e.getNewValue();
          setCurrentURL(catalogURL);
          if (catListBox != null)
            catListBox.addItem(catalogURL);
          firePropertyChangeEvent(e);

        } else if (e.getPropertyName().equals("Selection")) {
          DatasetNode ds = tree.getSelectedDataset();
          if (ds == null)
            return;

          if (ds instanceof Dataset)
            showDatasetInfo((Dataset)ds);

          if (ds instanceof CatalogRef) {
            CatalogRef ref = (CatalogRef) ds;
            String href = ref.getXlinkHref();
            if (href != null) {
              try {
                java.net.URI uri = ref.getParentCatalog().resolveUri(href);
                setCurrentURL(uri.toString());
              } catch (URISyntaxException ee) {
                throw new RuntimeException(ee);
              }
            }
          }
          else if (ds.getParent() == null) { // top
            setCurrentURL(tree.getCatalogURL());
          }

        } else if (e.getNewValue() instanceof Dataset) { // Dataset or File
          firePropertyChangeEvent((Dataset) e.getNewValue(), e.getPropertyName());
        }
      }
    });

    // htmlViewer Viewer
    htmlViewer = new HtmlBrowser();

    // listen for selection
    htmlViewer.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
      public void propertyChange(java.beans.PropertyChangeEvent e) {
        if (e.getPropertyName().equals("datasetURL")) {
          String datasetURL = (String) e.getNewValue();
          if (debugEvents) System.out.println("***datasetURL= " + datasetURL);
          DatasetNode node = tree.getSelectedDataset();
          if (node == null) return;
          if (node instanceof Dataset) {
            Dataset dataset = (Dataset) node;
            Access access = dataset.findAccess(datasetURL);
            firePropertyChangeEvent(new PropertyChangeEvent(this, "InvAccess", null, access));
          }
        } else if (e.getPropertyName().equals("catrefURL")) {
          String urlString = (String) e.getNewValue();
          if (debugEvents) System.out.println("***catrefURL= " + urlString);
          tree.setCatalog(urlString.trim());
        }
      }
    });

    // splitter
    split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, tree, htmlViewer);
    if (prefs != null)
      split.setDividerLocation(prefs.getInt(HDIVIDER, 400));

      //status label
    JPanel statusPanel = new JPanel(new BorderLayout());
    statusLabel = new JLabel("not connected");
    sourceText = new JLabel();
    statusPanel.add(statusLabel, BorderLayout.WEST);
    statusPanel.add(sourceText, BorderLayout.EAST);

    // button panel
    buttPanel = new JPanel();
    JButton openfileButton = new JButton("Open File");
    buttPanel.add(openfileButton, null);
    openfileButton.addActionListener(e -> {
        eventType = "File";
        try {
          tree.acceptSelected();
        } catch (Throwable t) {
          t.printStackTrace();
          JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
        } finally {
          eventType = null;
        }
    });

    JButton openCoordButton = new JButton("Open CoordSys");
    buttPanel.add(openCoordButton, null);
    openCoordButton.addActionListener(e -> {
        eventType = "CoordSys";
        try {
          tree.acceptSelected();
        } catch (Throwable t) {
          t.printStackTrace();
          JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
        } finally {
          eventType = null;
        }
    });

    JButton acceptButton = new JButton("Open dataset");
    buttPanel.add(acceptButton, null);
    acceptButton.addActionListener(e -> {
        eventType = "Dataset";
        try {
          tree.acceptSelected();
        } catch (Throwable t) {
          t.printStackTrace();
          JOptionPane.showMessageDialog(CatalogChooser.this, "ERROR "+t.getMessage());
        } finally {
          eventType = null;
        }
    });

    // put it all together
    setLayout(new BorderLayout());
    if (showComboChooser)
      add(topPanel, BorderLayout.NORTH);
    add(split, BorderLayout.CENTER);

    if (showOpenButton) {
      JPanel botPanel = new JPanel(new BorderLayout());
      botPanel.add(buttPanel, BorderLayout.NORTH);
      botPanel.add(statusPanel, BorderLayout.SOUTH);
      add(botPanel, BorderLayout.SOUTH);
    }
  }

  private void makeSourceEditWindow() {
    TextGetPutPane sourceEditor = new TextGetPutPane(null);
    IndependentWindow sourceEditorWindow = new IndependentWindow( "Source", BAMutil.getImage("threddsIcon.png"), sourceEditor);
    sourceEditorWindow.setBounds(new Rectangle(50, 50, 725, 450));
    sourceEditorWindow.show();
  }

  /**
   * Save persistent state.
   */
  public void save() {
    if (catListBox != null) catListBox.save();

    if (prefs != null) {
      if (fileChooser != null)
        fileChooser.save();
      if (catgenFileChooser != null)
        catgenFileChooser.save();
      prefs.putInt(HDIVIDER, split.getDividerLocation());
    }
  }

  private void firePropertyChangeEvent(Dataset ds, String oldPropertyName) {
    String propertyName = (eventType != null) ? eventType : oldPropertyName;
    PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, null, ds);
    firePropertyChangeEvent( event);
  }

  /**
   * Fires a PropertyChangeEvent:
   *

  propertyName = "Catalog", getNewValue() = catalog URL string
   *
  propertyName = "Dataset" or "File", getNewValue() = InvDataset chosen.
   *
  propertyName = "InvAccess" getNewValue() = InvAccess chosen.
   *
  propertyName = "catrefURL", getNewValue() = catref URL was chosen.
   *

   */
  private void firePropertyChangeEvent(PropertyChangeEvent event) {
    firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue());
  }

  /**
   * Add this button to the button panel.
   * @param b button to add
   */
  public void addButton( JButton b) {
    buttPanel.add(b, null);
    buttPanel.revalidate();
  }

  // public void useDQCpopup( boolean use) { tree.useDQCpopup( use); }

  /** Whether to throw events only if dataset has an Access.
   *  @param accessOnly if true, throw events only if dataset has an Access
   */
  public void setAccessOnly( boolean accessOnly) { tree.setAccessOnly(accessOnly); }

  /** Whether to throw events if catref URL was chosen catref URL was chosen in HtmlViewer (default false).
   */
  public void setCatrefEvents( boolean catrefEvents) { this.catrefEvents = catrefEvents; }

  /** Whether to throw events if dataset URL was chosen in HtmlViewer (default true).
   */
  public void setDatasetEvents( boolean datasetEvents) { this.datasetEvents = datasetEvents; }

  /**
   * Set the factory to create catalogs.
   * If you do not set this, it will use the default factory.
   * @param factory : read XML with this factory
   *
  public void setCatalogFactory(InvCatalogFactory factory) { tree.setCatalogFactory(factory); } */

  /**
   * Use this to set the string value in the combo box
   * @param item
   */
  public void setSelectedItem( String item) {
    if (catListBox != null)
      catListBox.setSelectedItem( item);
  }

  /**
   * Set the currently selected InvDataset.
   * @param ds select this InvDataset, must be already in the tree.
   */
  public void setSelectedDataset(Dataset ds) {
    tree.setSelectedDataset( ds);
    showDatasetInfo(ds);
  }

  public DatasetNode getSelectedDataset() {
    return tree.getSelectedDataset();
  }

  /**
   * Get the current catalog being shown.
   * @return current catalog, or null.
   */
  public Catalog getCurrentCatalog() { return tree.getCatalog(); }

  /**
   * Get the TreeView component.
   * @return the TreeView component.
   */
  public CatalogTreeView getTreeView() { return tree; }

  /**
   * Get the current URL string. This may be the top catalog, or a catalogRef, depending on
   *  what was last selected. Used to implement the " showSource" debugging tool.
   * @return current URL string
   */
  public String getCurrentURL() { return currentURL; }
  private void setCurrentURL( String currentURL) {
    this.currentURL = currentURL;
    sourceText.setText( currentURL);
    statusLabel.setText("Connected...");
  }

  /**
   * Set the current catalog.
   */
  public void setCatalog(Catalog catalog) {
    tree.setCatalog( catalog);
  }

  /**
   * Set the current catalog with a string URL.
   * May be of form catalog#datasetId
   */
  public void setCatalog(String catalogURL) {
    tree.setCatalog(catalogURL.trim());
  }

  private void showDatasetInfo(Dataset ds) {
    if (ds == null) return;
    Formatter sbuff = new Formatter();
    DatasetHtmlWriter writer = new DatasetHtmlWriter();
    writer.writeHtmlDescription(sbuff, ds, true, false, datasetEvents, catrefEvents, true);
    if (showHTML) System.out.println("HTML=\n"+sbuff);
    htmlViewer.setContent( ds.getName(), sbuff.toString());
  }

 /** Wrap this in a JDialog component.
   *
   * @param parent      JFrame (application) or JApplet (applet) or null
   * @param title       dialog window title
   * @param modal     is modal
   */
  public JDialog makeDialog( RootPaneContainer parent, String title, boolean modal) {
    this.parent = parent;
    return new Dialog( parent, title, modal);
  }

  private class Dialog extends JDialog {

    private Dialog(RootPaneContainer parent, String title, boolean modal) {
      super(parent instanceof Frame ? (Frame) parent : null, title, modal);

      // L&F may change
      UIManager.addPropertyChangeListener( new PropertyChangeListener() {
        public void propertyChange( PropertyChangeEvent e) {
          if (e.getPropertyName().equals("lookAndFeel"))
            SwingUtilities.updateComponentTreeUI( CatalogChooser.Dialog.this);
        }
      });

      // add a dismiss button
      JButton dismissButton = new JButton("Dismiss");
      buttPanel.add(dismissButton, null);

      dismissButton.addActionListener(e -> {
          setVisible(false);
      });

     // add it to contentPane
      Container cp = getContentPane();
      cp.setLayout(new BorderLayout());
      cp.add( CatalogChooser.this, BorderLayout.CENTER);
      pack();
    }
  }

}


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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import thredds.server.config.HtmlConfigBean;
import thredds.server.config.TdsContext;
import thredds.util.ContentType;
import ucar.nc2.time.CalendarDate;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Provide methods to write HTML representations of a catalog, directory, or CDM dataset to an HTTP response.
 * LOOK get rid of
 * @author edavis
 */

@Component
public class HtmlWriting {
  // static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HtmlWriting.class);

  @Autowired
  private TdsContext tdsContext;

  @Autowired
  private HtmlConfigBean htmlConfig;

  public String getHtmlDoctypeAndOpenTag() {
    return "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'\n" +
            "        'http://www.w3.org/TR/html4/loose.dtd'>\n" + "\n";
  }

  public String getGoogleTrackingContent() {
    if (this.htmlConfig.getGoogleTrackingCode().isEmpty()) {
      return "";
    } else {
      // See https://developers.google.com/analytics/devguides/collection/analyticsjs/
      return new StringBuilder()
              .append("\n")
              .append("\n")
              .append("\n").toString();
    }
  }

  public String getTdsCatalogCssLink() {
    return new StringBuilder()
            .append("<link rel='stylesheet' href='")
            .append(this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getCatalogCssUrl()))
            .append("' type='text/css' >").toString();
  }

  public String getTdsPageCssLink() {
    return new StringBuilder()
            .append("<link rel='stylesheet' href='")
            .append(this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getPageCssUrl()))
            .append("' type='text/css' >").toString();
  }

  //  public static final String UNIDATA_HEAD
  public String getUserHead() {
    return new StringBuilder()
            .append("

\n")
            .append("  \n")
            .append("
").append(this.tdsContext.getWebappDisplayName()).append("
\n")
            .append("

\n")
            .toString();
  }

  public String getOldStyleHeader() {
    StringBuilder sb = new StringBuilder();
    appendOldStyleHeader(sb);
    return sb.toString();
  }

  public void appendOldStyleHeader(StringBuilder sb) {
    appendOldStyleHeader(sb,
            this.htmlConfig.getWebappName(), this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getWebappUrl()),
            this.htmlConfig.getInstallLogoAlt(), this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getInstallLogoUrl()),
            this.htmlConfig.getInstallName(), this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getInstallUrl()),
            this.htmlConfig.getHostInstName(), this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getHostInstUrl()));
  }

  public void appendOldStyleHeader(StringBuilder sb,
                                   String webappName, String webappUrl,
                                   String logoAlt, String logoUrl,
                                   String installName, String installUrl,
                                   String hostName, String hostUrl) {
    // Table setup.
    sb.append("
\n")
            .append("
\n");
    // Logo
    sb.append("\n");

    // Installation name.
    sb.append("
")
            .append("")
            .append(installName).append("")
            .append("");
    if (false) sb.append(" at ").append(hostName);
    sb.append("
\n");

    // Webapp Name.
    sb.append("
")
            .append("")
            .append(webappName).append("")
            .append("
\n");

    sb.append("
\n")
            .append("
\n");
  }

  public void appendSimpleFooter(StringBuilder sb) {
    sb.append("
");
    if (this.htmlConfig.getInstallName() != null) {
      String installUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getInstallUrl());
      if (installUrl != null)
        sb.append("");
      sb.append(this.htmlConfig.getInstallName());
      if (installUrl != null)
        sb.append("");
    }
    if (this.htmlConfig.getHostInstName() != null) {
      sb.append(" at ");
      String hostInstUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getHostInstUrl());
      if (hostInstUrl != null)
        sb.append("");
      sb.append(this.htmlConfig.getHostInstName());
      if (hostInstUrl != null)
      sb.append("");
      sb.append(String.format(" see  Info ", tdsContext.getContextPath()));
      sb.append("
\n");
    }

    sb.append( this.tdsContext.getWebappDisplayName() )
            .append( " [Version " ).append( this.tdsContext.getVersionInfo() );
    sb.append( "]  Documentation" );
    sb.append( "
\n" );
  }

  /**
   * Write a file directory.
   *
   * @param res  the HttpServletResponse on which to write the file directory response.
   * @param dir  directory
   * @param path the URL path reletive to the base
   * @return the number of characters (Unicode code units) in the response.
   * @throws java.io.IOException if an I/O exception occurs.
   */
  public int writeDirectory(HttpServletResponse res, File dir, String path) throws IOException {
    // error checking
    if (dir == null) {
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return 0;
    }

    if (!dir.exists() || !dir.isDirectory()) {
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return 0;
    }

    // Get directory as HTML
    String dirHtmlString = getDirectory(path, dir);

    thredds.servlet.ServletUtil.setResponseContentLength(res, dirHtmlString);
    res.setContentType(ContentType.html.getContentHeader());
    PrintWriter writer = res.getWriter();
    writer.write(dirHtmlString);
    writer.flush();

    return dirHtmlString.length();
  }

  private String getDirectory(String path, File dir) {
    StringBuilder sb = new StringBuilder();

    // Render the page header
    sb.append(getHtmlDoctypeAndOpenTag()); // "\n" );
    sb.append("\r\n");
    sb.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
    sb.append("<title>");
    sb.append("Directory listing for ").append(dir.getPath()); // debug mode - show the actual file path
    sb.append("</title>\r\n");
    sb.append(this.getTdsCatalogCssLink()).append("\n");
    sb.append(this.getGoogleTrackingContent());
    sb.append("\r\n");
    sb.append("\r\n");
    sb.append("
");
    sb.append("Directory listing for ").append(dir.getPath());

    /* Render the link to our parent (if required)
    String parentDirectory = path;
    if (parentDirectory.endsWith("/")) {
      parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
    }
    int slash = parentDirectory.lastIndexOf('/');
    if (slash >= 0) {
      String parent = parentDirectory.substring(0, slash);
      sb.append(" - ");
      sb.append("");
      sb.append("Up to ").append(parent);
      sb.append("");
      sb.append("");
    } */

    sb.append("
\r\n");
    sb.append("
");

    sb.append("
\r\n");

    // Render the column headings
    sb.append("
\r\n");
    sb.append("");
    sb.append("Filename");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Size");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Last Modified");
    sb.append("	\r\n");
    sb.append("
");

    // Render the directory entries within this directory
    boolean shade = false;
    File[] children = dir.listFiles();
    List<File> fileList = (children == null) ? new ArrayList<File>() : Arrays.asList(children);
    Collections.sort(fileList);
    for (File child : fileList) {

      String childname = child.getName();
      if (childname.equalsIgnoreCase("WEB-INF") || childname.equalsIgnoreCase("META-INF")) {
        continue;
      }

      if (child.isDirectory()) childname = childname + "/";
      //if (!endsWithSlash) childname = path + "/" + childname; // client removes last path if no slash

      sb.append("
\r\n");
      shade = !shade;

      sb.append("  \r\n");
      sb.append("<tt>");
      sb.append(childname);
      sb.append("</tt>	\r\n");

      sb.append("<tt>");
      if (child.isDirectory()) {
        sb.append(" ");
      } else {
        sb.append(renderSize(child.length()));
      }
      sb.append("</tt>	\r\n");

      sb.append("<tt>");
      sb.append(CalendarDate.of(child.lastModified()).toString());
      sb.append("</tt>	\r\n");

      sb.append("
\r\n");
    }

    // Render the page footer
    sb.append("
\r\n");
    sb.append("
");

    appendSimpleFooter(sb);

    sb.append("\r\n");
    sb.append("\r\n");

    return sb.toString();
  }

  private String renderSize(long size) {

    long leftSide = size / 1024;
    long rightSide = (size % 1024) / 103;   // Makes 1 digit
    if ((leftSide == 0) && (rightSide == 0) && (size > 0)) {
      rightSide = 1;
    }

    return ("" + leftSide + "." + rightSide + " kb");
  }

   /*
  private void appendWebappFooter( StringBuilder sb )
  {
    sb.append( "
" )
            .append( this.tdsContext.getWebappDisplayName() )
            .append( " [Version " ).append( this.tdsContext.getVersionInfo() );
    sb.append( "]  Documentation" );
    sb.append("
\n");
  }

  //  private static final String TOMCAT_CSS
  private String getTomcatCSS() {
    return new StringBuilder("\r\n")
            .toString();
  }

  */

  /*   public void appendTableHeader(StringBuilder stringBuilder,
                                boolean includeInstall,
                                boolean includeWebapp,
                                boolean includeLogos) {
    // Table setup.
    stringBuilder
            .append("
\n");

    if (includeInstall) {
      stringBuilder.append("
\n");
      appendInstallationInfo(stringBuilder, includeLogos);
      stringBuilder.append("	\n");
      appendHostInstInfo(stringBuilder, includeLogos);
      stringBuilder.append("
\n");
    }

    if (includeWebapp) {
      stringBuilder
              .append("
\n");
      appendWebappInfo(stringBuilder, includeLogos);
      stringBuilder.append("
\n");
    }
    stringBuilder.append("

\n");
  }

  private void appendWebappInfo(StringBuilder stringBuilder, boolean includeLogo) {
    // Include webapp info
    String webappUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getWebappUrl());
    String webappLogoUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getWebappLogoUrl());
    if (includeLogo && webappLogoUrl != null)
      stringBuilder
              .append(" ");
    stringBuilder
            .append("")
            .append(this.tdsContext.getWebappDisplayName())
            .append("");
  }

  private void appendHostInstInfo(StringBuilder stringBuilder, boolean includeLogo) {
    // Include host institution information
    if (this.htmlConfig.getHostInstName() != null) {
      String hostInstUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getHostInstUrl());
      String hostInstLogoUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getHostInstLogoUrl());
      if (includeLogo && hostInstLogoUrl != null)
        stringBuilder
                .append(" ");
      if (hostInstUrl != null)
        stringBuilder.append("");
      stringBuilder.append(this.htmlConfig.getHostInstName());
      if (hostInstUrl != null)
        stringBuilder.append("");
    } else
      stringBuilder.append("Unknown Host Institution");
  }

  private void appendInstallationInfo(StringBuilder stringBuilder, boolean includeLogo) {
    // Include information on this intsallation.
    if (this.htmlConfig.getInstallName() != null) {
      String installUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getInstallUrl());
      String installLogoUrl = this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getInstallLogoUrl());
      if (includeLogo && installLogoUrl != null)
        stringBuilder
                .append(" ");
      if (installUrl != null)
        stringBuilder.append("");
      stringBuilder.append(this.htmlConfig.getInstallName());
      if (installUrl != null)
        stringBuilder.append("");
    } else {
      // This installation is not named.
      stringBuilder.append("Unnamed TDS Installation");
    }
  } */

  /*
    public String getXHtmlDoctypeAndOpenTag() {
    return "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n" +
            "        'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n" +
            "";
  }

  //  public static final String UNIDATA_CSS
  public String getUserCSS() {
    return new StringBuilder()
            .append("<link rel='stylesheet' href='")
            .append(this.htmlConfig.prepareUrlStringForHtml(this.htmlConfig.getPageCssUrl()))
            .append("' type='text/css' >").toString();
  }

  private String getCollapsableJs() {
      return new StringBuilder()
          .append("\n")
          .append("\n").toString();
  }

  private String getCollapsableCss() {
    return new StringBuilder()
      .append("\n").toString();
  }

  public StringBuilder makeCollapsable(StringBuilder sb, int level) {
	    if (level > 0) {
	    String replaceThis = "<tr";
        String withThis = "<tr class=\"header\"";
        int strRepStart = sb.lastIndexOf(replaceThis);
        if (strRepStart != -1) {
          sb.replace(strRepStart, strRepStart + replaceThis.length(), withThis);
        }
	  }
      return sb;
  }
   */

  /*
   * Show CDM compliance (coordinate systems, etc) of a NetcdfDataset.
   *
   * @param ds dataset to write
   *
  public void showCDM(HttpServletResponse res, NetcdfDataset ds)
          throws IOException {
    String cdmAsString = getCDM(ds);

    res.setContentLength(cdmAsString.length());
    res.setContentType(ContentType.html.getContentHeader());
    PrintWriter writer = res.getWriter();

    writer.write(cdmAsString);
    writer.flush();
  }

  private String getCDM(NetcdfDataset ds) throws IOException {
    StringBuilder sb = new StringBuilder(10000);

    String name = Escape.html(ds.getLocation());

    // Render the page header
    sb.append(getHtmlDoctypeAndOpenTag()); // "\n" );
    sb.append("\r\n");
    sb.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
    sb.append("<title>");
    sb.append("Common Data Model");
    sb.append("</title>\r\n");
    sb.append(this.getTdsPageCssLink()).append("\n");
    sb.append(this.getGoogleTrackingContent());
    sb.append("\r\n");
    sb.append("");
    sb.append("
");
    sb.append("Dataset ").append(name);
    sb.append("
");
    sb.append("
");

    sb.append("
\r\n");

    //////// Axis
    sb.append("
\r\n");
    sb.append("");
    sb.append("Axis");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Type");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Units");
    sb.append("	\r\n");
    sb.append("
");

    // Show the coordinate axes
    boolean shade = false;
    for (CoordinateAxis axis : ds.getCoordinateAxes()) {
      showAxis(axis, sb, shade);
      shade = !shade;
    }

    ///////////// Grid
    GridDataset gds = new ucar.nc2.dt.grid.GridDataset(ds);

    // find projections
    //List gridsets = gds.getGridsets();

    sb.append("
\r\n");
    sb.append("");
    sb.append("GeoGrid");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Description");
    sb.append("	\r\n");
    sb.append("");
    sb.append("Units");
    sb.append("	\r\n");
    sb.append("
");

    // Show the grids
    shade = false;
    for (GridDatatype grid : gds.getGrids()) {
      showGrid(grid, sb, shade);
      shade = !shade;
    }

    // Render the page footer
    sb.append("
\r\n");

    sb.append("
");

    appendSimpleFooter(sb);

    sb.append("\r\n");
    sb.append("\r\n");

    return (sb.toString());
  }

  private void showAxis(CoordinateAxis axis, StringBuilder sb, boolean shade) {
    sb.append("
\r\n");
    shade = !shade;

    sb.append("");
    sb.append("\r\n");

    StringBuilder sbuff = new StringBuilder();
    axis.getNameAndDimensions(sbuff);
    String name = Escape.html(sbuff.toString());
    sb.append(" ");
    sb.append(name);
    sb.append("</tt>	\r\n");

    sb.append("<tt>");
    AxisType type = axis.getAxisType();
    String stype = (type == null) ? "" : Escape.html(type.toString());
    sb.append(stype);
    sb.append("</tt>	\r\n");

    sb.append("<tt>");
    String units = axis.getUnitsString();
    String sunits = (units == null) ? "" : units;
    sb.append(sunits);
    sb.append("</tt>	\r\n");

    sb.append("
\r\n");
  }

  private void showGrid(GridDatatype grid, StringBuilder sb, boolean shade) {
    sb.append("
\r\n");
    shade = !shade;

    sb.append("");
    sb.append("\r\n");

    VariableEnhanced ve = grid.getVariable();
    StringBuilder sbuff = new StringBuilder();
    ve.getNameAndDimensions(new Formatter(sbuff), false, true);
    String name = Escape.html(sbuff.toString());
    sb.append(" ");
    sb.append(name);
    sb.append("</tt>	\r\n");

    sb.append("<tt>");
    String desc = ve.getDescription();
    String sdesc = (desc == null) ? "" : Escape.html(desc);
    sb.append(sdesc);
    sb.append("</tt>	\r\n");

    sb.append("<tt>");
    String units = ve.getUnitsString();
    String sunits = (units == null) ? "" : units;
    sb.append(sunits);
    sb.append("</tt>	\r\n");

    sb.append("
\r\n");
  } */

}


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

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

/**
 * Concrete implementation of Array specialized for floats.
 * Data storage is with 1D java array of floats.
 *
 * issues: what should we do if a conversion loses accuracy? nothing ? Exception ?
 *
 * @see Array
 * @author caron
 */
public class ArrayFloat extends Array {
  /** package private. use Array.factory() */
  static ArrayFloat factory(Index index) {
    return ArrayFloat.factory(index, null);
  }

  /* create new ArrayFloat with given indexImpl and backing store.
   * Should be private.
   * @param index use this Index
   * @param stor. use this storage. if null, allocate.
   * @return. new ArrayFloat.D<rank> or ArrayFloat object.
   */
  static ArrayFloat factory( Index index, float [] storage) {
      if (index instanceof Index0D) {
          return new ArrayFloat.D0(index, storage);
      } else if (index instanceof Index1D) {
          return new ArrayFloat.D1(index, storage);
      } else if (index instanceof Index2D) {
          return new ArrayFloat.D2(index, storage);
      } else if (index instanceof Index3D) {
          return new ArrayFloat.D3(index, storage);
      } else if (index instanceof Index4D) {
          return new ArrayFloat.D4(index, storage);
      } else if (index instanceof Index5D) {
          return new ArrayFloat.D5(index, storage);
      } else if (index instanceof Index6D) {
          return new ArrayFloat.D6(index, storage);
      } else if (index instanceof Index7D) {
          return new ArrayFloat.D7(index, storage);
      } else {
          return new ArrayFloat(index, storage);
      }
  }

  //////////////////////////////////////////////////////
  protected float[] storage;

  /**
  * Create a new Array of type float and the given shape.
  * dimensions.length determines the rank of the new Array.
  * @param dimensions the shape of the Array.
  */
  public ArrayFloat(int [] dimensions) {
    super(DataType.FLOAT, dimensions);
    storage = new float[(int) indexCalc.getSize()];
  }

  /**
  * Create a new Array using the given IndexArray and backing store.
  * used for sections. Trusted package private.
  * @param ima use this IndexArray as the index
  * @param data use this as the backing store
  */
  ArrayFloat(Index ima, float [] data) {
    super(DataType.FLOAT, ima);
    /* replace by something better
    if (ima.getSize() != data.length)
      throw new IllegalArgumentException("bad data length");  */
    if (data != null)
      storage = data;
    else
      storage = new float[(int)ima.getSize()];
  }

  /** create new Array with given indexImpl and same backing store */
  protected Array createView( Index index) {
    return ArrayFloat.factory( index, storage);
  }

  /* Get underlying primitive array storage. CAUTION! You may invalidate your warrentee! */
  public Object getStorage() { return storage; }

      // copy from javaArray to storage using the iterator: used by factory( Object);
  protected void copyFrom1DJavaArray(IndexIterator iter, Object javaArray) {
    float[] ja = (float []) javaArray;
    for (float aJa : ja) iter.setFloatNext(aJa);
  }

  // copy to javaArray from storage using the iterator: used by copyToNDJavaArray;
  protected void copyTo1DJavaArray(IndexIterator iter, Object javaArray) {
    float[] ja = (float []) javaArray;
    for (int i=0; i<ja.length; i++)
      ja[i] = iter.getFloatNext();
  }

  public ByteBuffer getDataAsByteBuffer() {
    ByteBuffer bb = ByteBuffer.allocate((int)(4*getSize()));
    FloatBuffer ib = bb.asFloatBuffer();
    ib.put( (float[]) get1DJavaArray(DataType.FLOAT)); // make sure its in canonical order
    return bb;
  }

 /** Return the element class type */
  public Class getElementType() { return float.class; }

    /** get the value at the specified index. */
  public float get(Index i) {
    return storage[i.currentElement()];
  }
    /** set the value at the sepcified index. */
  public void set(Index i, float value) {
    storage[i.currentElement()] = value;
  }

  public double getDouble(Index i) {
    return (double) storage[i.currentElement()];
  }
  public void setDouble(Index i, double value) {
    storage[i.currentElement()] = (float) value;
  }

  public float getFloat(Index i) {
    return storage[i.currentElement()];
  }
  public void setFloat(Index i, float value) {
    storage[i.currentElement()] = value;
  }

  public long getLong(Index i) {
    return (long) storage[i.currentElement()];
  }
  public void setLong(Index i, long value) {
    storage[i.currentElement()] = (float) value;
  }

  public int getInt(Index i) {
    return (int) storage[i.currentElement()];
  }
  public void setInt(Index i, int value) {
    storage[i.currentElement()] = (float) value;
  }

  public short getShort(Index i) {
    return (short) storage[i.currentElement()];
  }
  public void setShort(Index i, short value) {
    storage[i.currentElement()] = (float) value;
  }

  public byte getByte(Index i) {
    return (byte) storage[i.currentElement()];
  }
  public void setByte(Index i, byte value) {
    storage[i.currentElement()] = (float) value;
  }

  public char getChar(Index i) {
    return (char) storage[i.currentElement()];
  }
  public void setChar(Index i, char value) {
    storage[i.currentElement()] = (float) value;
  }

  /** not legal, throw ForbiddenConversionException */
  public boolean getBoolean(Index i) {
    throw new ForbiddenConversionException();
  }
  /** not legal, throw ForbiddenConversionException */
  public void setBoolean(Index i, boolean value) {
     throw new ForbiddenConversionException();
  }

  public Object getObject(Index i) {
    return storage[i.currentElement()];
  }
  public void setObject(Index i, Object value) {
    storage[i.currentElement()] = ((Number)value).floatValue();
  }

    // package private : mostly for iterators
  public double getDouble(int index) {return (double) storage[index]; }
  public void setDouble(int index, double value) { storage[index] = (float) value; }

  public float getFloat(int index) {
    return storage[index];
  }
  public void setFloat(int index, float value) { storage[index] = value;}

  public long getLong(int index) {return (long) storage[index];}
  public void setLong(int index, long value) { storage[index] = (float) value;}

  public int getInt(int index) { return (int) storage[index]; }
  public void setInt(int index, int value) { storage[index] = (float) value;}

  public short getShort(int index) { return (short) storage[index]; }
  public void setShort(int index, short value) { storage[index] = (float) value; }

  public byte getByte(int index) { return (byte) storage[index]; }
  public void setByte(int index, byte value) {storage[index] = (float) value;}

  public char getChar(int index) { return (char) storage[index];}
  public void setChar(int index, char value) { storage[index] = (float) value; }

  public boolean getBoolean(int index) { throw new ForbiddenConversionException(); }
  public void setBoolean(int index, boolean value) {throw new ForbiddenConversionException(); }

  public Object getObject(int index) { return getFloat(index); }
  public void setObject(int index, Object value) { storage[index] = ((Number)value).floatValue(); }

  /** Concrete implementation of Array specialized for floats, rank 0. */
  public static class D0 extends ArrayFloat {
    private Index0D ix;
    /** Constructor. */
    public D0 () {
      super(new int [] {});
      ix = (Index0D) indexCalc;
    }
    private D0 (Index i, float[] store) {
      super(i, store);
      ix = (Index0D) indexCalc;
    }
    /** get the value. */
    public float get() {
      return storage[ix.currentElement()];
    }
    /** set the value. */
    public void set(float value) {
      storage[ix.currentElement()] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 1. */
  public static class D1 extends ArrayFloat {
    private Index1D ix;
    /** Constructor for array of shape {len0}. */
    public D1 (int len0) {
      super(new int [] {len0});
      ix = (Index1D) indexCalc;
    }
    private D1 (Index i, float[] store) {
      super(i, store);
      ix = (Index1D) indexCalc;
    }
    /** get the value. */
    public float get(int i) {
      return storage[ix.setDirect(i)];
    }
    /** set the value. */
    public void set(int i, float value) {
      storage[ix.setDirect(i)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 2. */
  public static class D2 extends ArrayFloat {
    private Index2D ix;
    /** Constructor for array of shape {len0,len1}. */
    public D2 (int len0, int len1) {
      super(new int [] {len0, len1});
      ix = (Index2D) indexCalc;
    }
    private D2 (Index i, float[] store) {
      super(i, store);
      ix = (Index2D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j) {
      return storage[ix.setDirect(i,j)];
    }
    /** set the value. */
    public void set(int i, int j, float value) {
      storage[ix.setDirect(i,j)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 3. */
  public static class D3 extends ArrayFloat {
    private Index3D ix;
    /** Constructor for array of shape {len0,len1,len2}. */
    public D3 (int len0, int len1, int len2) {
      super(new int [] {len0, len1, len2});
      ix = (Index3D) indexCalc;
    }
    private D3 (Index i, float[] store) {
      super(i, store);
      ix = (Index3D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j, int k) {
      return storage[ix.setDirect(i,j,k)];
    }
    /** set the value. */
    public void set(int i, int j, int k, float value) {
      storage[ix.setDirect(i,j,k)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 4. */
  public static class D4 extends ArrayFloat {
    private Index4D ix;
    /** Constructor for array of shape {len0,len1,len2,len3}. */
    public D4 (int len0, int len1, int len2, int len3) {
      super(new int [] {len0, len1, len2, len3});
      ix = (Index4D) indexCalc;
    }
    private D4 (Index i, float[] store) {
      super(i, store);
      ix = (Index4D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j, int k, int l) {
      return storage[ix.setDirect(i,j,k,l)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, float value) {
      storage[ix.setDirect(i,j,k,l)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 5. */
  public static class D5 extends ArrayFloat {
    private Index5D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4}. */
    public D5 (int len0, int len1, int len2, int len3, int len4) {
      super(new int [] {len0, len1, len2, len3, len4});
      ix = (Index5D) indexCalc;
    }
    private D5 (Index i, float[] store) {
      super(i, store);
      ix = (Index5D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j, int k, int l, int m) {
      return storage[ix.setDirect(i,j,k,l, m)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, float value) {
      storage[ix.setDirect(i,j,k,l, m)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 6. */
  public static class D6 extends ArrayFloat {
    private Index6D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,}. */
    public D6 (int len0, int len1, int len2, int len3, int len4, int len5) {
      super(new int [] {len0, len1, len2, len3, len4, len5});
      ix = (Index6D) indexCalc;
    }
    private D6 (Index i, float[] store) {
      super(i, store);
      ix = (Index6D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j, int k, int l, int m, int n) {
      return storage[ix.setDirect(i,j,k,l,m,n)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, float value) {
      storage[ix.setDirect(i,j,k,l,m,n)] = value;
    }
  }

  /** Concrete implementation of Array specialized for floats, rank 7. */
  public static class D7 extends ArrayFloat {
    private Index7D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,len6}. */
    public D7 (int len0, int len1, int len2, int len3, int len4, int len5, int len6) {
      super(new int [] {len0, len1, len2, len3, len4, len5, len6});
      ix = (Index7D) indexCalc;
    }
    private D7 (Index i, float[] store) {
      super(i, store);
      ix = (Index7D) indexCalc;
    }
    /** get the value. */
    public float get(int i, int j, int k, int l, int m, int n, int o) {
      return storage[ix.setDirect(i,j,k,l,m,n,o)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, int o, float value) {
      storage[ix.setDirect(i,j,k,l,m,n,o)] = value;
    }
  }

}


package ucar.nc2.ogc.gml;

import net.opengis.gml.x32.TimePositionType;
import org.joda.time.DateTime;
import ucar.nc2.ft.PointFeature;
import ucar.nc2.ogc.MarshallingUtil;
import ucar.nc2.time.CalendarDate;

import java.io.IOException;

/**
 * Created by cwardgar on 2014/03/05.
 */
public abstract class NcTimePositionType {
    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:result/wml2:MeasurementTimeseries/wml2:point/
    //         wml2:MeasurementTVP/wml2:time
    public static TimePositionType initTime(TimePositionType time, PointFeature pointFeat) {
        // TEXT
        time.setStringValue(pointFeat.getNominalTimeAsCalendarDate().toString());

        return time;
    }

    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:phenomenonTime/gml:TimePeriod/gml:beginPosition
    public static TimePositionType initBeginPosition(
            TimePositionType beginPosition, CalendarDate date) throws IOException {
        // TEXT
            beginPosition.setStringValue(date.toString());

        return beginPosition;
    }

    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:phenomenonTime/gml:TimePeriod/gml:endPosition
    public static TimePositionType initEndPosition(
            TimePositionType endPosition, CalendarDate date) throws IOException {
        // TEXT
            endPosition.setStringValue(date.toString());

        return endPosition;
    }

    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:resultTime/gml:TimeInstant/gml:timePosition
    public static TimePositionType initTimePosition(TimePositionType timePosition) {
        DateTime resultTime = MarshallingUtil.fixedResultTime;
        if (resultTime == null) {
            resultTime = new DateTime();  // Initialized to "now".
        }

        // TEXT
        timePosition.setStringValue(resultTime.toString());

        return timePosition;
    }

    private NcTimePositionType() { }
}


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

import ucar.nc2.dt.*;
import ucar.nc2.dataset.*;
import ucar.nc2.constants.*;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.units.DateUnit;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.Variable;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.ma2.*;
import ucar.ma2.DataType;

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

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

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

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

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

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

  // needed for FeatureDatasetFactory
  public NsslRadialAdapter() {
  }

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

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

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

  }

  private void addCoordSystem(NetcdfDataset ds) throws IOException {

    //  int time = ds.findGlobalAttributeIgnoreCase("Time").getNumericValue().intValue();
    double ele = 0;
    Attribute attr = ds.findGlobalAttributeIgnoreCase("Elevation");
    if (attr != null)
      ele = attr.getNumericValue().doubleValue();

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

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

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

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

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

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

  }

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

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

  public boolean isStationary() {
    return true;
  }

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

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

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

  public boolean isVolume() {
    return isVolume;
  }

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

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

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

    origin = new ucar.unidata.geoloc.EarthLocationImpl(latv, lonv, elev);
  }

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

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

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

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

    setIsVolume(nds);

    if (isVolume && rnk == 3) {
      VariableSimpleIF v = new MyRadialVariableAdapter(vName, var.getAttributes());
      rsvar = makeRadialVariable(nds, v, var);
    } else if (!isVolume && rnk == 2) {
      VariableSimpleIF v = new MyRadialVariableAdapter(vName, var.getAttributes());
      rsvar = makeRadialVariable(nds, v, var);
    }

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

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

  public void clearDatasetMemory() {
    List rvars = getDataVariables();
    Iterator iter = rvars.iterator();
    while (iter.hasNext()) {
      RadialVariable radVar = (RadialVariable) iter.next();
      radVar.clearVariableMemory();
    }
  }

  public String getInfo() {
    StringBuilder sbuff = new StringBuilder();
    sbuff.append("Netcdfs2Dataset\n");
    sbuff.append(super.getDetailInfo());
    sbuff.append("\n\n");
    sbuff.append(parseInfo.toString());
    return sbuff.toString();
  }

  private class Netcdf2Variable extends MyRadialVariableAdapter implements RadialDatasetSweep.RadialVariable {
    ArrayList sweeps;
    int nsweeps;
    String name;

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

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

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

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

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

    }

    public String toString() {
      return name;
    }

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

    public int getNumSweeps() {
      return nsweeps;
    }

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

    public void clearVariableMemory() {
      // doing nothing
    }

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

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

        //setMeanElevation();
        //setMeanAzimuth();
      }

      public Variable getsweepVar() {
        return sweepVar;
      }

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

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

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

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

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

      //  private Object MUTEX =new Object();
      /* read 1d data ngates */
      public float[] readData(int ray) throws java.io.IOException {

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

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

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

      private void setMeanElevation() {

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

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

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

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

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

      public int getGateNumber() {
        return ngates;
      }

      public int getRadialNumber() {
        return nrays;
      }

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

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

      public Date getStartingTime() {
        return startDate;
      }

      public Date getEndingTime() {
        return endDate;
      }

      public int getSweepIndex() {
        return sweepno;
      }

      // public int getNumGates() {
      //   return ngates;
      // }

      private void setMeanAzimuth() {
        meanAzimuth = 0.0;
      }

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

      public boolean isConic() {
        return true;
      }

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

      public float[] getElevation() throws IOException {

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

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

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

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

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

        return dist;
      }

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

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

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

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

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

      public boolean isGateSizeConstant() {
        return true;
      }

      public void clearSweepMemory() {

      }
    } // Netcdf2Sweep class

  } // Netcdf2Variable

  private static void testRadialVariable(RadialDatasetSweep.RadialVariable rv) throws IOException {
    int nsweep = rv.getNumSweeps();
    //System.out.println("*** radar Sweep number is: \n" + nsweep);
    Sweep sw;
    float[] az;
    for (int i = 0; i < nsweep; i++) {
      //ucar.unidata.util.Trace.call1("LevelII2Dataset:testRadialVariable getSweep " + i);
      sw = rv.getSweep(i);
      sw.getMeanElevation();
      //ucar.unidata.util.Trace.call2("LevelII2Dataset:testRadialVariable getSweep " + i);
      float me = sw.getMeanElevation();
      System.out.println("*** radar Sweep mean elevation of sweep " + i + " is: " + me);
      int nrays = sw.getRadialNumber();
      az = new float[nrays];
      for (int j = 0; j < nrays; j++) {
        float azi = sw.getAzimuth(j);
        az[j] = azi;
      }
      //System.out.println("*** radar Sweep mean elevation of sweep " + i + " is: " + me);
    }
    sw = rv.getSweep(0);
    //ucar.unidata.util.Trace.call1("LevelII2Dataset:testRadialVariable readData");
    float[] ddd = sw.readData();
    sw.getAzimuth();
    sw.getElevation();
    //ucar.unidata.util.Trace.call2("LevelII2Dataset:testRadialVariable readData");
    assert (null != ddd);
    int nrays = sw.getRadialNumber();
    az = new float[nrays];
    for (int i = 0; i < nrays; i++) {

      int ngates = sw.getGateNumber();
      assert (ngates > 0);
      float[] d = sw.readData(i);
      assert (null != d);
      // float [] e = sw.readDataNew(i);
      // assert(null != e);
      float azi = sw.getAzimuth(i);
      assert (azi > 0);
      az[i] = azi;
      float ele = sw.getElevation(i);
      assert (ele > 0);
      float la = (float) sw.getOrigin(i).getLatitude();
      assert (la > 0);
      float lo = (float) sw.getOrigin(i).getLongitude();
      assert (lo > 0);
      float al = (float) sw.getOrigin(i).getAltitude();
      assert (al > 0);
    }
    assert (0 != nrays);
  }

}


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

import ucar.nc2.util.Indent;

import java.util.Arrays;
import java.util.Formatter;
import java.util.Map;
import java.util.HashMap;

/**
 * Counts the size of nested tables, for uncompressed messages.
 * A top-level BitCounterUncompressed counts bits for one row = obs = dataset.
 *   obs = new BitCounterUncompressed(root, 1, 0);
 * @author caron
 * @since May 10, 2008
 */
public class BitCounterUncompressed implements BitCounter {
  private final DataDescriptor parent; // represents the table - fields/cols are the subKeys of dkey
  private final int nrows; // number of rows in this table
  private final int replicationCountSize; // number of bits taken up by the count variable (non-zero only for sequences)

  private Map<DataDescriptor, Integer> bitPosition;
  private Map<DataDescriptor, BitCounterUncompressed[]> subCounters; // nested tables; null for regular fields
  private int[] startBit; // from start of data section, for each row
  private int countBits; // total nbits in this table
  private int bitOffset = 0; // count bits

  static private boolean debug = false;

  /**
   * This counts the size of an array of Structures or Sequences, ie Structure(n)
   * @param parent is a structure or a sequence - so has subKeys
   * @param nrows numbers of rows in the table, equals 1 for top level
   * @param replicationCountSize number of bits taken up by the count variable (non-zero only for sequences)
   */
  public BitCounterUncompressed(DataDescriptor parent, int nrows, int replicationCountSize) {
    this.parent = parent;
    this.nrows = nrows;
    this.replicationCountSize = replicationCountSize;
  }

  // not used yet
  public void setBitOffset(DataDescriptor dkey) {
    if (bitPosition == null)
      bitPosition = new HashMap<DataDescriptor, Integer>(2 * parent.getSubKeys().size());
    bitPosition.put(dkey, bitOffset);
    bitOffset += dkey.getBitWidth();
  }
  public int getOffset(DataDescriptor dkey) {
    return bitPosition.get(dkey);
  }

  /**
   * Track nested Tables.
   * @param subKey  subKey is a structure or a sequence - so itself has subKeys
   * @param n numbers of rows in the nested table
   * @param row which row in the parent Table this belongs to
   * @param replicationCountSize number of bits taken up by the count (non-zero for sequences)
   * @return  nested ReplicationCounter
   */
  public BitCounterUncompressed makeNested(DataDescriptor subKey, int n, int row, int replicationCountSize) {
    if (subCounters == null)
      subCounters = new HashMap<DataDescriptor, BitCounterUncompressed[]>(5); // assumes DataDescriptor.equals is ==

    BitCounterUncompressed[] subCounter = subCounters.get(subKey);
    if (subCounter == null) {
      subCounter = new BitCounterUncompressed[nrows]; // one for each row in this table
      subCounters.put(subKey, subCounter);
    }

    BitCounterUncompressed rc = new BitCounterUncompressed(subKey, n, replicationCountSize);
    subCounter[row] = rc;

    return rc;
  }

  public BitCounterUncompressed[] getNested(DataDescriptor subKey) {
    return (subCounters == null) ? null : subCounters.get(subKey);
  }

  // total bits of this table and all subtables
  int countBits(int startBit) {
    countBits = replicationCountSize;
    this.startBit = new int[nrows];

    for (int i=0; i<nrows; i++) {
      this.startBit[i] = startBit + countBits;
      if (debug) System.out.println(" BitCounterUncompressed row "+i+" startBit="+ this.startBit[i]);

      for (DataDescriptor nd : parent.subKeys) {
        BitCounterUncompressed[] bitCounter = (subCounters == null) ? null : subCounters.get(nd);
        if (bitCounter == null) // a regular field
          countBits += nd.getBitWidth();
        else {
          if (debug) System.out.println(" ---------> nested "+nd.getFxyName()+" starts at ="+ (startBit + countBits));
          countBits += bitCounter[i].countBits(startBit + countBits);
          if (debug) System.out.println(" <--------- nested "+nd.getFxyName()+" ends at ="+ (startBit + countBits));
        }
      }
    }
    return countBits;
  }

  public int getCountBits() { return countBits; }
  public int getNumberRows() { return nrows; }

  public int getStartBit(int row) {
    if (row >= startBit.length)
      throw new IllegalStateException();
    return startBit[row];
  }

  public void toString(Formatter f, Indent indent) {
    f.format("%s dds=%s, ", indent, parent.getFxyName());
    f.format("nrows=%d%n", nrows);
    if (subCounters == null) return;

    indent.incr();
    int count = 0;

    // Map<DataDescriptor, BitCounterUncompressed[]> subCounters; // nested tables; null for regular fields
    for (BitCounterUncompressed[] bcus : subCounters.values()) {
      if (bcus == null)
        f.format("%s%d: null", indent, count);
      else {
        for (BitCounterUncompressed bcu : bcus)
          bcu.toString(f, indent);
      }
      count++;
    }
    indent.decr();
  }
}


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

package ucar.nc2.grib.grib2.table;

import javax.annotation.Nullable;
import ucar.nc2.grib.GribResourceReader;
import ucar.nc2.grib.grib2.Grib2Parameter;
import ucar.nc2.util.TableParser;

import java.io.IOException;
import java.io.InputStream;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Read tables from https://github.com/Unidata/gempak/tree/master/gempak/tables/grid/g2*.tbl
 *
 * eg:
 * https://raw.githubusercontent.com/Unidata/gempak/master/gempak/tables/grid/g2varsfsl1.tbl
 *
 * LOOK: not used in standardTableMap.txt, so cannot be found in any search in Grib2Tables.
 *
 * @author caron
 * @since 9/14/2014
 */
public class GempakLocalTables extends LocalTables {
  private static boolean debug = false;

  GempakLocalTables(Grib2TableConfig config) {
    super(config);
    Formatter f = new Formatter();
    local = initLocalTable(config.getPath(), f);
  }

  /*
    ! GRIB2 parameter table
    !
    !D#  = Discipline number
    !CT# = Category number (Octet 10, Code Table 4.2)
    !ID# = Parameter number (Octet 11)
    !PD# = Product Definition Template number (Octet 8-9, Code Table 4.0)
    !
    ! temperature
    !D# CT# ID# PD# NAME                             UNITS                GNAM         SCALE   MISSING  HZREMAP  DIRECTION
    !23|123|123|123|12345678901234567890123456789012|12345678901234567890|123456789012|12345|123456.89|12345678|1234567890
              1         2         3         4         5         6         7         8         9         10        11        12
    0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
    000 000 000 000 Temperature                      K                    TMPK             0  -9999.00        0          0
    000 000 000 019 Temperature Below Normal         %                    PTBN             0  -9999.00        0          0
    000 000 000 029 Temperature Near Normal          %                    PTNN             0  -9999.00        0          0
    000 000 000 039 Temperature Above Normal         %                    PTAN             0  -9999.00        0          0
    000 000 001 000 Virtual Temperature              K                    TVRK             0  -9999.00        0          0
    000 000 002 000 Potential Temperature            K                    THTA             0  -9999.00        0          0
    000 000 003 000 Equivalent Potential Temp        K                    THTE             0  -9999.00        0          0
    000 000 004 008 Maximum Temperature              K                    TMXK             0  -9999.00        0          0
    000 000 005 008 Minimum Temperature              K                    TMNK             0  -9999.00        0          0
    000 000 006 000 Dew Point Temperature            K                    DWPK             0  -9999.00        0          0
    000 000 007 000 Dew Point Depression             K                    DPDK             0  -9999.00        0          0
    000 000 008 000 Lapse Rate                       K m**-1              LAPS             0  -9999.00        0          0
    000 000 009 000 Temperature Anomaly              K                    TMPKA            0  -9999.00        0          0
    000 000 010 000 Latent Heat Net Flux             W m**-2              FXLH             0  -9999.00        0          0
    000 000 011 000 Sensible Heat Net Flux           W m**-2              FXSH             0  -9999.00        0          0
    000 000 012 000 Heat Index                       K                    HEAT             0  -9999.00        0          0
    000 000 013 000 Wind Chill Factor                K                    CHILL            0  -9999.00       0          0
    !000 000 014 000 Minimum Dew Point Depression     K                    ????             0  -9999.00       0          0
    !000 000 015 000 Virtual Potential Temperature    K                    ????             0  -9999.00       0          0
    !
    ! moisture
    !D# CT# ID# PD# NAME                             UNITS                GNAM         SCALE   MISSING  HZREMAP  DIRECTION
    !23|123|123|123|12345678901234567890123456789012|12345678901234567890|123456789012|12345|123456.89|12345678|1234567890
    000 001 000 000 Specific Humidity                kg kg**-1            SPFH             0  -9999.00        0          0
   */

  private Map<Integer, Grib2Parameter> initLocalTable(String resourcePath, @Nullable Formatter f) {
    Map<Integer, Grib2Parameter> result = new HashMap<>(100);

    try (InputStream is = GribResourceReader.getInputStream(resourcePath)) {
      if (f != null) f.format("%s, %-20s, %-20s, %-20s%n", "id", "name", "units", "gname");
      TableParser parser = new TableParser("3i,7i,11i,15i,49,69,74,");
      parser.setComment("!");
      List<TableParser.Record> recs = parser.readAllRecords(is, 50000);
      for (TableParser.Record record : recs) {
        int disc =  (Integer) record.get(0);
        int cat =  (Integer) record.get(1);
        int id =  (Integer) record.get(2);
        int template =  (Integer) record.get(3);               // LOOK - 19, 29, 39 ???
        String name = ((String) record.get(4)).trim();
        String units = ((String) record.get(5)).trim();
        String gname = ((String) record.get(6)).trim();

        String ids = disc+"-"+cat+"-"+id;
        if (f != null) f.format("%s == %-20s, %-20s, %-20s%n", ids, name, units, gname);

        Grib2Parameter gp = new Grib2Parameter(disc, cat, id, gname, units, null, name);
        result.put(Grib2Tables.makeParamId(disc, cat, id), gp);
      }

    } catch (IOException ioe) {
      throw new RuntimeException(ioe);
    }

    return result;
  }

}


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

import ucar.nc2.util.IO;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.StringTokenizer;

/**
 * TextHistoryPane
 * Keeps a user-settable number of lines in a JTextArea.
 * Lines are always appended to bottom, and top lines are scrolled off.
 * A popup menu allows the user to change the number of lines to keep, the font
 * size, etc.
 * @author John Caron
 */
public class TextHistoryPane extends JPanel {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TextHistoryPane.class);

  protected JTextArea ta;
  private FontUtil.StandardFont fontu;
  private int nlines, removeIncr, count = 0;
  private int ptSize;

  protected FileManager fileChooser;

  /** default constructor. */
  public TextHistoryPane() {
    this( false, 5000, 50, true, true, 14);
  }

  /** constructor allows editing. */
  public TextHistoryPane(boolean editable) {
    this( editable, 5000, 50, true, true, 14);
  }

  /** constructor
     @param nlines  number of lines of text to keep history of
     @param removeIncr  remove this number of lines at a time
     @param popupOK enable popup menu
     @param ptSize font point size
   */
  public TextHistoryPane(boolean editable, int nlines, int removeIncr, boolean popupOK,  boolean lineWrap, int ptSize) {
    super(new BorderLayout());
    this.nlines = nlines-1;
    this.removeIncr = Math.min(nlines-1, removeIncr-1); // cant be bigger than nlines

    ta = new JTextArea();
    ta.setLineWrap(lineWrap);
    ta.setEditable(editable);
    fontu = FontUtil.getMonoFont( ptSize);
    ta.setFont(fontu.getFont());

    if (popupOK)
      ta.addMouseListener( new MyPopupMenu());

    //JScrollPane sp = new JScrollPane(ta, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    JScrollPane sp = new JScrollPane(ta); // , JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    add(sp, BorderLayout.CENTER);

    javax.swing.filechooser.FileFilter[] filters = new javax.swing.filechooser.FileFilter[2];
    filters[0] = new FileManager.HDF5ExtFilter();
    filters[1] = new FileManager.NetcdfExtFilter();
    fileChooser = new FileManager(null, null, null, null);
  }

  /** Append this line to the bottom of the JTextArea.
   * A newline is added and JTextArea is scrolled to bottom;
   * remove lines at top if needed.
   * @param line append this line. Ok to have multiple lines (ie embedded newlines)
   *   but not too many.
   */
  public void appendLine( String line) {

    if (count >= nlines) {
      try {
        int remove = Math.max(removeIncr, count - nlines); // nlines may have changed
        int offset = ta.getLineEndOffset( remove);
        ta.replaceRange( "", 0, offset);
      } catch (Exception e) {
        log.error("Problem in TextHistoryPane", e);
      }
      count = nlines - removeIncr;
    }
    ta.append(line);
    ta.append("\n");
    count++;

    // scroll to end
    ta.setCaretPosition(ta.getText().length());
  }

  public void clear() { ta.setText(null); }
  public String getText() { return ta.getText(); }
  public void gotoTop() { ta.setCaretPosition(0); }
  public void setText(String text) { ta.setText(text); }

  public void setTextFromStackTrace(Throwable e) {
    StringWriter sw = new StringWriter(5000);
    e.printStackTrace(new PrintWriter(sw));
    setText(sw.toString());
  }

  /* public void getStream() {
    ByteArrayOutputStream bout = new ByteArrayOutputStream(20000);
  }  */

  private class MyPopupMenu extends ucar.nc2.ui.widget.PopupMenu.PopupTriggerListener implements Printable {
    private JPopupMenu popup = new JPopupMenu("Options");
    private JTextField nlinesFld = new JTextField();
    private JTextField ptSizeFld = new JTextField();
    private AbstractAction incrFontAction, decrFontAction;

    private StringTokenizer token;
    private Font newFont;
    private int incrY;

    MyPopupMenu () {
      super();

        // the popup menu
      JPanel nlPan = new JPanel();
      nlPan.add(new JLabel("Number of lines to keep:"));
      nlPan.add(nlinesFld);
      popup.add(nlPan);

      incrFontAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          fontu.incrFontSize();
          ta.setFont(fontu.getFont());
       }
      };
      BAMutil.setActionProperties( incrFontAction, "FontIncr", "Increase Font Size", false, '+', -1);
      BAMutil.addActionToPopupMenu( popup, incrFontAction);

      decrFontAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          fontu.decrFontSize();
          ta.setFont(fontu.getFont());
       }
      };
      BAMutil.setActionProperties( decrFontAction, "FontDecr", "Decrease Font Size", false, '-', -1);
      BAMutil.addActionToPopupMenu( popup, decrFontAction);

      JMenuItem printButt= new JMenuItem("Print");
      popup.add(printButt);

      JMenuItem writeButt= new JMenuItem("Write to File");
      popup.add(writeButt);

      JMenuItem dissButt= new JMenuItem("Dismiss");
      popup.add(dissButt);

      JMenuItem clearButt= new JMenuItem("Clear");
      popup.add(clearButt);

        // listen to changes to the JTextField
      nlinesFld.addActionListener( new AbstractAction() {
        public void actionPerformed( ActionEvent e) {
          int numLines = Integer.parseInt(nlinesFld.getText());
          // System.out.println( "numLines = "+numLines);
          TextHistoryPane.this.nlines = Math.max(numLines-1, removeIncr);
          popup.setVisible(false);
        }
      });

        // print
      printButt.addActionListener( new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          PrinterJob printJob = PrinterJob.getPrinterJob();
          PageFormat pf = printJob.defaultPage();

          newFont = FontUtil.getMonoFont( 10).getFont();
          FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics( newFont);
          incrY = fontMetrics.getAscent() + fontMetrics.getDescent();

          printJob.setPrintable(MyPopupMenu.this, pf);
          if (printJob.printDialog()) {
            try {
              //if (Debug.isSet("print.job")) System.out.println("call printJob.print");
              printJob.print();
              //if (Debug.isSet("print.job")) System.out.println(" printJob done");
            } catch (Exception PrintException) {
              PrintException.printStackTrace();
            } finally {
              popup.setVisible(false);
            }
          }
        }
      });

      writeButt.addActionListener( new AbstractAction() {
        public void actionPerformed( ActionEvent e) {
          String filename = fileChooser.chooseFilename();
          if (filename == null) return;

          try {
            IO.writeToFile(ta.getText(), new File( filename)); // UTF-8 encoding

            JOptionPane.showMessageDialog(null, "Text written to"+filename);
          } catch (IOException ioe) {
            //System.out.println(" write TextArea to file = "+filename+" "+ioe);
            JOptionPane.showConfirmDialog(null, "Error writting to" + filename+" "+ioe.getMessage());
          }
          popup.setVisible(false);
        }
      });

      dissButt.addActionListener( new AbstractAction() {
        public void actionPerformed( ActionEvent e) {
          popup.setVisible(false);
        }
      });

      clearButt.addActionListener( new AbstractAction() {
        public void actionPerformed( ActionEvent e) {
          clear();
          popup.setVisible(false);
        }
      });

    }

    public void showPopup(MouseEvent e) {
      nlinesFld.setText(""+(TextHistoryPane.this.nlines+1));
      ptSizeFld.setText(""+ptSize);
      popup.show(ta, e.getX(), e.getY());
    }

    public int print(Graphics g, PageFormat pf, int pi) throws PrinterException {
      if (pi == 0)
        token = new StringTokenizer(ta.getText(), "\r\n");

      if (!token.hasMoreTokens())
        return Printable.NO_SUCH_PAGE;

      Graphics2D g2d = (Graphics2D) g;
      g2d.setPaint(Color.black);
      g2d.setFont(newFont);

      double xbeg = pf.getImageableX();
      double ywidth = pf.getImageableHeight() + pf.getImageableY();
      double y = pf.getImageableY() + incrY;
      while (token.hasMoreTokens() && (y < ywidth)) {
        String toke = token.nextToken();
        g2d.drawString( toke, (int) xbeg, (int) y);
        y += incrY;
      }
      return Printable.PAGE_EXISTS;
    }
  }

  public IndependentWindow makeIndependentWindow(String title) {
    return new IW( title, this);
  }

  private static class IW extends IndependentWindow {
    private IW( String title, TextHistoryPane ta) {
      super(title, BAMutil.getImage( "thredds"), ta);
      setSize(700,700);
      setLocation(100,100);
    }
  }

}