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

import java.util.*;

/**
 * Builds one Coordinate of one Variable,
 * by keeping the Set of Values found in the records.
 *
 * @author caron
 * @since 11/27/13
 */
public abstract class CoordinateBuilderImpl<T> implements CoordinateBuilder<T> {
  private Set<Object> valSet = new HashSet<>(100);
  private Map<Object, Integer> valMap;    // map of values to index in Coordinate
  protected Coordinate coord;

  @Override
  public void addRecord(T gr) {
    Object val = extract(gr);
    valSet.add(val);
  }

  @Override
  public void addAll(Coordinate coord) {
    valSet.addAll(coord.getValues());
  }

  public void add(Object val) {
    valSet.add(val);
  }

  @Override
  public void addAll(List<Object> coords) {
    valSet.addAll(coords);
  }

  @Override
  public Coordinate finish() {
    List<Object> valList = new ArrayList<>(valSet.size());
    valList.addAll(valSet);
    coord =  makeCoordinate(valList);
    valSet = null;

    List<?> values = coord.getValues();
    if (values != null) {
      valMap = new HashMap<>(coord.getSize() * 2);
      for (int i = 0; i < values.size(); i++)
        valMap.put(values.get(i), i);
    }
    return coord;
  }

  // Used by CoordinateND.makeSparseArray; not used by CoordinateTime2D
  @Override
  public int getIndex(T gr) {
    Integer result =  valMap.get( extract(gr));
    return (result == null) ? 0 : result;
  }

  @Override
  public Coordinate getCoordinate() {
    return coord;
  }

}


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

package thredds.ui.monitor;

import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import ucar.httpservices.*;
import thredds.logs.LogReader;
import thredds.logs.LogCategorizer;
import ucar.nc2.ui.widget.*;
import ucar.nc2.util.IO;
import ucar.nc2.util.net.HttpClientManager;
import ucar.util.prefs.ui.ComboBox;
import ucar.util.prefs.ui.Debug;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.XMLStore;

import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.awt.*;
import java.util.*;

import javax.swing.*;

/**
 * Manage TDS logs
 *
 * @author caron
 * @since Mar 26, 2009
 */
public class TdsMonitor extends JPanel {
  static private final String FRAME_SIZE = "FrameSize";

  private static JFrame frame;
  private static PreferencesExt prefs;
  private static XMLStore store;

  private ucar.util.prefs.PreferencesExt mainPrefs;
  private JTabbedPane tabbedPane;
  private ManagePanel managePanel;
  private AccessLogPanel accessLogPanel;
  private ServletLogPanel servletLogPanel;
  private URLDumpPane urlDump;

  private JFrame parentFrame;
  private FileManager fileChooser;
  private ManageForm manage;
  private DnsLookup dnsLookup = new DnsLookup();

  //private HTTPSession session;
  // private CredentialsProvider provider;

  public TdsMonitor(ucar.util.prefs.PreferencesExt prefs, JFrame parentFrame) throws HTTPException {
    this.mainPrefs = prefs;
    this.parentFrame = parentFrame;

    fileChooser = new FileManager(parentFrame, null, null, (PreferencesExt) prefs.node("FileManager"));

    // the top UI
    tabbedPane = new JTabbedPane(JTabbedPane.TOP);
    managePanel = new ManagePanel((PreferencesExt) mainPrefs.node("ManageLogs"));
    accessLogPanel = new AccessLogPanel((PreferencesExt) mainPrefs.node("LogTable"));
    servletLogPanel = new ServletLogPanel((PreferencesExt) mainPrefs.node("ServletLogPanel"));
    urlDump = new URLDumpPane((PreferencesExt) mainPrefs.node("urlDump"));

    tabbedPane.addTab("ManageLogs", managePanel);
    tabbedPane.addTab("AccessLogs", accessLogPanel);
    tabbedPane.addTab("ServletLogs", servletLogPanel);
    tabbedPane.addTab("UrlDump", urlDump);
    tabbedPane.setSelectedIndex(0);

    setLayout(new BorderLayout());
    add(tabbedPane, BorderLayout.CENTER);

    CredentialsProvider provider = new UrlAuthenticatorDialog(null);
    try {
      HTTPSession.setGlobalCredentialsProvider(provider, AuthSchemes.BASIC);
    } catch(HTTPException e) {
      System.err.println("Failed to set credentials");
    }
    HTTPSession.setGlobalUserAgent("TdsMonitor");
  }

  public void exit() {
    fileChooser.save();
    managePanel.save();
    accessLogPanel.save();
    servletLogPanel.save();
    urlDump.save();

    Rectangle bounds = frame.getBounds();
    prefs.putBeanObject(FRAME_SIZE, bounds);
    try {
      store.save();
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }

    done = true; // on some systems, still get a window close event
    System.exit(0);
  }

  private void gotoUrlDump(String urlString) {
    urlDump.setURL(urlString);
    tabbedPane.setSelectedIndex(3);
  }

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

  static private TdsMonitor ui;
  static private boolean done = false;

  static private File ehLocation = LogLocalManager.getDirectory("cache", "dns");

  /*private static String ehLocation = "C:\\data\\ehcache";
  //private static String ehLocation = "/machine/data/thredds/ehcache/";
  private static String config =
          "<ehcache>\n" +
                  "    <diskStore path='" + ehLocation.getPath() + "'/>\n" +
                  "    <defaultCache\n" +
                  "              maxElementsInMemory='10000'\n" +
                  "              eternal='false'\n" +
                  "              timeToIdleSeconds='120'\n" +
                  "              timeToLiveSeconds='120'\n" +
                  "              overflowToDisk='true'\n" +
                  "              maxElementsOnDisk='10000000'\n" +
                  "              diskPersistent='false'\n" +
                  "              diskExpiryThreadIntervalSeconds='120'\n" +
                  "              memoryStoreEvictionPolicy='LRU'\n" +
                  "              />\n" +
                  "    <cache name='dns'\n" +
                  "            maxElementsInMemory='5000'\n" +
                  "            eternal='false'\n" +
                  "            timeToIdleSeconds='86400'\n" +
                  "            timeToLiveSeconds='864000'\n" +
                  "            overflowToDisk='true'\n" +
                  "            maxElementsOnDisk='0'\n" +
                  "            diskPersistent='true'\n" +
                  "            diskExpiryThreadIntervalSeconds='3600'\n" +
                  "            memoryStoreEvictionPolicy='LRU'\n" +
                  "            />\n" +
                  "</ehcache>";

  private CacheManager cacheManager;
  private Cache dnsCache;

  void makeCache() {
    cacheManager = new CacheManager(new StringBufferInputStream(config));
    dnsCache = cacheManager.getCache("dns");
  } */

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

  private class ManagePanel extends JPanel {
    PreferencesExt prefs;

    ManagePanel(PreferencesExt p) {
      this.prefs = p;
      manage = new ManageForm(this.prefs);
      setLayout(new BorderLayout());
      add(manage, BorderLayout.CENTER);

      manage.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (!evt.getPropertyName().equals("Download")) return;
          ManageForm.Data data = (ManageForm.Data) evt.getNewValue();
          try {
            manage.getTextArea().setText(""); // clear the text area
            manage.getStopButton().setCancel(false); // clear the cancel state

            if (data.wantAccess) {
              TdsDownloader logManager = new TdsDownloader(manage.getTextArea(), data, TdsDownloader.Type.access);
              logManager.getRemoteFiles(manage.getStopButton());
            }
            if (data.wantServlet) {
              TdsDownloader logManager = new TdsDownloader(manage.getTextArea(), data, TdsDownloader.Type.thredds);
              logManager.getRemoteFiles(manage.getStopButton());
            }

            if (data.wantRoots) {
              String urls = data.getServerPrefix() + "/thredds/admin/log/dataroots.txt";
              File localDir = LogLocalManager.getDirectory(data.server, "");
              boolean ok = localDir.mkdirs();
              // if (!ok) manage.getTextArea().append("\nmkdirs failed");
              File file = new File(localDir, "roots.txt");
              HTTPSession session = HTTPFactory.newSession(urls);
              // session.setCredentialsProvider(provider);
              session.setUserAgent("TdsMonitor");
              JTextArea ta = manage.getTextArea();

              try {
                HttpClientManager.copyUrlContentsToFile(session, urls, file);
                String roots = IO.readFile(file.getPath());
                ta.append("\nRoots:\n");
                ta.append(roots);
                LogCategorizer.setRoots(roots);

              } catch (IOException ioe) {
                StringWriter sw = new StringWriter(5000);
                ioe.printStackTrace(new PrintWriter(sw));
                ta.setText(sw.toString());
              }
            }

          } catch (Throwable t) {
            t.printStackTrace();
          }

          if (manage.getStopButton().isCancel())
            manage.getTextArea().append("\nDownload canceled by user");
        }
      });

    }

    void save() {
      ComboBox servers = manage.getServersCB();
      servers.save();
    }
  }

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

  private abstract class OpPanel extends JPanel {
    PreferencesExt prefs;
    TextHistoryPane ta;
    IndependentWindow infoWindow;
    JComboBox serverCB;
    JTextArea startDateField, endDateField;
    JPanel topPanel;
    boolean isAccess;
    boolean removeTestReq;
    boolean problemsOnly;

    OpPanel(PreferencesExt prefs, boolean isAccess) {
      this.prefs = prefs;
      this.isAccess = isAccess;
      ta = new TextHistoryPane(true);
      infoWindow = new IndependentWindow("Details", BAMutil.getImage("netcdfUI"), new JScrollPane(ta));
      Rectangle bounds = (Rectangle) prefs.getBean(FRAME_SIZE, new Rectangle(200, 50, 500, 700));
      infoWindow.setBounds(bounds);

      topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));

      // which server
      serverCB = new JComboBox();
      serverCB.setModel(manage.getServersCB().getModel());
      serverCB.addActionListener(e -> {
          String server = (String) serverCB.getSelectedItem();
          setServer(server);
      });

      // serverCB.setModel(manage.getServers().getModel());
      topPanel.add(new JLabel("server:"));
      topPanel.add(serverCB);

      // the date selectors
      startDateField = new JTextArea("                    ");
      endDateField = new JTextArea("                    ");

      topPanel.add(new JLabel("Start Date:"));
      topPanel.add(startDateField);
      topPanel.add(new JLabel("End Date:"));
      topPanel.add(endDateField);

      AbstractAction showAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          showLogs();
        }
      };
      BAMutil.setActionProperties(showAction, "Import", "get logs", false, 'G', -1);
      BAMutil.addActionToContainer(topPanel, showAction);

      AbstractAction filterAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          Boolean state = (Boolean) getValue(BAMutil.STATE);
          removeTestReq = state.booleanValue();
        }
      };
      BAMutil.setActionProperties(filterAction, "time", "remove test Requests", true, 'F', -1);
      BAMutil.addActionToContainer(topPanel, filterAction);

      AbstractAction filter2Action = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          Boolean state = (Boolean) getValue(BAMutil.STATE);
          problemsOnly = state.booleanValue();
        }
      };
      BAMutil.setActionProperties(filter2Action, "time", "only show problems", true, 'F', -1);
      BAMutil.addActionToContainer(topPanel, filter2Action);

      AbstractAction infoAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          Formatter f = new Formatter();
          showInfo(f);
          ta.setText(f.toString());
          infoWindow.show();
        }
      };
      BAMutil.setActionProperties(infoAction, "Information", "info on selected logs", false, 'I', -1);
      BAMutil.addActionToContainer(topPanel, infoAction);

      setLayout(new BorderLayout());
      add(topPanel, BorderLayout.NORTH);
    }

    private LogLocalManager manager;

    public void setServer(String server) {
      manager = new LogLocalManager(server, isAccess);
      manager.getLocalFiles(null, null);
      setLocalManager(manager);
    }

    abstract void setLocalManager(LogLocalManager manager);

    abstract void showLogs();

    abstract void showInfo(Formatter f);

    abstract void resetLogs();

    void save() {
      if (infoWindow != null) prefs.putBeanObject(FRAME_SIZE, infoWindow.getBounds());
    }
  }

  /////////////////////////////////////////////////////////////////////
  String filterIP = "128.117.156,128.117.140,128.117.149";

  private class AccessLogPanel extends OpPanel {
    AccessLogTable logTable;

    AccessLogPanel(PreferencesExt p) {
      super(p, true);
      logTable = new AccessLogTable(startDateField, endDateField, p, dnsLookup);
      logTable.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
        public void propertyChange(java.beans.PropertyChangeEvent e) {
          if (e.getPropertyName().equals("UrlDump")) {
            String path = (String) e.getNewValue();
            gotoUrlDump(path);
          }
        }
      });

      AbstractAction allAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          resetLogs();
        }
      };
      BAMutil.setActionProperties(allAction, "Refresh", "show All Logs", false, 'A', -1);
      BAMutil.addActionToContainer(topPanel, allAction);

      AbstractAction dnsAction = new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
          showDNS();
        }
      };
      BAMutil.setActionProperties(dnsAction, "Dataset", "lookup DNS", false, 'D', -1);
      BAMutil.addActionToContainer(topPanel, dnsAction);

      add(logTable, BorderLayout.CENTER);
    }

    @Override
    void setLocalManager(LogLocalManager manager) {
      logTable.setLocalManager(manager);
    }

    @Override
    void showLogs() {
      LogReader.LogFilter filter = null;
      if (removeTestReq)
        filter = new LogReader.IpFilter(filterIP.split(","), filter);
      if (problemsOnly)
        filter = new LogReader.ErrorOnlyFilter(filter);

      logTable.showLogs(filter);
    }

    void showInfo(Formatter f) {
      logTable.showInfo(f);
    }

    void resetLogs() {
      logTable.resetLogs();
    }

    void showDNS() {
      logTable.showDNS();
    }

    void save() {
      logTable.exit();
      super.save();
    }
  }

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

  private class ServletLogPanel extends OpPanel {
    ServletLogTable logTable;

    ServletLogPanel(PreferencesExt p) {
      super(p, false);
      logTable = new ServletLogTable(startDateField, endDateField, p, dnsLookup);
      add(logTable, BorderLayout.CENTER);
    }

    @Override
    void setLocalManager(LogLocalManager manager) {
      logTable.setLocalManager(manager);
    }

    @Override
    void showLogs() {
      ServletLogTable.MergeFilter filter = null;
      if (removeTestReq)
        filter = new ServletLogTable.IpFilter(filterIP.split(","), filter);
      if (problemsOnly)
        filter = new ServletLogTable.ErrorOnlyFilter(filter);

      logTable.showLogs(filter);
    }

    void resetLogs() {
    }

    void showInfo(Formatter f) {
      logTable.showInfo(f);
    }

    void save() {
      logTable.exit();
      super.save();
    }
  }

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

  /*
   * Finds all files matching
   * a glob pattern.  This method recursively searches directories, allowing
   * for glob expressions like {@code "c:\\data\\200[6-7]\\*\\1*\\A*.nc"}.
   *
   * @param globExpression The glob expression
   * @return List of File objects matching the glob pattern.  This will never
   *         be null but might be empty
   * @throws Exception if the glob expression does not represent an absolute
   *                   path
   * @author Mike Grant, Plymouth Marine Labs; Jon Blower
   *
  java.util.List<File> globFiles(String globExpression) throws Exception {
    // Check that the glob expression is an absolute path.  Relative paths
    // would cause unpredictable and platform-dependent behaviour so
    // we disallow them.
    // If ds.getLocation() is a glob expression this test will still work
    // because we are not attempting to resolve the string to a real path.
    File globFile = new File(globExpression);
    if (!globFile.isAbsolute()) {
      throw new Exception("Dataset location " + globExpression +
              " must be an absolute path");
    }

    // Break glob pattern into path components.  To do this in a reliable
    // and platform-independent way we use methods of the File class, rather
    // than String.split().
    java.util.List<String> pathComponents = new ArrayList<String>();
    while (globFile != null) {
      // We "pop off" the last component of the glob pattern and place
      // it in the first component of the pathComponents List.  We therefore
      // ensure that the pathComponents end up in the right order.
      File parent = globFile.getParentFile();
      // For a top-level directory, getName() returns an empty string,
      // hence we use getPath() in this case
      String pathComponent = parent == null ? globFile.getPath() : globFile.getName();
      pathComponents.add(0, pathComponent);
      globFile = parent;
    }

    // We must have at least two path components: one directory and one
    // filename or glob expression
    java.util.List<File> searchPaths = new ArrayList<File>();
    searchPaths.add(new File(pathComponents.get(0)));
    int i = 1; // Index of the glob path component

    while (i < pathComponents.size()) {
      FilenameFilter globFilter = new GlobFilenameFilter(pathComponents.get(i));
      java.util.List<File> newSearchPaths = new ArrayList<File>();
      // Look for matches in all the current search paths
      for (File dir : searchPaths) {
        if (dir.isDirectory()) {
          // Workaround for automounters that don't make filesystems
          // appear unless they're poked
          // do a listing on searchpath/pathcomponent whether or not
          // it exists, then discard the results
          new File(dir, pathComponents.get(i)).list();

          for (File match : dir.listFiles(globFilter)) {
            newSearchPaths.add(match);
          }
        }
      }
      // Next time we'll search based on these new matches and will use
      // the next globComponent
      searchPaths = newSearchPaths;
      i++;
    }

    // Now we've done all our searching, we'll only retain the files from
    // the list of search paths
    java.util.List<File> filesToReturn = new ArrayList<File>();
    for (File path : searchPaths) {
      if (path.isFile()) filesToReturn.add(path);
    }

    return filesToReturn;
  } */

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

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

    // prefs storage
    try {
      String prefStore = ucar.util.prefs.XMLStore.makeStandardFilename(".unidata", "TdsMonitor.xml");
      store = ucar.util.prefs.XMLStore.createFromFile(prefStore, null);
      prefs = store.getPreferences();
      Debug.setStore(prefs.node("Debug"));
    } catch (IOException e) {
      System.out.println("XMLStore Creation failed " + e);
    }

    // initializations
    BAMutil.setResourcePath("/resources/nj22/ui/icons/");

    // put UI in a JFrame
    frame = new JFrame("TDS Monitor");
    ui = new TdsMonitor(prefs, frame);

    frame.setIconImage(BAMutil.getImage("netcdfUI"));
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        if (!done) ui.exit();
      }
    });

    frame.getContentPane().add(ui);
    Rectangle bounds = (Rectangle) prefs.getBean(FRAME_SIZE, new Rectangle(50, 50, 800, 450));
    frame.setBounds(bounds);

    frame.pack();
    frame.setBounds(bounds);
    frame.setVisible(true);
  }
}


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

package ucar.nc2.dt;

import ucar.nc2.*;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.units.DateFormatter;
import ucar.unidata.geoloc.LatLonRect;

import java.util.*;

/**
 * Superclass for implementations of TypedDataset.
 *
 * @deprecated use ucar.nc2.ft.*
 * @author John Caron
 */

public abstract class TypedDatasetImpl implements TypedDataset {
  protected NetcdfDataset netcdfDataset;
  protected String title, desc, location;
  protected Date startDate, endDate;
  protected LatLonRect boundingBox;
  protected List<VariableSimpleIF> dataVariables = new ArrayList<>();
  protected StringBuffer parseInfo = new StringBuffer();

  /** No-arg constructor */
  public TypedDatasetImpl() {}

  /** Constructor when theres no NetcdfFile underneath.
   *
   * @param title title of the dataset.
   * @param description description of the dataset.
   * @param location URI of the dataset
   */
  public TypedDatasetImpl(String title, String description, String location) {
    this.title = title;
    this.desc = description;
    this.location = location;
  }

  /** Construtor when theres a NetcdfFile underneath
   * @param netcdfDataset adapt this NetcdfDataset
   */
  public TypedDatasetImpl(NetcdfDataset netcdfDataset) {
    this.netcdfDataset = netcdfDataset;
    this.location = netcdfDataset.getLocation();

    this.title = netcdfDataset.getTitle();
    if (title == null)
      title = netcdfDataset.findAttValueIgnoreCase(null, "title", null);
    if (desc == null)
      desc = netcdfDataset.findAttValueIgnoreCase(null, "description", null);
  }

  public void setTitle( String title) { this.title = title; }
  public void setDescription( String desc) { this.desc = desc; }
  public void setLocationURI( String location) {this.location = location; }

  protected abstract void setStartDate(); // reminder for subclasses to set this
  protected abstract void setEndDate(); // reminder for subclasses to set this
  protected abstract void setBoundingBox(); // reminder for subclasses to set this

  protected void removeDataVariable( String varName) {
    Iterator iter = dataVariables.iterator();
    while (iter.hasNext()) {
      VariableSimpleIF v = (VariableSimpleIF) iter.next();
      if (v.getShortName().equals( varName) )
        iter.remove();
    }
  }

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

  public NetcdfFile getNetcdfFile() { return netcdfDataset; }
  public String getTitle() { return title; }
  public String getDescription() { return desc; }
  public String getLocationURI() {return location; }
  public String getLocation() {return location; }
  public List<Attribute> getGlobalAttributes() {
    if (netcdfDataset == null) return new ArrayList<>();
    return netcdfDataset.getGlobalAttributes();
  }

  public Attribute findGlobalAttributeIgnoreCase( String name ) {
    if (netcdfDataset == null) return null;
    return netcdfDataset.findGlobalAttributeIgnoreCase( name);
  }

  public void close() throws java.io.IOException {
    if (netcdfDataset != null) netcdfDataset.close();
  }

  public String getDetailInfo() {
    DateFormatter formatter = new DateFormatter();
    StringBuilder sbuff = new StringBuilder();

    sbuff.append("  location= ").append(getLocation()).append("\n");
    sbuff.append("  title= ").append(getTitle()).append("\n");
    sbuff.append("  desc= ").append(getDescription()).append("\n");
    sbuff.append("  start= ").append(formatter.toDateTimeString(getStartDate())).append("\n");
    sbuff.append("  end  = ").append(formatter.toDateTimeString(getEndDate())).append("\n");
    sbuff.append("  bb   = ").append(getBoundingBox()).append("\n");
    if (getBoundingBox() != null )
      sbuff.append("  bb   = ").append(getBoundingBox().toString2()).append("\n");

    sbuff.append("  has netcdf = ").append(getNetcdfFile() != null).append("\n");
    List<Attribute> ga = getGlobalAttributes();
    if (ga.size() > 0) {
      sbuff.append("  Attributes\n");
      for (Attribute a : ga) {
        sbuff.append("    ").append(a).append("\n");
      }
    }

    List<VariableSimpleIF> vars = getDataVariables();
    sbuff.append("  Variables (").append(vars.size()).append(")\n");
    for (VariableSimpleIF v : vars) {
      sbuff.append("    name='").append(v.getShortName()).append("' desc='").append(v.getDescription()).append("' units='").append(v.getUnitsString()).append("' type=").append(v.getDataType()).append("\n");
    }

    sbuff.append("\nparseInfo=\n");
    sbuff.append(parseInfo);
    sbuff.append("\n");

    return sbuff.toString();
  }

  public Date getStartDate() { return startDate; }
  public Date getEndDate() { return endDate; }
  public LatLonRect getBoundingBox() { return boundingBox; }

  public List<VariableSimpleIF> getDataVariables() {return dataVariables; }
  public VariableSimpleIF getDataVariable( String shortName) {
    for (VariableSimpleIF s : dataVariables) {
      String ss = s.getShortName();
      if (shortName.equals(ss)) return s;
    }
    return null;
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
/*
 * MetarParseReport
 *
 * parses one METAR report into it's variables
 *
 */
package ucar.nc2.dt.point.decode;

import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @deprecated
 */

public class MetarParseReport {

    /**
     * Used to store fields values
    **/
    //private LinkedHashMap<String, String> field = new LinkedHashMap();
    private LinkedHashMap field = new LinkedHashMap();

    /**
     * Used to store fields units
    **/
    //private HashMap<String, String> unit = new HashMap();
    private HashMap unit = new HashMap();

    public boolean parseReport(String input) {

        field.clear();
        unit.clear();
        Matcher m;
        float var1, var2, var3;
        String report = null, remark = null;
        String [] split;

        //	return if( /^\n/ ) ;
        if (MP.B_CR.matcher(input).find()) {
            return false;
        }

        // 	split input into report and remark to distinguish fields
        split = MP.REMARKS.split(input);
        report = split[ 0 ] +" ";
        if( split.length ==  2)
            remark = " "+ split[ 1 ] +" ";

        //	$rep_type = $1 if( s#^(METAR|SPECI|TESTM|TESTS) ## ) ;
        m = MP.B_metar.matcher(report);
        if (m.find()) {
            field.put("Report_Type", m.group(1));
            report = m.replaceFirst("");
        } else { // default
            field.put("Report_Type", "METAR");
        }
        unit.put("Report_Type", "" );

        //	$stn_name = $1 if( s#^(\w4) ## ) ;
        m = MP.station.matcher(report);
        if (m.find()) {
            field.put("Station", m.group(1));
            unit.put("Station", "" );
            report = m.replaceFirst(" ");
        } else {
            return false;
        }

        // all patterns from this point are embeded in spaces

        // get day hour minute
        //	if( s#(\d2)(\d2)(\d2)Z## )
        m = MP.ddhhmmZ.matcher(report);
        if (m.find()) {
            field.put("Day", m.group(1));
            unit.put("Day", "" );
            field.put("Hour", m.group(2));
            unit.put("Hour", "" );
            field.put("Minute", m.group(3));
            unit.put("Minute", "" );
            report = m.replaceFirst(" ");
        } else {
            return false;
        }

        // skip NIL reports
        if (MP.NIL.matcher(report).find()) {
            return false;
        }

        m = MP.COR.matcher(report);
        if (m.find()) {
            report = m.replaceFirst(" ");
        }

        //	$AUTO = 1 if( s#AUTO\s+## ) ;
        m = MP.AUTOS.matcher(report);
        if (m.find()) {
            field.put("AUTOS", "yes");
            unit.put("AUTOS", "" );
            report = m.replaceFirst(" ");
        }

        // get wind direction and speed
        //	if( s#(E|W|N|S)?(\d3|VRB)(\d2,3)(G)?(\d2,3)?(KMH|KT|MPS|MPH)\s+## )
        m = MP.wind_direction_speed.matcher(report);
        if (m.find()) {
            //	if( $2 eq "VRB" )
            if (m.group(2).equals("VRB")) {
                if( m.group(1) == null ) {
                    field.put("Variable_Wind_direction", "" );
                } else {
                    field.put("Variable_Wind_direction", m.group(1));
                }
                unit.put("Variable_Wind_direction", "" );
            } else {
                // $DIR = $2 ;
                field.put("Wind_Direction", m.group(2));
                unit.put("Wind_Direction", "degrees" );
            }
            //	$SPD = $3 ;
            field.put("Wind_Speed", m.group(3));
            //	$GUST = $5 if( $4 eq "G" ) ;
            if (m.group(4) != null && m.group(4).equals("G")) {
                field.put("Wind_Gust", m.group(5));
            }
            //		$UNITS = $6 ; need work  if units != KT
            unit.put("Wind_Speed", m.group(6));
            unit.put("Wind_Gust", m.group(6));
            report = m.replaceFirst(" ");
        }
        // get min|max wind direction
        //	if( s#^(\d3)V(\d3)\s+## )
        m = MP.min_max_wind_dir.matcher(report);
        if (m.find()) {
            //		$DIRmin = $1 ;
            field.put("Wind_Direction_Min", m.group(1));
            unit.put("Wind_Direction_Min", "degrees" );
            //		$DIRmax = $2 ;
            field.put("Wind_Direction_Max", m.group(2));
            unit.put("Wind_Direction_Max", "degrees" );
            report = m.replaceFirst(" ");
        }

        // some reports use a place holder for visibility
        //		s#9999\s+## ;
        report = MP.N9999.matcher(report).replaceFirst(" ");

        // get visibility
        boolean done = false;

        //	$prevail_VIS_SM = 0.0 if( s#M1/4SM\s+|<1/4SM\s+## ) ;
        m = MP.visibilitySM.matcher(report);
        if (m.find()) {
            field.put("Visibility", "0.0");
            unit.put("Visibility", "miles");
            report = m.replaceFirst(" ");
            done = true;
        }

        //	$prevail_VIS_KM = 0.0 if( s#M1/4KM\s+|<1/4KM\s+## ) ;
        if( ! done ) {

            m = MP.visibilityKM.matcher(report);
            if (m.find()) {
                field.put("Visibility", "0.0");
                unit.put("Visibility", "kilometer");
                report = m.replaceFirst(" ");
                done = true;

            }

        }

        //	if( s#(\d1,4) (\d1,3)/(\d1,3)(SM|KM)\s+## )
        if( ! done ) {

            m = MP.visibility1.matcher(report);
            if (m.find()) {
                //	$prevail_VIS_SM = $1 + ( $2 / $3 ) if( $4 eq "SM" ) ;
                var1 = Float.parseFloat(m.group(1));
                var2 = Float.parseFloat(m.group(2));
                var3 = Float.parseFloat(m.group(3));
                var1 = var1 + (var2 / var3);
                if (m.group(4).equals("SM")) {
                    field.put("Visibility", Float.toString(var1));
                    unit.put("Visibility", "miles");
                    // $prevail_VIS_KM = $1 + ( $2 / $3 ) if( $4 eq "KM" ) ;
                } else {
                    field.put("Visibility", Float.toString(var1));
                    unit.put("Visibility", "kilometer");
                }
                report = m.replaceFirst(" ");
                done = true;
            }

        }
        //	 else( s#^(\d1,3)/(\d1,3)(KM|SM)\s+## )
        if( ! done ) {

            m = MP.visibility2.matcher(report);
            if (m.find()) {
                var1 = Float.parseFloat(m.group(1));
                var2 = Float.parseFloat(m.group(2));
                var1 = var1 / var2;
                //	 $prevail_VIS_SM = $1 / $2  if( $3 eq "SM" ) ;
                if (m.group(3).equals("SM")) {
                    field.put("Visibility", Float.toString(var1));
                    unit.put("Visibility", "miles");

                //	 $prevail_VIS_KM = $1 / $2  if( $3 eq "KM" ) ;
                } else {
                    field.put("Visibility", Float.toString(var1));
                    unit.put("Visibility", "kilometer");
                }
                report = m.replaceFirst(" ");
                done = true;
            }
        }
        //	else( s# P?(\d1,4)(KM|SM)\s+## )
        if( ! done ) {
              m = MP.visibility3.matcher(report);
              if (m.find()) {
                  // $prevail_VIS_SM = $1 if( $2 eq "SM" ) ;
                  if (m.group(2).equals("SM")) {
                      field.put("Visibility", m.group(1));
                      unit.put("Visibility", "miles");
                     // $prevail_VIS_KM = $1 if( $2 eq "KM" ) ;
                  } else {
                      field.put("Visibility", m.group(1));
                      unit.put("Visibility", "kilometer");
                  }
                  report = m.replaceFirst(" ");
                  done = true;
              }
        }
        if( ! done ) {
            //	else( s# (\d4)((NE)|(NW)|(SE)|(SW)|(N)|(S)|(E)|(W))\s+## )
            m = MP.visibility_direction.matcher(report);
            if (m.find()) {
            //	$prevail_VIS_M = $1 ;
                field.put("Visibility", m.group(1));
                unit.put("Visibility", "meters");
                // $VIS_dir = $2 ;
                field.put("Visibility_Direction", m.group(2));
                unit.put("Visibility_Direction", "");
                report = m.replaceFirst(" ");
                done = true;
             }
        }

        // 	clear air
        //	$CAVOK = 1 if( s#CAVOK\s+## ) ;
        m = MP.CAVOKS.matcher(report);
        if (m.find()) {
            field.put("Clear_Air", "yes");
            unit.put("Clear_Air", "" );
            report = m.replaceFirst(" ");
        }

        // 	runway decoding here
        //	$RVRNO = 1 if( s#RVRNO\s+## ) ;
        m = MP.RVRNO.matcher(report);
        if (m.find()) {
            //field.put("RVRNOS", "");
            field.put("RunwayReports", "No");
            //unit.put("RVRNOS", "");
            unit.put("RunwayReports", "");
            report = m.replaceFirst(" ");
        }

        for (int i = 0; i < 4; i++) {
            //	if( s# R(\d2)(R|L|C)?/(M|P)?(\d1,4)V?(M|P)?(\d1,4)?(FT|N|D)?\s+## )
            m = MP.runway.matcher(report);
            if (m.find()) {
                //	$RV_designator[ $i ] = "$1$2" ;
                String RV = "RV" + Integer.toString(i +1);
                field.put(RV, m.group(1) );
                unit.put(RV, "");
                /*
                field.put(RV, m.group(1) + m.group(2));
                unit.put(RV, "");

                //	 $RV_above_max[ $i ] = 1
                // if( $3 eq "P" || $5 eq "P" ) ;
                if ((m.group(3) != null && m.group(3).equals("P")) ||
                    (m.group(5) != null && m.group(5).equals("P"))) {
                    field.put(RV + "_Above_Max", "");
                    unit.put(RV + "_Above_Max", "");
                }

                //	$RV_below_min[ $i ] = 1
                //	if( $3 eq "M" || $5 eq "M" ) ;
                if ((m.group(3) != null && m.group(3).equals("M")) ||
                    (m.group(5) != null && m.group(5).equals("M"))) {
                    field.put(RV + "_Below_Min", "");
                    unit.put(RV + "_Below_Min", "");
                }

                //			$RV_vrbl[ $i ] = 1 if( $6 ne "" ) ;
                //			if( $RV_vrbl[ $i ] )
                //				$RV_min[ $i ] = $4 * 1;
                //				$RV_max[ $i ] = $6 * 1;
                if (m.group(6) != null) {
                    field.put(RV + "_Vrbl", "");
                    unit.put(RV + "_Vrbl", "");
                    field.put(RV + "_Min", m.group(4));
                    unit.put(RV + "_Min", "feet");
                    field.put(RV + "_Max", m.group(6));
                    unit.put(RV + "_Max", "feet");
                } else {
                    //				$RV_visRange[ $i ] = $4 * 1;
                    if(m.group(7) != null && m.group(7).equals( "FT")) {
                        field.put(RV + "_Visibility_Range", m.group(4));
                        unit.put(RV + "_Visibility_Range", "feet");
                    } else {
                        field.put(RV + "_Visibility_Range", m.group(4));
                        unit.put(RV + "_Visibility_Range", "");
                    }
                }
                */
                report = m.replaceFirst(" ");
            } else {
                break;
            }
        } // end runway decoding

        // 	Get weather conditions
        // 	code table 4678
        done = true;
        StringBuilder WX = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            // if( s#(\+|-|VC|PR| )(MI|BC|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP)## )
            m = MP.WeatherPrecip.matcher(report);
            //System.out.println( "before if  report ="+ report );
            if (m.find()) {
                done = false;
                //System.out.println( "after if report ="+ report );
                //if (! m.group(1).equals( " " )) {
                    WX.append( m.group(1) );
                //}
                if (m.group(2) != null) {
                    WX.append( m.group(2) );
                }
                WX.append( m.group(3) );
                report = m.replaceFirst(" ");
            }
            //  if( s#(\+|-|VC|PR| )(MI|BC|DR|BL|SH|TS|FZ)?(BR|FG|FU|VA|DU|SA|HZ|PY)## )
            m = MP.WeatherObs.matcher(report);
            if (m.find()) {
                done = false;
                //System.out.println( "after if report ="+ report );
                //if (! m.group(1).equals( " " )) {
                    WX.append( m.group(1) );
                //}
                if (m.group(2) != null) {
                    WX.append( m.group(2) );
                }
                WX.append( m.group(3) );
                report = m.replaceFirst(" ");
            }
            // if( s#(\+|-|VC|PR| )(MI|BC|DR|BL|SH|TS|FZ)?(PO|SQ|FC|SS|DS)## )
            m = MP.WeatherOther.matcher(report);
            if (m.find()) {
                done = false;
                //System.out.println( "after if report ="+ report );
                //if (! m.group(1).equals( " " )) {
                    WX.append( m.group(1) );
                //}
                if (m.group(2) != null) {
                    WX.append( m.group(2) );
                }
                WX.append( m.group(3) );
                report = m.replaceFirst(" ");
            }
            if( done )
                break;
            done = true;
            WX.append( " " );
        }
        if (WX.length() > 0) {
            WX.setLength( WX.length() -1 );
            field.put("Weather", WX.toString() );
            unit.put("Weather", "");
        }
        //System.out.println( "after if report ="+ report );

        // 	Interpret cloud conditions
        //	$cloud_type[ 0 ] = $1 if( s#(CLR|SKC)\s+## ) ;
        m = MP.CLR_or_SKC.matcher(report);
        if (m.find()) {
            field.put("Cloud_Type", m.group(1));
            unit.put("Cloud_Type", "");
            report = m.replaceFirst(" ");
        }

        //	$vert_VIS = cloud_hgt2_meters( $1 ) if( s#^VV(\d3)\s+## ) ;
        m = MP.vertical_VIS.matcher(report);
        if (m.find()) {
            field.put("Vertical_Visibility", cloud_hgt2_meters(m.group(1)));
            unit.put("Vertical_Visibility", "meters" );
            report = m.replaceFirst(" ");
        }

        // 	cloud layers up to 6
        for (int i = 0; i < 6; i++) {
            //		if( s# (\+|-)?(OVC|SCT|FEW|BKN)(\d3)(\w1,3)?\s+## )
            m = MP.cloud_cover.matcher(report);
            if (m.find()) {
                String cloud = "Cloud_Layer_" + Integer.toString(i + 1);
                if (m.group(1) == null) {
                    field.put(cloud + "_Type", m.group(2));
                } else {
                    field.put(cloud + "_Type", m.group(1) + m.group(2));
                }
                unit.put(cloud + "_Type", "");
                // just report meters
                // $cloud_hgt[ $i ] = $3 * 100 ;
                //field.put(cloud + "_Height", Integer.toString(Integer.parseInt(m.group(3)) * 100));
                //unit.put(cloud + "_Height", "feet");

                 //	$cloud_meters[ $i ] = cloud_hgt2_meters( $3 ) ;
                field.put(cloud + "_Height", cloud_hgt2_meters(m.group(3)));
                unit.put(cloud + "_Height", "meters");
                //	$cloud_phenom[ $i ] = padstr( $4, 4 ) if( $4 ) ;
                if (m.group(4) != null) {
                    field.put(cloud + "_Phenom", m.group(4));
                    unit.put(cloud + "_Phenom", "");
                }
                report = m.replaceFirst(" ");
            } else {
                break;
            }
        } // end clouds

        // 	Temperature and Dew Point, try temp tenths first
        float air_temperature = -999;
        float dew_point_temperature = -999;
        //	if( s#T(0|1)(\d3)(0|1)?(\d3)?\s+## )
        if( remark != null)
            m = MP.Temperature_tenths.matcher(remark);

        if ( remark != null && m.find() ) {
            //		if( $1 == 0 )
            //			$T_tenths = 0.1 * $2 ;
            //	 	else
            //			$T_tenths = -0.1 * $2 ;
            //
            air_temperature = (float)(Float.parseFloat(m.group(2)) * .1);
            if (m.group(1).equals("1")) {
                //T = "-" + T;
                air_temperature *= -1;
            }
            field.put("Temperature", Float.toString( air_temperature ) );
            unit.put("Temperature", "Celsius");

            //		if( defined( $3 ) && $3 == 0 )
            //			$TD_tenths = 0.1 * $4 ;
            //	 	elsif( defined( $3 ) && $3 == 1 )
            //			$TD_tenths = -0.1 * $4 ;
            //
            if (m.group(3) != null) {
                dew_point_temperature = (float)(Float.parseFloat(m.group(4)) * .1);
                //			$TD *= -1 if( $3 ) ;
                if (m.group(3).equals("1")) {
                    //TD = "-" + TD;
                    dew_point_temperature *= -1;
                }
                field.put("DewPoint", Float.toString( dew_point_temperature ));
                unit.put("DewPoint", "Celsius");
            }
            remark = m.replaceFirst(" ");

        } else { // check for coarse temperature
            // 	get temperature and dew point
            //	if( s#^(M)?(\d2)/(M)?(\d2)?\s+## )
            m = MP.Temperature.matcher(report);
            if (m.find()) {
                //		$T = $2 ;
                //String T = m.group(2);
                air_temperature = Float.parseFloat(m.group(2));
                //		$T *= -1 if( $1 ) ;
                //if(  m.group( 1 ).equals( "M" )  )
                if (m.group(1) != null) {
                    //T = "-" + T;
                    air_temperature *= -1;
                }
                field.put("Temperature", Float.toString( air_temperature ) );
                unit.put("Temperature", "Celsius");
                //		$TD = $4 if( defined( $4 ) ) ;
                if (m.group(4) != null) {
                    dew_point_temperature = Float.parseFloat(m.group(4));
                    //			$TD *= -1 if( $3 ) ;
                    if (m.group(3) != null) {
                        //TD = "-" + TD;
                       dew_point_temperature *= -1;
                    }
                    field.put("DewPoint", Float.toString( dew_point_temperature ));
                    unit.put("DewPoint", "Celsius");
                }
                report = m.replaceFirst(" ");
            } // end T and TD
        }

        // 	get Altimeter settings
        //	if( s# (A|Q)(\d4\.?\d?)\s+## )
        m = MP.altimeter.matcher(report);
        if (m.find()) {
            // if( $1 eq "A" )
            if (m.group(1).equals("A")) {
                // $inches_ALTIM = $2 * 0.01 ;
                field.put("Altimeter", Double.toString(Float.parseFloat(m.group(2)) * 0.01));
                unit.put("Altimeter", "inches");
            } else {
                // $hectoPasc_ALTIM = $2 ;
                field.put("Altimeter", m.group(2));
                unit.put("Altimeter", "hectopascal");
            }
            report = m.replaceFirst(" ");
        }

        //	$NOSIG = 1 if( s#NOSIG## ) ;
        m = MP.NOSIG.matcher(report);
        if (m.find()) {
            field.put("Weather", "No");
            unit.put("Weather", "");
            report = m.replaceFirst(" ");
        }

        // 	check for remarks or done
        if( remark == null)
            return true;

        // process remarks now, looking for most used ones first

        // get Automated reports
        //	 if( s#(A01|A01A|A02|A02A|AO1|AO1A|AO2|AO2A|AOA)\s+## ) ;
        m = MP.automatic_report.matcher(remark);
        if (m.find()) {
            field.put("Automatic_Report", m.group(1));
            unit.put("Automatic_Report", "");
            remark = m.replaceFirst(" ");
        }

        //	check if no more info in report
        if (MP.spaces.matcher(remark).matches()) {
            return true;
        }

        // Sea-Level presure not available
        //$SLPNO = 1 if( s# SLPNO\s+## ) ;
        m = MP.SLPNO.matcher(remark);
        if (m.find()) {
            //field.put("SLPNO", "yes");
            //unit.put("SLPNO", "");
            field.put("Sea_Level_Pressure_available", "No");
            unit.put("Sea_Level_Pressure_available", "");
            remark = m.replaceFirst(" ");
        }

        //if( s# SLP\s?(\d3)\s+## )
        m = MP.SLP.matcher(remark);
        if (m.find()) {
            float slp;
            //	if( $1 >= 550 )
            if (Integer.parseInt(m.group(1)) >= 550) {
                // $SLP = $1 / 10. + 900. ;
                field.put("Sea_Level_Pressure", Double.toString(Float.parseFloat(m.group(1)) * 0.1 + 900));
            } else {
                // $SLP =  $1 / 10. + 1000. ;
                field.put("Sea_Level_Pressure", Double.toString(Float.parseFloat(m.group(1)) * 0.1 + 1000));
            }
            unit.put("Sea_Level_Pressure", "hectopascal");
            remark = m.replaceFirst(" ");
        }
        //	check if no more info in report
        if (MP.spaces.matcher(remark).matches()) {
            return true;
        }

        // 	Hourly precipitation amount
        //	$PRECIP_hourly = $1 / 100 if( s#P ?(\d1,5)\s+## ) ;
        m = MP.hourly_precip.matcher(remark);
        if (m.find()) {
            field.put("Hourly_Precipitation", Double.toString(Float.parseFloat(m.group(1)) * .01));
            unit.put("Hourly_Precipitation", "inches");
            remark = m.replaceFirst(" ");
        }

        //	check if no more info in report
        if (MP.spaces.matcher(remark).matches()) {
            return true;
        }

        // precipitation sensor not working  PWINO
        //$PWINO = 1 if( s#PWINO\s+## ) ;
        m = MP.PWINO.matcher(remark);
        if (m.find()) {
            //field.put("PWINO", "");
            //unit.put("PWINO", "");
            field.put("PRECIP_sensor_working", "No");
            unit.put("PRECIP_sensor_working", "");
            remark = m.replaceFirst(" ");
        }

        //	check if no more info in report
        if (MP.spaces.matcher(remark).matches()) {
            return true;
        }

        // 	Lightning detection sensor not working  TSNO
        //	$TSNO = 1 if( s#TSNO\s+## ) ;
        m = MP.TSNO.matcher(remark);
        if (m.find()) {
            //field.put("TSNO", "yes");
            //unit.put("TSNO", "");
            field.put("Lightning_sensor_working", "No");
            unit.put("Lightning_sensor_working", "");
            remark = m.replaceFirst(" ");
        }

        //	check if no more info in report
        if (MP.spaces.matcher(remark).matches()) {
            return true;
        }

        // 	get Tornado data if present
        //	if( s#(TORNADO\w0,2|WATERSPOUTS*|FUNNEL CLOUDS*)\s+## )
        m = MP.tornado.matcher(remark);
        if (m.find()) {
            field.put("TornadicType", m.group(1));
            unit.put("TornadicType", "");
            remark = m.replaceFirst(" ");
            // if( s#(B|E)(\d\d)(\d\d)?\s+## )
            m = MP.tornadoTime.matcher(remark);
            if (m.find()) {
                String time;
                String units;
                if(m.group(2) == null ) {
                    time = m.group(3);
                    units = "mm";
                } else {
                    time = m.group(2) + m.group(3);
                    units = "hhmm";
                }
                // if( $1 eq "B" )
                if (m.group(1).equals("B")) {
                    // $BTornadic_hh = $2 ;
                    // $BTornadic_mm = $3 ;
                    field.put("Begin_Tornado", time);
                    unit.put("Begin_Tornado", units);
                } else {
                     // $ETornadic_hh = $2 ;
                     //	 $ETornadic_mm = $3 if( defined( $3 ) ) ;
                    field.put("End_Tornado", time);
                    unit.put("End_Tornado", units);
                }
                remark = m.replaceFirst(" ");
            }
            // $TornadicLOC = padstr( $1, 10 )
            //	 if( s#^(DSNT|VCY STN|VC STN|VCY|VC)\s+## ) ;
            m = MP.tornadoLocation.matcher(remark);
            if (m.find()) {
                field.put("Tornado_Location", m.group(1));
                unit.put("Tornado_Location", "");
                remark = m.replaceFirst(" ");
            }

             // $TornadicDIR = padstr( $1, 2 )
            //	if( s#^(NE|NW|SE|SW|N|S|E|W)\s+## ) ;
            m = MP.tornadoDirection.matcher(remark);
            if (m.find()) {
                field.put("Tornado_Direction", m.group(1));
                unit.put("Tornado_Direction", "");
                remark = m.replaceFirst(" ");
            }
        } // end tornado

        // 	get Peak winds
        //	if( s#PK WND (\d3)(\d1,3)/(\d\d)?(\d\d)\s+## )
        m = MP.peakWind.matcher(remark);
        if (m.find()) {
            // $PKWND_dir = $1 ;
            field.put("Peak_Wind_Direction", m.group(1));
            unit.put("Peak_Wind_Direction", "degrees");
            // $PKWND_spd = $2 ;
            field.put("Peak_Wind_Speed", m.group(2));
            unit.put("Peak_Wind_Speed", "knots");
            // $PKWND_hh = $3 if( defined( $3 ) ) ;
            // $PKWND_mm = $4 ;
            String time;
            String units;
            if(m.group(3) == null ) {
                 time = m.group(4);
                 units = "mm";
            } else {
                 time = m.group(3) + m.group(4);
                 units = "hhmm";
            }
            field.put("Peak_Wind_Time", time );
            unit.put("Peak_Wind_Time", units );
            remark = m.replaceFirst(" ");
        }

        // 	get Wind shift
        //	if( s#WSHFT (\d\d)?(\d\d)\s+## )
        m = MP.windShift.matcher(remark);
        if (m.find()) {
            // $WshfTime_hh = $1 if( defined( $1 ) );
            // $WshfTime_mm = $2 ;
            String time;
            String units;
            if(m.group(1) == null ) {
                 time = m.group(2);
                 units = "mm";
            } else {
                 time = m.group(1) + m.group(2);
                 units = "hhmm";
            }

            field.put("Wind_Shift", time);
            unit.put("Wind_Shift", units);
            remark = m.replaceFirst(" ");
        }

        // 	get FROPO ( wind shift because of frontal passage )
        //	$Wshft_FROPA = 1 if( s#FROPA\s+## ) ;
        m = MP.FROPA.matcher(remark);
        if (m.find()) {
            field.put("Wind_Shift_Frontal_Passage", "Yes");
            unit.put("Wind_Shift_Frontal_Passage", "");
            remark = m.replaceFirst(" ");
        }

        // 	Tower visibility
        //	if( s#TWR (VIS|VSBY) (\d1,3) (\d1,2)/(\d1,2)\s+## )
        //	$VIS_TWR = $2 + ( $3 / $4 ) ;
        m = MP.towerVisibility1.matcher(remark);
        if (m.find()) {
            var1 = Float.parseFloat(m.group(2));
            var2 = Float.parseFloat(m.group(3));
            var3 = Float.parseFloat(m.group(4));
            var1 = var1 + (var2 / var3);
            field.put("Tower_Visibility", Float.toString(var1));
            unit.put("Tower_Visibility", "miles");
            remark = m.replaceFirst(" ");
            // 	elsif( s#TWR (VIS|VSBY) (\d1,2)/(\d1,2)\s+## )
            //		$VIS_TWR = ( $2 / $3 ) ;
        } else {
            m = MP.towerVisibility2.matcher(remark);
            if (m.find()) {
                var1 = Float.parseFloat(m.group(2));
                var2 = Float.parseFloat(m.group(3));
                var1 = var1 / var2;
                field.put("Tower_Visibility", Float.toString(var1));
                unit.put("Tower_Visibility", "miles");
                remark = m.replaceFirst(" ");
                // 		elsif( s#TWR (VIS|VSBY) (\d1,3)\s+## )
                //			$VIS_TWR = $2 ;
            } else {
                m = MP.towerVisibility3.matcher(remark);
                if (m.find()) {
                    field.put("Tower_Visibility", m.group(2));
                    unit.put("Tower_Visibility", "miles");
                    remark = m.replaceFirst(" ");
                }
            }
        }

        // Surface visibility
        //	if( s# SFC (VIS|VSBY) (\d1,3) (\d1,2)/(\d1,2)\s+## )
        //	$VIS_SFC = $2 + ( $3 / $4 ) ;
        m = MP.surfaceVisibility1.matcher(remark);
        if (m.find()) {
            var1 = Float.parseFloat(m.group(2));
            var2 = Float.parseFloat(m.group(3));
            var3 = Float.parseFloat(m.group(4));
            var1 = var1 + (var2 / var3);
            field.put("Surface_Visibility", Float.toString(var1));
            unit.put("Surface_Visibility", "miles");
            remark = m.replaceFirst(" ");
            // 		elsif( s# SFC (VIS|VSBY) (\d1,2)/(\d1,2)\s+## )
            //			$VIS_SFC = ( $2 / $3 ) ;
        } else {
            m = MP.surfaceVisibility2.matcher(remark);
            if (m.find()) {
                var1 = Float.parseFloat(m.group(2));
                var2 = Float.parseFloat(m.group(3));
                var1 = var1 / var2;
                field.put("Surface_Visibility", Float.toString(var1));
                unit.put("Surface_Visibility", "miles");
                remark = m.replaceFirst(" ");
                //  elsif( s# SFC (VIS|VSBY) (\d1,3)\s+## )
                // $VIS_SFC = $2 ;
            } else {
                m = MP.surfaceVisibility3.matcher(remark);
                if (m.find()) {
                    field.put("Surface_Visibility", m.group(2));
                    unit.put("Surface_Visibility", "miles");
                    remark = m.replaceFirst(" ");
                }
            }
        }

        // 	Variable visibility
        //	if( s#(VIS|VSBY) (\d1,3) (\d1,2)/(\d1,2)V(\d1,3) (\d1,2)/(\d1,2)\s+## )
        //		$VISmin = $2 + ( $3 / $4 ) ;
        //		$VISmax = $5 + ( $6 / $7 ) ;
        m = MP.variableVisibility1.matcher(remark);
        if (m.find()) {
            var1 = Float.parseFloat(m.group(2));
            var2 = Float.parseFloat(m.group(3));
            var3 = Float.parseFloat(m.group(4));
            var1 = var1 + (var2 / var3);
            field.put("Variable_Visibility_Min", Float.toString(var1));
            unit.put("Variable_Visibility_Min", "miles");
            var1 = Float.parseFloat(m.group(5));
            var2 = Float.parseFloat(m.group(6));
            var3 = Float.parseFloat(m.group(7));
            var1 = var1 + (var2 / var3);
            field.put("Variable_Visibility_Max", Float.toString(var1));
            unit.put("Variable_Visibility_Max", "miles");
            remark = m.replaceFirst(" ");
            //	 elsif( s#(VIS|VSBY) (\d1,3)V(\d1,3) (\d1,2)/(\d1,2)\s+## )
            //		$VISmin = $2 ;
            //		$VISmax = $3 + ( $4 / $5 ) ;
        } else {
            m = MP.variableVisibility2.matcher(remark);
            if (m.find()) {
                field.put("Variable_Visibility_Min", m.group(2));
                unit.put("Variable_Visibility_Min", "miles");
                var1 = Float.parseFloat(m.group(3));
                var2 = Float.parseFloat(m.group(4));
                var3 = Float.parseFloat(m.group(5));
                var1 = var1 + (var2 / var3);
                field.put("Variable_Visibility_Max", Float.toString(var1));
                unit.put("Variable_Visibility_Max", "miles");
                remark = m.replaceFirst(" ");
                //  elsif( s#(VIS|VSBY) (\d1,2)/(\d1,2)V(\d1,3) (\d1,2)/(\d1,2)\s+## )
                //			$VISmin = ( $2 / $3 ) ;
                //			$VISmax = $4 + ( $5 / $6 ) ;
            } else {
                m = MP.variableVisibility3.matcher(remark);
                if (m.find()) {
                    var1 = Float.parseFloat(m.group(2));
                    var2 = Float.parseFloat(m.group(3));
                    var1 = var1 / var2;
                    field.put("Variable_Visibility_Min", Float.toString(var1));
                    unit.put("Variable_Visibility_Min", "miles");
                    var1 = Float.parseFloat(m.group(4));
                    var2 = Float.parseFloat(m.group(5));
                    var3 = Float.parseFloat(m.group(6));
                    var1 = var1 + (var2 / var3);
                    field.put("Variable_Visibility_Max", Float.toString(var1));
                    unit.put("Variable_Visibility_Max", "miles");
                    remark = m.replaceFirst(" ");
                    // elsif( s#(VIS|VSBY) (\d1,3) (\d1,2)/(\d1,2)V(\d1,3)\s+## )
                    //	 $VISmin = $2 + ( $3 / $4 ) ;
                    //	 $VISmax = $5 ;
                } else {
                    m = MP.variableVisibility4.matcher(remark);
                    if (m.find()) {
                        var1 = Float.parseFloat(m.group(2));
                        var2 = Float.parseFloat(m.group(3));
                        var3 = Float.parseFloat(m.group(4));
                        var1 = var1 + (var2 / var3);
                        field.put("Variable_Visibility_Min", Float.toString(var1));
                        unit.put("Variable_Visibility_Min", "miles");
                        field.put("Variable_Visibility_Max", m.group(5));
                        unit.put("Variable_Visibility_Max", "miles");
                        remark = m.replaceFirst(" ");
                        // elsif( s#(VIS|VSBY) (\d1,3)V(\d1,3)\s+## )
                        //	 $VISmin = $2 ;
                        //	 $VISmax = $3 ;
                    } else {
                        m = MP.variableVisibility5.matcher(remark);
                        if (m.find()) {
                            field.put("Variable_Visibility_Min", m.group(2));
                            unit.put("Variable_Visibility_Min", "miles");
                            field.put("Variable_Visibility_Max", m.group(3));
                            unit.put("Variable_Visibility_Max", "miles");
                            remark = m.replaceFirst(" ");
                            // elsif( s#(VIS|VSBY) (\d1,2)/(\d1,2)V(\d1,3)\s+## )
                            //	 $VISmin = ( $2 / $3 ) ;
                            //	 $VISmax = $4 ;
                        } else {
                            m = MP.variableVisibility6.matcher(remark);
                            if (m.find()) {
                                var1 = Float.parseFloat(m.group(2));
                                var2 = Float.parseFloat(m.group(3));
                                var1 = var1 / var2;
                                field.put("Variable_Visibility_Min", Float.toString(var1));
                                unit.put("Variable_Visibility_Min", "miles");
                                field.put("Variable_Visibility_Max", m.group(4));
                                unit.put("Variable_Visibility_Max", "miles");
                                remark = m.replaceFirst(" ");
                            }
                        }
                    }
                }
            }
        } // end variableVisibility

        // 	Second site visiblity
        //	if( s#(VIS|VSBY) (\d1,3) (\d1,2)/(\d1,2) (RY\d1,2)\s+## )
        //		$VIS_2ndSite = $2 + ( $3 / $4 ) ;
        //		$VIS_2ndSite_LOC = padstr( $5, 10 ) ;
        m = MP.Visibility2ndSite1.matcher(remark);
        if (m.find()) {
            var1 = Float.parseFloat(m.group(2));
            var2 = Float.parseFloat(m.group(3));
            var3 = Float.parseFloat(m.group(4));
            var1 = var1 + (var2 / var3);
            field.put("Second_Site_Visibility", Float.toString(var1));
            unit.put("Second_Site_Visibility", "miles");
            field.put("Second_Site_Location", m.group(5));
            unit.put("Second_Site_Location", "");
            remark = m.replaceFirst(" ");
            // 	elsif( s#(VIS|VSBY) (\d1,3) (RY\d1,2)\s+## )
            //		$VIS_2ndSite = $2 ;
            //		$VIS_2ndSite_LOC = padstr( $3, 10 ) ;
        } else {
            m = MP.Visibility2ndSite2.matcher(remark);
            if (m.find()) {
                field.put("Second_Site_Visibility", m.group(2));
                unit.put("Second_Site_Visibility", "miles");
                field.put("Second_Site_Location", m.group(3));
                unit.put("Second_Site_Location", "");
                remark = m.replaceFirst(" ");
                // elsif( s#(VIS|VSBY) (\d1,2)/(\d1,2) (RY\d1,2)\s+## )
                //	 $VIS_2ndSite = ( $2 / $3 ) ;
                //	 $VIS_2ndSite_LOC = padstr( $4, 10 ) ;
            } else {
                m = MP.Visibility2ndSite3.matcher(remark);
                if (m.find()) {
                    var1 = Float.parseFloat(m.group(2));
                    var2 = Float.parseFloat(m.group(3));
                    var1 = var1 / var2;
                    field.put("Second_Site_Visibility", Float.toString(var1));
                    unit.put("Second_Site_Visibility", "miles");
                    field.put("Second_Site_Location", m.group(4));
                    unit.put("Second_Site_Location", "");
                    remark = m.replaceFirst(" ");
                }
            }
        } // end Second site visiblity

/* let lighning go to extra fields cuz convnetion not followed
        // 	Lightning data ( Occasional,Frequent,Continuous) and
        //	(Cloud-Ground,In-Cloud,Cloud-Cloud,Cloud-Air)
        //	if( s# (OCNL|FRQ|CNS) LTG\s?(CG|IC|CC|CA)\s?(DSNT|AP|VCY STN|VCNTY STN)?\s?(NE|NW|SE|SW|N|S|E|W)?\s+## )
        m = MP.Lightning.matcher(remark);
        if (m.find()) {
           //		$LTG_OCNL = 1 if( $1 eq "OCNL" ) ;
           //		$LTG_FRQ = 1 if( $1 eq "FRQ" ) ;
           //		$LTG_CNS = 1 if( $1 eq "CNS" ) ;
           //		$LTG_CG = 1 if( $2 eq "CG" ) ;
           //		$LTG_IC = 1 if( $2 eq "IC" ) ;
           //		$LTG_CC = 1 if( $2 eq "CC" ) ;
           //		$LTG_CA = 1 if( $2 eq "CA" ) ;
           //		$LTG_DSNT = 1 if( $3 eq "DSNT" ) ;
           //		$LTG_AP = 1 if( $3 eq "AP" ) ;
           //		$LTG_VcyStn = 1 if( $3 eq "VCY STN" || $3 eq "VCNTY STN" ) ;
           //		$LTG_DIR = padstr( $4, 2 ) if( defined( $4 ) ) ;
           field.put("Lightning", m.group(1));
           unit.put("Lightning", "");
           remark = m.replaceFirst(" ");
        } // end Lightning data
*/

        // 	get min/max for Variable Ceiling
        //	if( s#CIG (\d1,4)V(\d1,4)\s+## )
        //		$Ceiling_min = $1 ;
        //		$Ceiling_max = $2 ;
        m = MP.CIG.matcher(remark);
        if (m.find()) {
            field.put("Ceiling_Min", Integer.toString(Integer.parseInt(m.group(1)) * 100));
            unit.put("Ceiling_Min", "feet");
            field.put("Ceiling_Max", Integer.toString(Integer.parseInt(m.group(2)) * 100));
          unit.put("Ceiling_Max", "feet");
            remark = m.replaceFirst(" ");
        }

        //	 ? about SKY condition at 2nd location
        // 	get 2nd site ceiling and location
        //	if( s#CIG (\d3) (RY\d1,2)\s+## )
        //		$CIG_2ndSite_meters = $1 * 10 ;
        //		$CIG_2ndSite_LOC = $2 ;
        m = MP.CIG_RY.matcher(remark);
        if (m.find()) {
            var1 = Float.parseFloat(m.group(1)) * 10;
            field.put("Second_Site_Sky", Float.toString(var1));
            unit.put("Second_Site_Sky", "feet");
            field.put("Second_Site_Sky_Location", m.group(2));
            unit.put("Second_Site_Sky_Location", "");
            remark = m.replaceFirst(" ");
        }

        // 	Presure falling rapidly
        //	$PRESFR = 1 if( s#PRESFR/?\s+## ) ;
        m = MP.PRESFR.matcher(remark);
        if (m.find()) {
            field.put("Pressure_Falling_Rapidly", "Yes");
            unit.put("Pressure_Falling_Rapidly", "");
            remark = m.replaceFirst(" ");
        }

        // 	Presure rising rapidly
        //	$PRESRR = 1 if( s#PRESRR/?\s+## ) ;
        m = MP.PRESRR.matcher(remark);
        if (m.find()) {
            field.put("Pressure_Rising_Rapidly", "Yes");
            unit.put("Pressure_Rising_Rapidly", "");
            remark = m.replaceFirst(" ");
        }

        // 	Sector visibility
        //	if( s# (VIS|VSBY) (NE|NW|SE|SW|N|S|E|W)(\d1,3) (\d1,2)/(\d1,2)\s+## )
        //		$SectorVIS_DIR = padstr( $2, 2 ) ;
        //		$SectorVIS = $3 + ( $4 / $5 ) ;
        m = MP.sectorVisibility1.matcher(remark);
        if (m.find()) {
            field.put("Sector_Visibility_Direction", m.group(2));
            unit.put("Sector_Visibility_Direction", "");
            var1 = Float.parseFloat(m.group(3));
            var2 = Float.parseFloat(m.group(4));
            var3 = Float.parseFloat(m.group(5));
            var1 = var1 + (var2 / var3);
            field.put("Sector_Visibility", Float.toString(var1));
            unit.put("Sector_Visibility", "miles");
            remark = m.replaceFirst(" ");
            // 	elsif( s#(VIS|VSBY) (NE|NW|SE|SW|N|S|E|W) (\d1,2)/(\d1,2)\s+## )
            //		$SectorVIS_DIR = padstr( $2, 2 ) ;
            //		$SectorVIS = ( $3 / $4 ) ;
        } else {
            m = MP.sectorVisibility2.matcher(remark);
            if (m.find()) {
                field.put("Sector_Visibility_Direction", m.group(2));
                unit.put("Sector_Visibility_Direction", "");
                var1 = Float.parseFloat(m.group(3));
                var2 = Float.parseFloat(m.group(4));
                var1 = var1 / var2;
                field.put("Sector_Visibility", Float.toString(var1));
                unit.put("Sector_Visibility", "miles");
                remark = m.replaceFirst(" ");
                // 		elsif( s#(VIS|VSBY) (NE|NW|SE|SW|N|S|E|W)(\d1,3)\s+## )
                //			$SectorVIS_DIR = padstr( $2, 2 ) ;
                //			$SectorVIS = $3 ;
            } else {
                m = MP.sectorVisibility3.matcher(remark);
                if (m.find()) {
                    field.put("Sector_Visibility_Direction", m.group(2));
                    unit.put("Sector_Visibility_Direction", "");
                    field.put("Sector_Visibility", m.group(3));
                    unit.put("Sector_Visibility", "miles");
                    remark = m.replaceFirst(" ");
                }
            }
        }

        // 	Hailstone activity and size
        //	if( s# GR M1/4\s+## )
        //		$GR = 1 ;
        //		$GRsize = 1 / 8 ;
        m = MP.GR1.matcher(remark);
        if (m.find()) {
            field.put("Hailstone_Activity", "yes");
            unit.put("Hailstone_Activity", "");
            field.put("Hailstone_Size", "0.25");
            unit.put("Hailstone_Size", "");
            remark = m.replaceFirst(" ");
            // 	elsif( s# GR (\d1,3) (\d1,2)/(\d1,2)\s+## )
            //		$GR = 1 ;
            //		$GRsize = $1 + ( $2 / $3 ) ;
        } else {
            m = MP.GR2.matcher(remark);
            if (m.find()) {
                field.put("Hailstone_Activity", "yes");
                unit.put("Hailstone_Activity", "");
                var1 = Float.parseFloat(m.group(1));
                var2 = Float.parseFloat(m.group(2));
                var3 = Float.parseFloat(m.group(3));
                var1 = var1 + (var2 / var3);
                field.put("Hailstone_Size", Float.toString(var1));
                unit.put("Hailstone_Size", "");
                remark = m.replaceFirst(" ");
                // 		elsif( s# GR (\d1,2)/(\d1,2)\s+## )
                //			$GR = 1 ;
                //			$GRsize = ( $1 / $2 ) ;
            } else {
                m = MP.GR3.matcher(remark);
                if (m.find()) {
                    field.put("Hailstone_Activity", "yes");
                    unit.put("Hailstone_Activity", "");
                    var1 = Float.parseFloat(m.group(1));
                    var2 = Float.parseFloat(m.group(2));
                    var1 = var1 / var2;
                    field.put("Hailstone_Size", Float.toString(var1));
                    unit.put("Hailstone_Size", "");
                    remark = m.replaceFirst(" ");
                    // 			elsif( s#GR (\d1,3)\s+## )
                    //				$GR = 1 ;
                    //				$GRsize = $1 ;
                } else {
                    m = MP.GR4.matcher(remark);
                    if (m.find()) {
                        field.put("Hailstone_Activity", "yes");
                        unit.put("Hailstone_Activity", "");
                        field.put("Hailstone_Size", m.group(1));
                        unit.put("Hailstone_Size", "");
                        remark = m.replaceFirst(" ");
                    }
                }
            }
        }
        //	$GR = 1 if( s# GS\s+## ) ;
        m = MP.GR.matcher(remark);
        if (m.find()) {
            field.put("Hailstone_Activity", "yes");
            unit.put("Hailstone_Activity", "");
            remark = m.replaceFirst(" ");
        }

        // 	VIRGA activity
        //	if( s#VIRGA (DSNT )?(NE|NW|SE|SW|N|S|E|W)?\s+## )
        //		$VIRGA = 1 ;
        //		$VIRGAdir = padstr( $2, 2 ) if( $2 ) ;
        m = MP.VIRGA.matcher(remark);
        if (m.find()) {
            field.put("Virga_Activity", "yes");
            unit.put("Virga_Activity", "");
            if( m.group(1) != null ) {
                field.put("Virga_Direction", m.group(1));
                unit.put("Virga_Direction", "");
            }
            if( m.group(2) != null ) {
                field.put("Virga_Direction", m.group(2));
                unit.put("Virga_Direction", "");
            }
            remark = m.replaceFirst(" ");
        }

        // 	Surface-based Obscuring Phenomena  SfcObscuration weather conditions
        // 	code table 4678
        //	if( s#-X(VC|PR)?(MI|BC|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS|DS)?(\d)\s+## )
        //		$SfcObscuration = padstr( "$1$2$3$4$5", 8 ) ;
        //		$OctsSkyObscured = $6 ;
        /* not coded to current implementation, so let it go to plain lang remarks
        m = MP.obscuring.matcher(remark);
        if (m.find()) {
            String tmp = "";
            if (m.group(1) != null) {
                tmp = m.group(1);
            }
            if (m.group(2) != null) {
                tmp = m.group(2);
            }
            if (m.group(3) != null) {
                tmp = m.group(3);
            }
            if (m.group(4) != null) {
                tmp = m.group(4);
            }
            if (m.group(5) != null) {
                tmp = m.group(5);
            }
            if ( ! tmp.equals("")) {
                field.put("Surface_Obscuration", tmp );
                unit.put("Surface_Obscuration", "" );
            }
            field.put("OctsSkyObscured", m.group(6));
            unit.put("OctsSkyObscured", "");
            remark = m.replaceFirst(" ");
        }
        */

        // 	get Ceiling_est or Ceiling height
        //	$CIGNO = 1 if( s#CIGNO\s+## ) ;
        m = MP.CIGNO.matcher(remark);
        if (m.find()) {
            //field.put("CIGNO", "");
            //unit.put("CIGNO", "");
            field.put("Ceiling_height_available", "No");
            unit.put("Ceiling_height_available", "");
            remark = m.replaceFirst(" ");
        }

        //	if( s#CIG(E)?(\d3)\s+## )
        //		if( $1 eq "E" )
        //			$Ceiling_est = $2 * 100 ;
        //		 else
        //			$Ceiling = $2 * 100 ;
        m = MP.CIG_EST.matcher(remark);
        if (m.find()) {
            String est = Integer.toString(Integer.parseInt(m.group(2)) * 100);
            //if (m.group(1).equals("E")) {
                //field.put("Ceiling_Estimate", est);
                //unit.put("Ceiling_Estimate", "feet");
            //else {
                field.put("Ceiling", est);
                unit.put("Ceiling", "feet");
            //
            remark = m.replaceFirst(" ");
        }

        // 	Variable Sky conditions
        //	if( s#(FEW|SCT|BKN|OVC)(\d3)? V (FEW|SCT|BKN|OVC)\s+## )
        //		$VrbSkyBelow = $1 ;
        //		$VrbSkyLayerHgt = $2 * 100 if( defined( $2 ) ) ;
        //		$VrbSkyAbove = $3 ;
        m = MP.variableSky.matcher(remark);
        if (m.find()) {
            field.put("Variable_Sky_Below", m.group(1));
            unit.put("Variable_Sky_Below", "feet");
            if (m.group(2) != null) {
                field.put("Variable_Sky_Height", Integer.toString(Integer.parseInt(m.group(2)) * 100));
                unit.put("Variable_Sky_Height", "feet");
            }
            field.put("Variable_Sky_Above", m.group(3));
            unit.put("Variable_Sky_Above", "feet");
            remark = m.replaceFirst(" ");
        }

/* let these end up in extra_remarks

// 	Significant Cloud Types
//	if( s#(CB|CBMAM|TCU|ACC|SCSL|ACSL|ROTOR CLD|ROPE|ROPE CLD)\s+## )
//		$Sign_cloud = padstr( $1, 10 ) ;
//		$Sign_dist = padstr( $1, 10 )
//			if( s#^(VCNTY STN|VCY STN|VC STN|VCY|VC|DSNT|OMT)\s+## ) ;
//		$Sign_dir = padstr( "$1$2$3", 10 )
//			if( s#^(NE|NW|SE|SW|N|S|E|W)(\-| MOV )?(NE|NW|SE|SW|N|S|E|W)?/?\s+## ) ;
        m = MP.significantCloud.matcher(remark);
        if (m.find()) {
            field.put("Significant_Cloud", m.group(1));
            unit.put("Significant_Cloud", "");
            remark = m.replaceFirst(" ");
            m = MP.significantCloud1.matcher(remark);
            if (m.find()) {
                field.put("Significant_Cloud_Vicinity", m.group(1));
                unit.put("Significant_Cloud_Vicinity", "" );
                remark = m.replaceFirst(" ");
                m = MP.significantCloud2.matcher(remark);
                if (m.find()) {
                    field.put("Significant_Cloud_Direction", m.group(1));
                    unit.put("Significant_Cloud_Direction", "");
                    remark = m.replaceFirst(" ");
                }
            }
        }
*/

/* let these end up in extra_remarks
// 	Obscuring Phenomena Aloft
//	if( s#(VC|PR)?(MI|BC|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS|DS)? (FEW|SCT|BKN|OVC)(\d3)\s+## )
//		$ObscurAloft = padstr( "$1$2$3$4$5", 8 ) ;
//		$ObscurAloftSkyCond = $6 ;
//		$ObscurAloftHgt = $7 * 100 ;
//
        m = MP.obscuringPhen.matcher(remark);
        if (m.find()) {
            String tmp = "";
            if (m.group(1) != null) {
                tmp = m.group(1);
            }
            if (m.group(2) != null) {
                tmp = m.group(2);
            }
            if (m.group(3) != null) {
                tmp = m.group(3);
            }
            if (m.group(4) != null) {
                tmp = m.group(4);
            }
            if (m.group(5) != null) {
                tmp = m.group(5);
            }
            if ( ! tmp.equals("")) {
                field.put("Surface_Phenomena", tmp );
                unit.put("Surface_Phenomena", "" );
            }
            field.put("Obscuring_Phenomena_Sky", m.group(6));
            unit.put("Obscuring_Phenomena_Sky", "");
            if (m.group(7) != null) {
                field.put("Obscuring_Phenomena_Sky_Height", Integer.toString(Integer.parseInt(m.group(7)) * 100));
                unit.put("Obscuring_Phenomena_Sky_Height", "feet");
            }
            remark = m.replaceFirst(" ");
        }
*/

        // 	Air craft mishap  ACFTMSHP
        //	$ACFTMSHP = 1 if( s#\(?ACFT\s?MSHP\)?\s+## ) ;
        m = MP.ACFT.matcher(remark);
        if (m.find()) {
            field.put("Air_craft_mishap", "yes");
            unit.put("Air_craft_mishap", "");
            remark = m.replaceFirst(" ");
        }

        // 	No changes in weather conditions until next report  NOSPECI
        //	$NOSPECI = 1 if( s#NOSPECI\s+## ) ;
        m = MP.NOSPECI.matcher(remark);
        if (m.find()) {
            field.put("Changes_in_weather", "No");
            unit.put("Changes_in_weather", "");
            remark = m.replaceFirst(" ");
        }

        // 	This is first report of the day  FIRST
        //	$FIRST = 1 if( s#FIRST\s+## ) ;
        m = MP.FIRST.matcher(remark);
        if (m.find()) {
            field.put("First_Report_Today", "yes");
            unit.put("First_Report_Today", "");
            remark = m.replaceFirst(" ");
        }

        // 	This is last report in observation coverage  LAST
        //	$LAST = 1 if( s#LAST\s+## ) ;
        m = MP.LAST.matcher(remark);
        if (m.find()) {
            field.put("Last_Report_Today", "yes");
            unit.put("Last_Report_Today", "");
            remark = m.replaceFirst(" ");
        }

        // 	Cloud Types
        //	if( s# 8/(\d|/)(\d|/)(\d|/)\s+# # )
        //		$Cloud_low = $1 ;
        //		$Cloud_medium = $2 ;
        //		$Cloud_high = $3 ;
        m = MP.cloud_height.matcher(remark);
        if (m.find()) {
            if( ! m.group(1).equals( "/")) {
                field.put("Cloud_Low", m.group(1));
                unit.put("Cloud_Low", "");
            }
            if( ! m.group(2).equals( "/")) {
                field.put("Cloud_Medium", m.group(2));
                unit.put("Cloud_Medium", "");
            }
            if( ! m.group(3).equals( "/")) {
                field.put("Cloud_High", m.group(3));
                unit.put("Cloud_High", "");
            }
            remark = m.replaceFirst(" ");
        }
        //
        // 	Snow Increasing Rapidly   SNINCR
        //	if( s#SNINCR (\d1,3)/(\d1,3)\s+## )
        //		$SNINCR = $1 ;
        //		$SNINCR_TotalDepth = $2 ;
        m = MP.SNINCR.matcher(remark);
        if (m.find()) {
            field.put("Snow_Increasing_Rapidly", m.group(1));
            unit.put("Snow_Increasing_Rapidly", "inches");
            field.put("Snow_Increasing_Depth", m.group(2));
            unit.put("Snow_Increasing_Depth", "inches");
            remark = m.replaceFirst(" ");
        }
        //
        // 	Snow depth on ground
        //	if( s#4/(\d1,3)\s+# # )
        //		$SN_depth = $1 ;
        m = MP.snowDepth.matcher(remark);
        if (m.find()) {
            field.put("Snow_Depth", m.group(1));
            unit.put("Snow_Depth", "inches");
            remark = m.replaceFirst(" ");
        }
        //
        //	 Water equivalent of snow on ground
        //	$SN_waterequiv = $1 / 10 if( s# 933(\d3)\s+# # ) ;
        m = MP.waterEquiv.matcher(remark);
        if (m.find()) {
            field.put("Water_Equivalent_of_Snow", Double.toString(Float.parseFloat(m.group(1)) * 0.1 ));
            unit.put("Water_Equivalent_of_Snow", "");
            remark = m.replaceFirst(" ");
        }

        // 	Duration of sunshine
        //	if( s# 98(\d1,3|///)\s+# # )
        //		if( $1 eq "///" )
        //			$SunSensorOut = 1 ;
        //		 else
        //			$SunShineDur = $1 ;
        m = MP.sunShine.matcher(remark);
        if (m.find()) {
            if( m.group(1).equals("///") ) {
                field.put("Sun_Sensor_working", "No");
                unit.put("Sun_Sensor_working", "");
            } else {
                field.put("Sun_Sensor_Duration", m.group(1));
                unit.put("Sun_Sensor_Duration", "");
            }
            remark = m.replaceFirst(" ");
        }

        // 	Precipitation amount
        //	if( s# 6(\d4|////)\s+# # )
        //		$PRECIP_amt = $1 / 100 if( $1 ne "////" ) ;
        m = MP.precipitation.matcher(remark);
        if (m.find()) {
            if( ! m.group(1).equals("////") ) {
                field.put("Precipitation_amount", Double.toString(Float.parseFloat(m.group(1)) * 0.01 ));
                unit.put("Precipitation_amount", "inches");
            }
            remark = m.replaceFirst(" ");
        }
        //
        // 	24 Hour Precipitation amount
        //	if( s# 7(\d4|////)\s+# # )
        //		$PRECIP_24_amt = $1 / 100 if( $1 ne "////" ) ;
        m = MP.precipitation24.matcher(remark);
        if (m.find()) {
            if( ! m.group(1).equals("////") ) {
                field.put("Precipitation_amount_24Hours", Double.toString(Float.parseFloat(m.group(1)) * 0.01 ));
                unit.put("Precipitation_amount_24Hours", "inches");
            }
            remark = m.replaceFirst(" ");
        }
        //
        // 	Maximum Temperature
        //	if( s# 1(0|1|/)(\d3|///)\s+# # )
        //		$Tmax = $2 / 10 if( $2 ne "///" ) ;
        //		$Tmax *= -1.0 if( $1 == 1 ) ;
        //
        m = MP.maxTemperature.matcher(remark);
        if (m.find()) {
            if( ! m.group(2).equals("///") ) {
                double maxtemp = Float.parseFloat(m.group(2));
                if( m.group(1).equals("1") ) {
                    maxtemp *= -0.1;
                } else if( m.group(1).equals("0") ) {
                    maxtemp *= 0.1;
                }
                field.put("Max_Temperature", Double.toString( maxtemp ) );
                unit.put("Max_Temperature", "Celsius");
            }
            remark = m.replaceFirst(" ");
        }

        // 	Minimum Temperature
        //	if( s# 2(0|1|/)(\d3|///)\s+# # )
        //		$Tmin = $2 / 10 if( $2 ne "///" ) ;
        //		$Tmin *= -1.0 if( $1 == 1 ) ;
        m = MP.minTemperature.matcher(remark);
        if (m.find()) {
            if( ! m.group(2).equals("///") ) {
                double mintemp = Float.parseFloat(m.group(2));
                if( m.group(1).equals("1") ) {
                    mintemp *= -0.1;
                } else if( m.group(1).equals("0") ) {
                    mintemp *= 0.1;
                }
                field.put("Min_Temperature", Double.toString( mintemp ) );
                unit.put("Min_Temperature", "Celsius");
            }
            remark = m.replaceFirst(" ");
        }
        //
        // 	24-Hour Maximum and Minimum Temperature
        //	if( s# 4(0|1|/)(\d3|///)(0|1|/)(\d3|///)\s+# # )
        //		$Tmax24 = $2 / 10 if( $2 ne "///" ) ;
        //		$Tmax24 *= -1.0 if( $1 == 1 ) ;
        //		$Tmin24 = $4 / 10 if( $4 ne "///" ) ;
        //		$Tmin24 *= -1.0 if( $3 == 1 ) ;
        m = MP.maxMinTemp24.matcher(remark);
        if (m.find()) {
            if( ! m.group(2).equals("///") ) {
                double maxtemp = Float.parseFloat(m.group(2));
                if( m.group(1).equals("1") ) {
                    maxtemp *= -0.1;
                } else if( m.group(1).equals("0") ) {
                    maxtemp *= 0.1;
                }
                field.put("Max_Temperature_24Hour", Double.toString( maxtemp ) );
                unit.put("Max_Temperature_24Hour", "Celsius");
            }
            if( ! m.group(4).equals("///") ) {
                double mintemp = Float.parseFloat(m.group(4));
                if( m.group(3).equals("1") ) {
                    mintemp *= -0.1;
                } else if( m.group(3).equals("0") ) {
                    mintemp *= 0.1;
                }
                field.put("Min_Temperature_24Hour", Double.toString( mintemp ) );
                unit.put("Min_Temperature_24Hour", "Celsius");
            }
            remark = m.replaceFirst(" ");
        }
        //
        // 	Presure Tendency
        //	if( s# 5(0|1|2|3|4|5|6|7|8)(\d3/?|///)\s+# # )
        //		$char_Ptend = $1 ;
        //		$Ptend = $2 / 10 if( $2 ne "///" ) ;
        //
        m = MP.pressureTendency.matcher(remark);
        if (m.find()) {
            field.put("Presure_Tendency_char", m.group(1));
            unit.put("Presure_Tendency_char", "");
            if( ! m.group(2).equals("///") ) {
                field.put("Presure_Tendency", Double.toString(Float.parseFloat(m.group(2)) * 0.1 ));
                unit.put("Presure_Tendency", "hectopascals");
            }
            remark = m.replaceFirst(" ");
        }

        // 	Freezing Rain sensor not working  FZRANO
        //	$FZRANO = 1 if( s#FZRANO\s+## ) ;
        m = MP.FZRANO.matcher(remark);
        if (m.find()) {
            field.put("Freezing_Rain_sensor_working", "No");
            unit.put("Freezing_Rain_sensor_working", "");
            remark = m.replaceFirst(" ");
        }

        // 	Tipping bucket rain gauge is inoperative.
        //	$PNO = 1 if( s#PNO\s+## ) ;
        m = MP.PNO.matcher(remark);
        if (m.find()) {
            field.put("Tipping_bucket_rain_gauge_working", "No");
            unit.put("Tipping_bucket_rain_gauge_working", "");
            remark = m.replaceFirst(" ");
        }

        // 	Maintenance is needed on system Indicator
        //	$maintIndicator = 1 if( s#\$\s+## ) ;
        m = MP.maintenace.matcher(remark);
        if (m.find()) {
            field.put("Maintenance_needed", "yes");
            unit.put("Maintenance_needed", "");
            remark = m.replaceFirst(" ");
        }

        /* let this go to the extra fields field cuz it's too general
// 	Get Recent weather conditions with Beginning and Ending times, moved
//	because the RE are too general and they match wrongly
// 	code table 4678
	for(int i = 0; i < 3; i++ ) {
            String RWX = "Recent_Weather_"+ Integer.toString( i +1 );
//	    if( s#(\+|-|VC|PR)?(MI|BC|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS|DS)?B(\d2,4)E(\d2,4)\s+## )
            m = MP.recentWeather.matcher(remark);
            if (m.find()) {
//		$Recent_WX[ $i ] = padstr( "$1$2$3$4$5", 8 ) ;
                String tmp = "";
                if (m.group(1) != null) {
                    tmp = m.group(1);
                }
                if (m.group(2) != null) {
                    tmp = tmp + m.group(2);
                }
                if (m.group(3) != null) {
                    tmp = tmp + m.group(3);
                }
                if (m.group(4) != null) {
                    tmp = tmp + m.group(4);
                }
                if (m.group(5) != null) {
                    tmp = tmp + m.group(5);
                }
                if ( ! tmp.equals("")) {
                    field.put( RWX, tmp );
                    unit.put( RWX, "" );
                } else {
                    break;
                }
//		if( length( $6 ) == 5 )
//			$Recent_WX_Bhh[ $i ] = substr( $6, 1, 2 ) * 1 ;
//			$Recent_WX_Bmm[ $i ] = substr( $6, 3, 2 ) * 1 ;
//		elsif( length( $6 ) == 3 )
//			$Recent_WX_Bmm[ $i ] = substr( $6, 1, 2 ) * 1 ;
                field.put( RWX +"_Begin_Time", m.group(6) );
                unit.put( RWX +"_Begin_Time", "" );
//		if( length( $7 ) == 5 )
//			$Recent_WX_Ehh[ $i ] = substr( $7, 1, 2 ) * 1 ;
//			$Recent_WX_Emm[ $i ] = substr( $7, 3, 2 ) * 1 ;
//	   	elsif( length( $7 ) == 3 )
//			$Recent_WX_Emm[ $i ] = substr( $7, 1, 2 ) * 1 ;
                field.put( RWX +"_End_Time", m.group(7) );
                unit.put( RWX +"_End_Time", "" );
                remark = m.replaceFirst(" ");
//	    elsif( s#(\+|-|VC|PR)?(MI|BC|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS|DS)?(B|E)(\d2,4)\s+## )
            } else {
                m = MP.recentWeather1.matcher(remark);
                if (m.find()) {
//		$Recent_WX[ $i ] = padstr( "$1$2$3$4$5", 8 ) ;
                    String tmp = "";
                    if (m.group(1) != null) {
                        tmp = m.group(1);
                    }
                    if (m.group(2) != null) {
                        tmp = tmp + m.group(2);
                    }
                    if (m.group(3) != null) {
                        tmp = tmp + m.group(3);
                    }
                    if (m.group(4) != null) {
                        tmp = tmp + m.group(4);
                    }
                    if (m.group(5) != null) {
                        tmp = tmp + m.group(5);
                    }
                    if ( ! tmp.equals("")) {
                        field.put( RWX, tmp );
                        unit.put( RWX, "" );
                    } else {
                        break;
                    }
//		if( $6 eq "B" && ( length( $7 ) == 4 ))
//			$Recent_WX_Bhh[ $i ] = substr( $7, 0, 2 ) * 1 ;
//			$Recent_WX_Bmm[ $i ] = substr( $7, 2, 2 ) * 1 ;
//		 elsif( $6 eq "B" && ( length( $7 ) == 2 ))
//			$Recent_WX_Bmm[ $i ] = substr( $7, 0, 2 ) * 1 ;
                    if( m.group(6).equals("B" ) ) {
                        field.put( RWX +"_Begin_Time", m.group(7) );
                        unit.put( RWX +"_Begin_Time", "" );
//		 elsif( $6 eq "E" && ( length( $7 ) == 4 ))
//			$Recent_WX_Ehh[ $i ] = substr( $7, 0, 2 ) * 1 ;
//			$Recent_WX_Emm[ $i ] = substr( $7, 2, 2 ) * 1 ;
//		 elsif( $6 eq "E" && ( length( $7 ) == 2 ))
//			$Recent_WX_Emm[ $i ] = substr( $7, 0, 2 ) * 1 ;
                     } else {
                        field.put( RWX +"_End_Time", m.group(7) );
                        unit.put( RWX +"_End_Time", "" );
		             }
                     remark = m.replaceFirst(" ");
	        } else {
//		last ;
                    break;
	        }
            }
        } // end for recent weather
*/
        // 	Extra remarks includes Volcanic eruptions
        m = MP.spaces.matcher(remark);
        if (m.find()) {
            remark = m.replaceFirst("");
        }
        if( remark.length() != 0) {
            field.put("Extra_fields", remark );
            unit.put("Extra_fields", "");
        }

        return true;
    } // end parseReport

// convert cloud height to  meters
    private String cloud_hgt2_meters(String height) {

        if (height.equals("999")) {
            return "30000";
        } else {
//		$meters = 30 * $height ;
            return Integer.toString(30 * Integer.parseInt(height));
        }
    } // end cloud_hgt2_meters

    /**
     * Used to return fields in Metar report.
     * @return LinkedHashMap
     **/
     public LinkedHashMap getFields() {
        return field;
     }

    /**
     * Used to return units of the fields in Metar report.
     * @return LinkedHashMap
     **/
     public HashMap getUnits() {
        return unit;
     }

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

        String report = null;
        //report = "KD07 150256Z AUTO 28005KT BR M08/M11 A3005 RMK AO2 SLP223 T10781111 21205 40051//// 50006 PWINO FZRANO";
        //report = "METAR K1V4 251254Z AUTO 01/01 A3002 RMK AO2 SLP172 P0001 T00110006 PWINO FZRANO TSNO ";
        //report = "KDEN 201453Z 35007KT 1/4SM R35L/5000V6000FT -DZ BR -SG -FZDZ OVC003 05/04 A2996 RMK 70155 53777 61111 8/53/";
        //report = "ROBB 201453Z 35007KT 1/4SM RMK AO1 21200 4/12 T00110006 SLPNO SLP067 P0000";
        //report = "ROBB 201453Z NVRB05G15KT 090V180 1/4SM AUTO RMK PWINO TSNO TORNADO B10 DSNT NE";
        //report = "ROBB 201453Z NVRB05G15KT R26/0450V0650D RMK PWINO TSNO TORNADO B10 DSNT NE";
        //report = "ROBB 201453Z SKC VV312 RMK TORNADO B10 DSNT NE";
        //report = "KDEN 201453Z 35007KT 1/4SM R35L/5000V6000FT -DZ BR -SG -FZDZ OVC003 05/04 A2996 RMK CIG 003V006";
        //report = "KDEN 201553Z 01006KT 1/4SM R35L/4500VP6000FT  -DZ BR OVC003 05/04 A2996 RMK AO2 SFC VIS 3/4 CIG 003V006 SLP124 P0000 T00500044";
        //report = "SOCA 202000Z 10003KT 9999 VCSH FEW010CB SCT015TCU 27/26 Q1010 NOSIG TEMPO 4000 CB & TCU AU S ET W";
        //report = "SOCA 202000Z 10003KT RMK LTG DSNT SE VIS 6 5/10 RY11 WSHFT 1853 FROPA SLP575 TEMPO 4000 CB & TCU AU S ET W";
        report = "SOCA 202000Z 10003KT RMK 98015 933002" +
                " CIGNO CIG 125 OVC V BKN -FZRA $";
        // Function References
        MetarParseReport func = new MetarParseReport();

        if( args.length == 1 ) { // read external file of Metar reports
            System.out.println( args[ 0 ] +" "+ args.length );
            // read reports from file to parse
            try (InputStream ios = new FileInputStream( args[ 0 ] )) {
              BufferedReader dataIS = new BufferedReader(new InputStreamReader(ios));
              //System.out.println("<report>");
              while (true) {
                report = dataIS.readLine();
                if (report == null) {
                  break;
                }
                System.out.println("\n" + report);
                if (!func.parseReport(report)) {
                  System.out.println("badly formed report, can't parse");
                  continue; // bad report, can't parse
                }

                LinkedHashMap field = func.getFields();
                HashMap unit = func.getUnits();
                String key;

                System.out.println("<report>");
                for (Iterator it = field.keySet().iterator(); it.hasNext(); ) {
                  key = (String) it.next();
                  //System.out.println(key + "\t\t" + (String) field.get(key));
                  System.out.println("\t<parameter name=\"" + key + "\" value=\"" +
                          (String) field.get(key) + "\" units=\"" + (String) unit.get(key)
                          + "\" />");
                }
                System.out.println("</report>");
              }

              //System.out.println("</report>");
              return;
            }
        }

        // else
        System.out.println( report );
        if( ! func.parseReport(report) ) {
            System.out.println("badly formed report, can't parse");
            System.exit(1);
        }
        LinkedHashMap field = func.getFields();
        HashMap unit = func.getUnits();
        String key;

        System.out.println("<report>");
        for( Iterator it = field.keySet().iterator(); it.hasNext(); ) {
            key = (String) it.next();
            //System.out.println(key + "\t\t" + (String) field.get(key));
            System.out.println("\t<parameter name=\""+ key +"\" value=\""+
           (String) field.get(key) +"\" units=\""+ (String) unit.get(key)
           +"\" />" );
        }
        System.out.println("</report>");

    }

} // end MetarParseReport