/* Copyright 2012, UCAR/Unidata.
   See the LICENSE file for more information.
*/

package dap4.dap4lib;

import dap4.core.util.DapContext;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.dap4lib.serial.D4DSP;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;

/**
 * Provide a DSP interface to raw data
 */

public class FileDSP extends D4DSP
{
    //////////////////////////////////////////////////
    // Constants

    static protected final String[] EXTENSIONS = new String[]{
            ".dap", ".raw"
    };

    //////////////////////////////////////////////////
    // Instance variables

    //Coverity[FB.URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD]
    protected byte[] raw = null; // Complete serialized binary databuffer

    //////////////////////////////////////////////////
    // Constructor(s)

    public FileDSP()
    {
        super();
    }

    //////////////////////////////////////////////////
    // DSP API

    /**
     * A path is file if it has no base protocol or is file:
     *
     * @param location file:/ or possibly an absolute path
     * @param context  Any parameters that may help to decide.
     * @return true if this path appears to be processible by this DSP
     */
    public boolean dspMatch(String location, DapContext context)
    {
        try {
            XURI xuri = new XURI(location);
            if(xuri.isFile()) {
                String path = xuri.getPath();
                for(String ext : EXTENSIONS) {
                    if(path.endsWith(ext))
                        return true;
                }
            }
        } catch (URISyntaxException use) {
            return false;
        }
        return false;
    }

    @Override
    public void close()
    {
    }

    @Override
    public FileDSP
    open(String filepath)
            throws DapException
    {
        try {
            if(filepath.startsWith("file:")) try {
                XURI xuri = new XURI(filepath);
                filepath = xuri.getPath();
            } catch (URISyntaxException use) {
                throw new DapException("Malformed filepath: " + filepath)
                        .setCode(DapCodes.SC_NOT_FOUND);
            }
            try (FileInputStream stream = new FileInputStream(filepath)) {
                this.raw = DapUtil.readbinaryfile(stream);
            }
            try (FileInputStream stream = new FileInputStream(filepath)) { // == rewind
                ChunkInputStream rdr = new ChunkInputStream(stream, RequestMode.DAP);
                String document = rdr.readDMR();
                byte[] serialdata = DapUtil.readbinaryfile(rdr);
                super.build(document, serialdata, rdr.getRemoteByteOrder());
            }
            return this;
        } catch (IOException ioe) {
            throw new DapException(ioe).setCode(DapCodes.SC_INTERNAL_SERVER_ERROR);
        }
    }

    //////////////////////////////////////////////////
    // Extension to access a raw byte stream

    public FileDSP
    open(byte[] rawdata)
            throws DapException
    {
        try {
            this.raw = rawdata;
            ByteArrayInputStream stream = new ByteArrayInputStream(this.raw);
            ChunkInputStream rdr = new ChunkInputStream(stream, RequestMode.DAP);
            String document = rdr.readDMR();
            byte[] serialdata = DapUtil.readbinaryfile(rdr);
            super.build(document, serialdata, rdr.getRemoteByteOrder());
            return this;
        } catch (IOException ioe) {
            throw new DapException(ioe).setCode(DapCodes.SC_INTERNAL_SERVER_ERROR);
        }
    }

}


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

package opendap.dap;

import java.util.Enumeration;
import java.io.PrintWriter;

import opendap.dap.parsers.DDSXMLParser;
import opendap.util.Debug;

/**
 * The Alias type is a special attribute. It is simply a reference
 * (like a "soft link" in a UNIX file system) to another attribute.
 *

 * The rules for the definiton and interpretation an Alias are as follows:
 *

 * In the persistent representation of the DDS (XML, or old DAS & DDS syntax)
 * Alias definitions will contain 2 fields: name, attribute. Example from a DDS:
 *

 * Alias CalDate  .profiler.cast.conductivity.calibration.date
 *

 * Or a DDX:
 *

 * <Alias name="CalDate" attribute=".profiler.cast.conductivity.calibration.date"/>
 *

 *

 * The rules for the interpretation of these fields are as follows:
 *

 *
 Then Alias defintion begins with the word Alias

 *

 *
 The name field is consumed in it's entirety (unparsed) and used
 * as the name of the Alias.
 *

 *
 There a number of rules specific to the representation and interpretation
 * of the  attribute field in the Alias declaration.

 *

 *
 The " (double quote) and the \ (backslash aka escape)
 * characters MUST be escaped (using \ character) in the value of the attribute field.

 *

 *
 Each variable and attribute name in the attribute field must be enclosed
 * in double quotes if their values contain the dot (.) character. For
 * example: .sometimes."names.contain".dots would be three names sometimes,
 * names.contain, and dots.

 *

 *
 Fully qualified attribute names always begin with
 * the dot (.) character.

 *

 *
 All attribute names always MUST be fully qualified (begin with a dot).
 * The leading dot represents a fully
 * qualifed path attribute reference, starting at top level of DDX, or the DAS.

 *

 *

 *
 If the attribute field contains only the dot (.) character, then it is
 * referencing the collection of attributes associated with the highest level
 * of the Dataset (global attributes) This collection always exists, but may be empty.

 *

 *
 The attribute field MUST NOT be empty.

 *
 The attribute field MUST NOT point to another Alias.

 *

 *

 *
 After the parser has completely parsed the DDX (or DAS) it will attempt to
 * reslove each alias to a specific attribute. If an alias cannot be resolved to
 * some attribute, and exception will be thrown by the parser.

 *

 *

 *

 * Warning: DAS and DDS objects built using methods other than
 * DDS.parse(), DDS.parseXML,
 * DDS.getDAS(), or DAS.parse()
 * must call DDS.resolveAliases() or DAS.resolveAliases() prior to
 * allowing client software access to
 * these objects. Since an Alias essentially represents a "pointer" to some (other)
 * Attribute, that
 * Attribute object must be found. Once this has been done (by calling the correct
 * resolveAliases() method)
 * the Aliases will act transparently as references to their target Attributes.
 *
 * @author ndp
 * @version $Revision: 15901 $
 * @see AttributeTable
 * @see DDS
 * @see DDS#parse
 * @see DDS#parseXML
 * @see DDS#getDAS
 * @see DAS
 * @see DAS#resolveAliases
 */
public class Alias extends Attribute
{
    /**
     * If is_alias is true, the name of the Attribute
     * we are aliased to.
     */
    private String aliasedToAttributeNamed;

    /**
     * The Attribute to which this Alias points.
     */
    private Attribute targetAttribute;

    /**
     * The BaseType variable that contains the Attribute
     * to which this Alias points.
     */
    private BaseType targetVariable;

    /**
     * Construct an Alias. This constructor is used by the DDSXMLParser to
     * and the DAS parser build Aliases for the DDX.
     *
     * @param aName         a String containing the name of the alias.
     * @param attributeName the String containing the normalized name
     *                      of the variable and attribute that this Alias references.
     */
    public Alias(String aName, String attributeName) {
        super(aName, ALIAS);
        aliasedToAttributeNamed = attributeName;
        targetAttribute = null;
        targetVariable = null;
    }

    public void setMyAttribute(Attribute a) {
        targetAttribute = a;
    }

    public void setMyVariable(BaseType v) {
        targetVariable = v;
    }

    private Attribute getMyAttribute() {

        return (targetAttribute);

    }

//    private BaseType getMyVariable() {
//        return (targetVariable);
//    }

    /**
     * Returns the attribute type constant.
     *
     * @return the attribute type constant.
     */
    public int getType() {

        return (getMyAttribute().getType());
    }

    /**
     * Returns true if the attribute is a container.
     *
     * @return true if the attribute is a container.
     */
    public boolean isContainer() {
        return (getMyAttribute().getType() == CONTAINER);
    }

    /**
     * Returns true if the attribute is an alias.
     *
     * @return true if the attribute is an alias.
     */
    public boolean isAlias() {
        return true;
    }

    /**
     * Returns the name of the attribute aliased to.
     *
     * @return the name of the attribute aliased to.
     */
    public String getAliasedTo() {
        return "";
    }

    /**
     * Returns the name of the attribute aliased to.
     *
     * @return the name of the attribute aliased to.
     */
    public String getAliasedToAttributeFieldAsClearString() {
        return aliasedToAttributeNamed;
    }

    /**
     * Returns the name of the attribute aliased to.
     *
     * @return the name of the attribute aliased to.
     */
    public String getAliasedToAttributeField() {
        // Assume that aliased attribute name if escaped
        //return EscapeStrings.id2www(aliasedToAttributeNamed);
        return (aliasedToAttributeNamed);
    }

    /**
     * Returns the AttributeTable container.
     *
     * @return the AttributeTable container.
     */
    public AttributeTable getContainer() throws NoSuchAttributeException {
        return (getMyAttribute().getContainer());
    }

    /**
     * Returns the values of this attribute as an Enumeration
     * of String.
     *
     * @return an Enumeration of String.
     */
    public Enumeration getValues() throws NoSuchAttributeException {
        return (getMyAttribute().getValues());
    }

    /**
     * Returns the attribute value at index.
     *
     * @param index the index of the attribute value to return.
     * @return the attribute String at index.
     */
    public String getValueAt(int index) throws NoSuchAttributeException {
        return (getMyAttribute().getValueAt(index));
    }

    /**
     * Append a value to this attribute. Always checks the validity of the
     * attribute's value.
     *
     * @param value the attribute String to add.
     * @throws AttributeBadValueException thrown if the value is not a legal
     *                                    member of type
     */
    public void appendValue(String value)
            throws AttributeBadValueException {

        throw new AttributeBadValueException("It is illegal to add values to an Alias. " +
                "Values can only be added to an Attribute");
    }

    /**
     * Append a value to this attribute.
     *
     * @param value the attribute String to add.
     * @param check if true, check the validity of he attribute's value, if
     *              false don't.
     * @throws AttributeBadValueException thrown if the value is not a legal
     *                                    member of type
     */
    public void appendValue(String value, boolean check)
            throws AttributeBadValueException {

        throw new AttributeBadValueException("It is illegal to add values to an Alias. " +
                "Values can only be added to an Attribute");
    }

    /**
     * Remove the i'th String from this attribute.
     *
     * @param index the index of the value to remove.
     */
    public void deleteValueAt(int index) throws AttributeBadValueException {
        throw new AttributeBadValueException("It is illegal to remove values from an Alias. " +
                "Values can only be removed from an Attribute");
    }

    public void print(PrintWriter os, String pad) {

        if (Debug.isSet("Alias")) {
	    DAPNode.log.debug("  Attribute \"" + getClearName() + "\" is an Alias.");
	}

        os.println(pad + "Alias " + getEncodedName() + " " + getAliasedToAttributeField() + ";");

    }

    public void printXML(PrintWriter pw, String pad, boolean constrained) {

        if (Debug.isSet("Alias")) pw.println("    Printing Alias \"" + getClearName() + "\"");

/*
	if (Debug.isSet("Alias")) pw.println("       constrained:    "+constrained);
	if (Debug.isSet("Alias")) pw.println("       targetVariable: '"+targetVariable.getName()+
                               "' (Projected: "+((ServerMethods) targetVariable).isProject()+")");
*/

        if (constrained &&
                targetVariable != null
                ) {

              if(!targetVariable.isProject())
                return;
        }

        pw.println(pad + "<Alias name=\"" +
                DDSXMLParser.normalizeToXML(getClearName()) + "\" " +
                "Attribute=\"" +
                DDSXMLParser.normalizeToXML(getAliasedToAttributeFieldAsClearString()) + "\"/>");

        if (Debug.isSet("Alias")) pw.println("Leaving Alias.print()");
        pw.flush();

    }

    /**
     * Returns a clone of this Alias.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this Alias.
     */

    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        Alias a = (Alias) super.cloneDAG(map);
        a.aliasedToAttributeNamed = this.aliasedToAttributeNamed;
        a.targetAttribute = (Attribute)cloneDAG(map,this.targetAttribute);
        a.targetVariable = (BaseType)cloneDAG(map,this.targetVariable);
        return a;
    }

}

// $Log: Alias.java,v $
//
// Revision 1.1  2003/08/12 23:51:24  ndp
// Mass check in to begin Java-OPeNDAP development work
//
// Revision 1.7  2011/01/09 dmh
// change to cloneDAG
//
// Revision 1.6  2002/08/27 04:30:11  ndp
//
// Revision 1.5  2003/02/12 16:41:15  ndp
// *** empty log message ***
//
// Revision 1.4  2002/12/03 22:44:15  ndp
// *** empty log message ***
//
// Revision 1.3  2002/10/26 01:19:40  ndp
// *** empty log message ***
//
// Revision 1.2  2003/09/02 17:49:34  ndp
// *** empty log message ***
//
// Revision 1.1  2002/10/08 23:03:23  ndp
// *** empty log message ***
//


/*
 * Copyright (c) 2010 The University of Reading
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University of Reading, nor the names of the
 *    authors or contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package uk.ac.rdg.resc.edal.time;

import org.joda.time.DateTimeField;
import org.joda.time.DateTimeFieldType;
import org.joda.time.DurationField;
import org.joda.time.ReadablePartial;
import org.joda.time.field.PreciseDurationDateTimeField;

/**
 * A {@link DateTimeField} that represents the day in the month within a fixed-length
 * year.  This is a {@link PreciseDurationDateTimeField} because a day is a precise
 * duration, even though a month is not.
 * @author Jon
 */
final class DayOfMonthOfFixedYearDateTimeField extends PreciseDurationDateTimeField {

    private final FixedYearVariableMonthChronology chron;
    private final int[] daysInMonth;
    private int maxValue;

    public DayOfMonthOfFixedYearDateTimeField(FixedYearVariableMonthChronology chron) {
        super(DateTimeFieldType.dayOfMonth(), chron.days());
        this.chron = chron;

        this.daysInMonth = chron.getMonthLengths();
        this.maxValue = daysInMonth[0];
        for (int i = 1; i < daysInMonth.length; i++) {
            this.maxValue = Math.max(this.maxValue, daysInMonth[i]);
        }
    }

    @Override
    public int get(long instant) {
        int dayOfYear = this.chron.dayOfYear().get(instant);
        int monthOfYear = this.chron.monthOfYear().get(instant);
        // Calculate the number of days in the completed months so far
        int numCompletedMonths = monthOfYear - 1;
        int daysInCompletedMonths = 0;
        for (int i = 0; i < numCompletedMonths; i++) {
            daysInCompletedMonths += this.daysInMonth[i];
        }
        return dayOfYear - daysInCompletedMonths;
    }

    @Override
    public int getMinimumValue() {
        return 1;
    }

    @Override
    public int getMaximumValue() {
        return this.maxValue;
    }

    @Override
    public int getMaximumValue(long instant) {
        int monthOfYear = this.chron.monthOfYear().get(instant);
        return this.daysInMonth[monthOfYear - 1];
    }

    // Adapted from the package-private BasicDayOfMonthDateTimeField
    @Override
    public int getMaximumValue(ReadablePartial partial) {
        if (partial.isSupported(DateTimeFieldType.monthOfYear())) {
            int month = partial.get(DateTimeFieldType.monthOfYear());
            return this.daysInMonth[month - 1]; // Months are 1-based
        }
        return this.getMaximumValue();
    }

    // Adapted from the package-private BasicDayOfMonthDateTimeField
    @Override
    public int getMaximumValue(ReadablePartial partial, int[] values) {
        int size = partial.size();
        for (int i = 0; i < size; i++) {
            if (partial.getFieldType(i) == DateTimeFieldType.monthOfYear()) {
                int month = values[i];
                return this.daysInMonth[month - 1];
            }
        }
        return this.getMaximumValue();
    }

    @Override
    public DurationField getRangeDurationField() {
        return this.chron.months();
    }

}


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

import ucar.ma2.*;
import ucar.nc2.util.Optional;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

/**
 * HorizCoordSys with LatLonAxis2D latAxis, lonAxis
 * some code forked from ucar.nc2.dataset.GridCoordinate2D
 */
public class HorizCoordSys2D extends HorizCoordSys {
  static private boolean debug = false;
  static private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HorizCoordSys2D.class);

  // private final LatLonAxis2D latAxis, lonAxis;
  private final int nrows, ncols;
  private Edges edges;

   HorizCoordSys2D(LatLonAxis2D latCoord, LatLonAxis2D lonCoord) {
    super(null, null, latCoord, lonCoord, null);
    int[] shape = latCoord.getShape(); // y, x

    nrows = shape[0];
    ncols = shape[1];
  }

  @Override
  public boolean isLatLon2D() {
    return true;
  }

    @Override
  public Optional<HorizCoordSys> subset(SubsetParams params) {

    LatLonRect llbb = (LatLonRect) params.get(SubsetParams.latlonBB);
    Integer horizStride = (Integer) params.get(SubsetParams.horizStride);
    if (horizStride == null || horizStride < 1) horizStride = 1;

    LatLonAxis2D lataxisSubset = null, lonaxisSubset = null;

    Formatter errMessages = new Formatter();
    if (llbb != null) {
      Optional<List<RangeIterator>> opt = computeBounds(llbb, horizStride);
      if (!opt.isPresent()) {
        errMessages.format("%s;%n", opt.getErrorMessage());
      } else {
        List<RangeIterator> ranges = opt.get();
        lataxisSubset = latAxis2D.subset(ranges.get(1), ranges.get(0));  // x, y
        lonaxisSubset = lonAxis2D.subset(ranges.get(1), ranges.get(0));
      }
    } else if (horizStride > 1) {
      try {
        lataxisSubset = latAxis2D.subset(new Range(0, ncols-1, horizStride), new Range(0, nrows-1, horizStride));
        lonaxisSubset = lonAxis2D.subset(new Range(0, ncols-1, horizStride), new Range(0, nrows-1, horizStride));
      } catch (InvalidRangeException e) {
        errMessages.format("%s;%n", e.getMessage());
      }
    }

    String errs = errMessages.toString();
    if (errs.length() > 0)
      return Optional.empty(errs);

    // makes a copy of the axis
    if (lataxisSubset == null) lataxisSubset = (LatLonAxis2D) latAxis2D.copy();
    if (lonaxisSubset == null) lonaxisSubset = (LatLonAxis2D) lonAxis2D.copy();

    return Optional.of(new HorizCoordSys2D(lataxisSubset, lonaxisSubset));
  }

  @Override
  public LatLonPoint getLatLon(int yindex, int xindex) {
    double lat = latAxis2D.getCoord(yindex, xindex);
    double lon = lonAxis2D.getCoord(yindex, xindex);
    return new LatLonPointImpl(lat, lon);
  }

  @Override
  public List<RangeIterator> getRanges() {
    return latAxis2D.getRanges(); // both are the same
  }

  @Override
  public Optional<CoordReturn> findXYindexFromCoord(double x, double y) {
    synchronized (this) {
      if (edges == null) edges = new Edges();
    }
    CoordReturn result = new CoordReturn();
    int[] index = new int[2];
    boolean ok = edges.findCoordElement(y, x, index);
    if (!ok)
      return Optional.empty("not in grid2D");

    result.x = index[1];
    result.y = index[0];
    result.xcoord = getLonAxis2D().getCoord(result.y, result.x);
    result.ycoord = getLatAxis2D().getCoord(result.y, result.x);
    return Optional.of(result);
  }

  @Override
  public List<CoverageCoordAxis> getCoordAxes() {
    List<CoverageCoordAxis> result = new ArrayList<>();
    result.add(latAxis2D);
    result.add(lonAxis2D);
    return result;
  }

  // return y, x ranges
  private Optional<List<RangeIterator>> computeBounds(LatLonRect llbb, int horizStride) {
    synchronized (this) {
      if (edges == null) edges = new Edges();
    }
    return edges.computeBoundsExhaustive(llbb, horizStride);
  }

  // assume this class is instantiated when these edges are needed
  private class Edges {
    private ArrayDouble.D2 latEdge, lonEdge;
    private MAMath.MinMax latMinMax, lonMinMax;

    Edges() {
      latEdge = (ArrayDouble.D2) latAxis2D.getCoordBoundsAsArray();
      lonEdge = (ArrayDouble.D2) lonAxis2D.getCoordBoundsAsArray();

      // assume missing values have been converted to NaNs
      latMinMax = MAMath.getMinMax(latEdge);
      lonMinMax = MAMath.getMinMax(lonEdge);

      // normalize to [minLon,minLon+360]
      int nlons = (int) lonEdge.getSize();
      for (int i = 0; i < nlons; i++) {
        double nonVal = lonEdge.getDouble(i);
        lonEdge.setDouble(i, LatLonPointImpl.lonNormalFrom(nonVal, lonMinMax.min));
      }

      if (debug)
        System.out.printf("Bounds (%d %d): lat= (%f,%f) lon = (%f,%f) %n",
                nrows, ncols, latMinMax.min, latMinMax.max, lonMinMax.min, lonMinMax.max);
    }

    /**
     * Find the best index for the given lat,lon point.
     *
     * @param wantLat   lat of point
     * @param wantLon   lon of point
     * @param rectIndex return (row,col) index, or best guess here. may not be null
     * @return false if not in the grid.
     */
    public boolean findCoordElement(double wantLat, double wantLon, int[] rectIndex) {
      double wantLonNormal = LatLonPointImpl.lonNormalFrom(wantLon, lonMinMax.min);
      return findCoordElementNoForce(wantLat, wantLonNormal, rectIndex);
    }

    private boolean findCoordElementNoForce(double wantLat, double wantLon, int[] rectIndex) {
      if (wantLat < latMinMax.min) return false;
      if (wantLat > latMinMax.max) return false;
      if (wantLon < lonMinMax.min) return false;
      if (wantLon > lonMinMax.max) return false;

      double gradientLat = (latMinMax.max - latMinMax.min) / nrows;
      double gradientLon = (lonMinMax.max - lonMinMax.min) / ncols;

      double diffLat = wantLat - latMinMax.min;
      double diffLon = wantLon - lonMinMax.min;

      // initial guess
      rectIndex[0] = (int) Math.round(diffLat / gradientLat);  // row
      rectIndex[1] = (int) Math.round(diffLon / gradientLon);  // col

      int count = 0;
      while (true) {
        count++;
        if (debug) System.out.printf("%nIteration %d %n", count);
        if (contains(wantLat, wantLon, rectIndex))
          return true;

        if (!jump2(wantLat, wantLon, rectIndex))
          return false;

        // bouncing around
        if (count > 10) {
          // last ditch attempt
          return incr(wantLat, wantLon, rectIndex);
          //if (!ok)
          //  log.error("findCoordElement didnt converge lat,lon = "+wantLat+" "+ wantLon);
          //return ok;
        }
      }
    }

    /**
     * Is the point (lat,lon) contained in the (row, col) rectangle ?
     *
     * @param wantLat   lat of point
     * @param wantLon   lon of point
     * @param rectIndex rectangle row, col, will be clipped to [0, nrows), [0, ncols)
     * @return true if contained
     */
    private boolean containsOld(double wantLat, double wantLon, int[] rectIndex) {
      rectIndex[0] = Math.max(Math.min(rectIndex[0], nrows - 1), 0);
      rectIndex[1] = Math.max(Math.min(rectIndex[1], ncols - 1), 0);

      int row = rectIndex[0];
      int col = rectIndex[1];

      if (debug) System.out.printf(" (%d,%d) contains (%f,%f) in (lat=%f %f) (lon=%f %f) ?%n",
              rectIndex[0], rectIndex[1], wantLat, wantLon,
              latEdge.get(row, col), latEdge.get(row + 1, col), lonEdge.get(row, col), lonEdge.get(row, col + 1));

      if (wantLat < latEdge.get(row, col)) return false;
      if (wantLat > latEdge.get(row + 1, col)) return false;
      if (wantLon < lonEdge.get(row, col)) return false;
      if (wantLon > lonEdge.get(row, col + 1)) return false;
      return true;
    }

    /**
     * Is the point (lat,lon) contained in the (row, col) rectangle ?
     *
     * @param wantLat   lat of point
     * @param wantLon   lon of point
     * @param rectIndex rectangle row, col, will be clipped to [0, nrows), [0, ncols)
     * @return true if contained
     */

/*
  http://mathforum.org/library/drmath/view/54386.html

      Given any three points on the plane (x0,y0), (x1,y1), and
  (x2,y2), the area of the triangle determined by them is
  given by the following formula:

        1 | x0 y0 1 |
    A = - | x1 y1 1 |,
        2 | x2 y2 1 |

  where the vertical bars represent the determinant.
  the value of the expression above is:

       (.5)(x1*y2 - y1*x2 -x0*y2 + y0*x2 + x0*y1 - y0*x1)

  The amazing thing is that A is positive if the three points are
  taken in a counter-clockwise orientation, and negative otherwise.

  To be inside a rectangle (or any convex body), as you trace
  around in a clockwise direction from p1 to p2 to p3 to p4 and
  back to p1, the "areas" of triangles p1 p2 p, p2 p3 p, p3 p4 p,
  and p4 p1 p must all be positive.  If you don't know the
  orientation of your rectangle, then they must either all be
  positive or all be negative.

  this method works for arbitrary convex regions on the plane.
*/
    private boolean contains(double wantLat, double wantLon, int[] rectIndex) {
      rectIndex[0] = Math.max(Math.min(rectIndex[0], nrows - 1), 0);
      rectIndex[1] = Math.max(Math.min(rectIndex[1], ncols - 1), 0);

      int row = rectIndex[0];
      int col = rectIndex[1];

      double x1 = lonEdge.get(row, col);
      double y1 = latEdge.get(row, col);

      double x2 = lonEdge.get(row, col + 1);
      double y2 = latEdge.get(row, col + 1);

      double x3 = lonEdge.get(row + 1, col + 1);
      double y3 = latEdge.get(row + 1, col + 1);

      double x4 = lonEdge.get(row + 1, col);
      double y4 = latEdge.get(row + 1, col);

      // must all have same determinate sign
      boolean sign = detIsPositive(x1, y1, x2, y2, wantLon, wantLat);
      if (sign != detIsPositive(x2, y2, x3, y3, wantLon, wantLat)) return false;
      if (sign != detIsPositive(x3, y3, x4, y4, wantLon, wantLat)) return false;
      if (sign != detIsPositive(x4, y4, x1, y1, wantLon, wantLat)) return false;

      return true;
    }

    private boolean detIsPositive(double x0, double y0, double x1, double y1, double x2, double y2) {
      double det = (x1 * y2 - y1 * x2 - x0 * y2 + y0 * x2 + x0 * y1 - y0 * x1);
      if (det == 0)
        log.warn("determinate = 0 on lat/lon=" + latAxis2D.getName() + ", " + lonAxis2D.getName()); // LOOK needed?
      return det > 0;
    }

    private boolean jumpOld(double wantLat, double wantLon, int[] rectIndex) {
      int row = Math.max(Math.min(rectIndex[0], nrows - 1), 0);
      int col = Math.max(Math.min(rectIndex[1], ncols - 1), 0);
      double gradientLat = latEdge.get(row + 1, col) - latEdge.get(row, col);
      double gradientLon = lonEdge.get(row, col + 1) - lonEdge.get(row, col);

      double diffLat = wantLat - latEdge.get(row, col);
      double diffLon = wantLon - lonEdge.get(row, col);

      int drow = (int) Math.round(diffLat / gradientLat);
      int dcol = (int) Math.round(diffLon / gradientLon);

      if (debug) System.out.printf("   jump from %d %d (grad=%f %f) (diff=%f %f) (delta=%d %d)",
              row, col, gradientLat, gradientLon,
              diffLat, diffLon, drow, dcol);

      if ((drow == 0) && (dcol == 0)) {
        if (debug) System.out.printf("%n   incr:");
        return incr(wantLat, wantLon, rectIndex);
      } else {
        rectIndex[0] = Math.max(Math.min(row + drow, nrows - 1), 0);
        rectIndex[1] = Math.max(Math.min(col + dcol, ncols - 1), 0);
        if (debug) System.out.printf(" to (%d %d)%n", rectIndex[0], rectIndex[1]);
        if ((row == rectIndex[0]) && (col == rectIndex[1])) return false; // nothing has changed
      }

      return true;
    }

    /**
     * jump to a new row,col
     *
     * @param wantLat   want this lat
     * @param wantLon   want this lon
     * @param rectIndex starting row, col and replaced by new guess
     * @return true if new guess, false if failure
     */
  /*
    choose x, y such that (matrix multiply) :

    (wantx) = (fxx fxy)  (x)
    (wanty)   (fyx fyy)  (y)

     where fxx = d(fx)/dx  ~= delta lon in lon direction
     where fxy = d(fx)/dy  ~= delta lon in lat direction
     where fyx = d(fy)/dx  ~= delta lat in lon direction
     where fyy = d(fy)/dy  ~= delta lat in lat direction

    2 linear equations in 2 unknowns, solve in usual way
   */
    private boolean jump2(double wantLat, double wantLon, int[] rectIndex) {
      int row = Math.max(Math.min(rectIndex[0], nrows - 1), 0);
      int col = Math.max(Math.min(rectIndex[1], ncols - 1), 0);
      double lat = latEdge.get(row, col);
      double lon = lonEdge.get(row, col);
      double diffLat = wantLat - lat;
      double diffLon = wantLon - lon;

      double dlatdy = latEdge.get(row + 1, col) - lat;
      double dlatdx = latEdge.get(row, col + 1) - lat;
      double dlondx = lonEdge.get(row, col + 1) - lon;
      double dlondy = lonEdge.get(row + 1, col) - lon;

      // solve for dlon

      double dx = (diffLon - dlondy * diffLat / dlatdy) / (dlondx - dlatdx * dlondy / dlatdy);
      // double dy =  (diffLat - dlatdx * diffLon / dlondx) / (dlatdy - dlatdx * dlondy / dlondx);
      double dy = (diffLat - dlatdx * dx) / dlatdy;

      if (debug)
        System.out.printf("   jump from %d %d (dlondx=%f dlondy=%f dlatdx=%f dlatdy=%f) (diffLat,Lon=%f %f) (deltalat,Lon=%f %f)",
                row, col, dlondx, dlondy, dlatdx, dlatdy,
                diffLat, diffLon, dy, dx);

      int drow = (int) Math.round(dy);
      int dcol = (int) Math.round(dx);

      if ((drow == 0) && (dcol == 0)) {
        if (debug) System.out.printf("%n   incr:");
        return incr(wantLat, wantLon, rectIndex);
      } else {
        rectIndex[0] = Math.max(Math.min(row + drow, nrows - 1), 0);
        rectIndex[1] = Math.max(Math.min(col + dcol, ncols - 1), 0);
        if (debug) System.out.printf(" to (%d %d)%n", rectIndex[0], rectIndex[1]);
        if ((row == rectIndex[0]) && (col == rectIndex[1])) return false; // nothing has changed
      }

      return true;
    }

    private boolean incr(double wantLat, double wantLon, int[] rectIndex) {
      int row = rectIndex[0];
      int col = rectIndex[1];
      double diffLat = wantLat - latEdge.get(row, col);
      double diffLon = wantLon - lonEdge.get(row, col);

      if (Math.abs(diffLat) > Math.abs(diffLon)) { // try lat first
        rectIndex[0] = row + ((diffLat > 0) ? 1 : -1);
        if (contains(wantLat, wantLon, rectIndex)) return true;
        rectIndex[1] = col + ((diffLon > 0) ? 1 : -1);
        if (contains(wantLat, wantLon, rectIndex)) return true;
      } else {
        rectIndex[1] = col + ((diffLon > 0) ? 1 : -1);
        if (contains(wantLat, wantLon, rectIndex)) return true;
        rectIndex[0] = row + ((diffLat > 0) ? 1 : -1);
        if (contains(wantLat, wantLon, rectIndex)) return true;
      }

      // back to original, do box search
      rectIndex[0] = row;
      rectIndex[1] = col;
      return box9(wantLat, wantLon, rectIndex);
    }

    // we think its got to be in one of the 9 boxes around rectIndex
    private boolean box9(double wantLat, double wantLon, int[] rectIndex) {
      int row = rectIndex[0];
      int minrow = Math.max(row - 1, 0);
      int maxrow = Math.min(row + 1, nrows);

      int col = rectIndex[1];
      int mincol = Math.max(col - 1, 0);
      int maxcol = Math.min(col + 1, ncols);

      if (debug) System.out.printf("%n   box9:");
      for (int i = minrow; i <= maxrow; i++)
        for (int j = mincol; j <= maxcol; j++) {
          rectIndex[0] = i;
          rectIndex[1] = j;
          if (contains(wantLat, wantLon, rectIndex)) return true;
        }

      return false;
    }

    // return y, x ranges
    Optional<List<RangeIterator>> computeBoundsExhaustive(LatLonRect rect, int horizStride) {
      LatLonPointImpl llpt = rect.getLowerLeftPoint();
      LatLonPointImpl urpt = rect.getUpperRightPoint();

      double miny = llpt.getLatitude();
      double maxy = urpt.getLatitude();

      // normalize to [minLon,minLon+360], edge already normalized to this
      double minx = LatLonPointImpl.lonNormalFrom(llpt.getLongitude(), lonMinMax.min);
      double maxx = LatLonPointImpl.lonNormalFrom(urpt.getLongitude(), lonMinMax.min);

      int shape[] = lonAxis2D.getShape();
      int ny = shape[0];
      int nx = shape[1];
      int minCol = Integer.MAX_VALUE, minRow = Integer.MAX_VALUE;
      int maxCol = -1, maxRow = -1;

      boolean allX = (minx > lonMinMax.max && maxx > lonMinMax.max && minx > maxx);
      boolean allY =  (miny <= latMinMax.min && maxy >= latMinMax.max);
      if (allX && allY) {
        // return full set
        return Optional.of(getRanges());
      }

      if (minx > lonMinMax.max && maxx > lonMinMax.max && minx < maxx) { // otherwise ignoring minx > maxx
        return Optional.empty("no intersection");
      } else if (minx > lonMinMax.max && maxx > lonMinMax.max && minx > maxx) {
        minCol = 0;   // all of x
        maxCol = nx;
        minx = lonMinMax.min;
      } else if (minx > lonMinMax.min && maxx > lonMinMax.max) {
        maxCol = nx;
      } else if (minx > lonMinMax.max && maxx < lonMinMax.max) {
        minCol = 0;
        minx = lonMinMax.min;
      }

      // probably not needed
      if (miny <= latMinMax.min) {
        minRow = 0;
      } else if (maxy >= latMinMax.max) {
        maxRow = ny;
      }

      // brute force, examine every point LOOK BAD
      for (int row = 0; row <= ny; row++) {
        for (int col = 0; col <= nx; col++) {
          double lat = latEdge.get(row, col);
          double lon = lonEdge.get(row, col);

          if ((lat >= miny) && (lat <= maxy) && (lon >= minx) && (lon <= maxx)) {
            if (col > maxCol) maxCol = col;
            if (col < minCol) minCol = col;
            if (row > maxRow) maxRow = row;
            if (row < minRow) minRow = row;
          }
        }
      }

      try {
        List<RangeIterator> list = new ArrayList<>();
        list.add(new Range(minRow, maxRow-1, horizStride));
        list.add(new Range(minCol, maxCol-1, horizStride));
        return Optional.of(list);

      } catch (InvalidRangeException e) {
        throw new RuntimeException(e);
      }

    }

    private double getMinOrMaxLon(double lon1, double lon2, boolean wantMin) {
      double midpoint = (lon1 + lon2) / 2;
      lon1 = LatLonPointImpl.lonNormal(lon1, midpoint);
      lon2 = LatLonPointImpl.lonNormal(lon2, midpoint);

      return wantMin ? Math.min(lon1, lon2) : Math.max(lon1, lon2);
    }

    // brute force
    public boolean findCoordElementExhaustive(double wantLat, double wantLon, int[] rectIndex) {
      if (wantLat < latMinMax.min) return false;
      if (wantLat > latMinMax.max) return false;
      if (wantLon < lonMinMax.min) return false;
      if (wantLon > lonMinMax.max) return false;

      for (int row = 0; row < nrows; row++) {
        for (int col = 0; col < ncols; col++) {

          rectIndex[0] = row;
          rectIndex[1] = col;

          if (contains(wantLat, wantLon, rectIndex)) {
            return true;
          }
        }
      }
      //debug = saveDebug;
      return false;
    }
  }

  /*
  public int[] findXYindexFromCoord(double x_coord, double y_coord) {
      int[] result = new int[2];

      int[] result2 = new int[2];
      boolean found = g2d.findCoordElement(y_coord, x_coord, result2);
      if (found) {
        result[0] = result2[1];
        result[1] = result2[0];
      } else {
        result[0] = -1;
        result[1] = -1;
      }
      return result;
  } */

}


package ucar.nc2.ogc.waterml;

import net.opengis.waterml.x20.CollectionType;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft.DsgFeatureCollection;
import ucar.nc2.ft.FeatureDatasetPoint;
import ucar.nc2.ft.StationTimeSeriesFeature;
import ucar.nc2.ft.StationTimeSeriesFeatureCollection;
import ucar.nc2.ogc.MarshallingUtil;
import ucar.nc2.ogc.om.NcOMObservationPropertyType;

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

/**
 * Created by cwardgar on 2014/03/13.
 */
public abstract class NcCollectionType {
    // wml2:Collection
    public static CollectionType initCollection(CollectionType collection, FeatureDatasetPoint fdPoint,
            List<VariableSimpleIF> dataVars) throws IOException {
        // @gml:id
        String id = MarshallingUtil.createIdForType(CollectionType.class);
        collection.setId(id);

        // wml2:metadata
        NcDocumentMetadataPropertyType.initMetadata(collection.addNewMetadata());

        // wml2:observationMember[0..*]
        StationTimeSeriesFeatureCollection stationFeatColl = getStationFeatures(fdPoint);
        try {
            while (stationFeatColl.hasNext()) {
                StationTimeSeriesFeature stationFeat = stationFeatColl.next();

                for (VariableSimpleIF dataVar : dataVars) {
                    if (dataVar.getDataType().isNumeric()) {
                        // wml2:observationMember
                        NcOMObservationPropertyType.initObservationMember(
                                collection.addNewObservationMember(), stationFeat, dataVar);
                    }
                }
            }
        } finally {
            stationFeatColl.finish();
        }

        return collection;
    }

    public static StationTimeSeriesFeatureCollection getStationFeatures(FeatureDatasetPoint fdPoint) {
        String datasetFileName = new File(fdPoint.getNetcdfFile().getLocation()).getName();

        if (!fdPoint.getFeatureType().equals(FeatureType.STATION)) {
            throw new IllegalArgumentException(String.format("In %s, expected feature type to be STATION, not %s.",
                    datasetFileName, fdPoint.getFeatureType()));
        }

        List<DsgFeatureCollection> featCollList = fdPoint.getPointFeatureCollectionList();

        if (featCollList.size() != 1) {
            throw new IllegalArgumentException(String.format("Expected %s to contain 1 FeatureCollection, not %s.",
                    datasetFileName, featCollList.size()));
        } else if (!(featCollList.get(0) instanceof StationTimeSeriesFeatureCollection)) {
            String expectedClassName = StationTimeSeriesFeatureCollection.class.getName();
            String actualClassName = featCollList.get(0).getClass().getName();

            throw new IllegalArgumentException(String.format("Expected %s's FeatureCollection to be a %s, not a %s.",
                    datasetFileName, expectedClassName, actualClassName));
        }

        return (StationTimeSeriesFeatureCollection) featCollList.get(0);
    }

    private NcCollectionType() { }
}


package ucar.nc2.ogc.om;

import net.opengis.om.x20.OMObservationType;
import net.opengis.waterml.x20.MeasurementTimeseriesDocument;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.ft.StationTimeSeriesFeature;
import ucar.nc2.ogc.MarshallingUtil;
import ucar.nc2.ogc.gml.NcFeaturePropertyType;
import ucar.nc2.ogc.gml.NcReferenceType;
import ucar.nc2.ogc.gml.NcTimeInstantPropertyType;
import ucar.nc2.ogc.waterml.NcMeasurementTimeseriesType;

import java.io.IOException;

/**
 * Created by cwardgar on 2014/02/26.
 */
public abstract class NcOMObservationType {
    // wml2:Collection/wml2:observationMember/om:OM_Observation
    public static OMObservationType initOmObservation(OMObservationType omObservation,
            StationTimeSeriesFeature stationFeat, VariableSimpleIF dataVar) throws IOException {
        // @gml:id
        String id = MarshallingUtil.createIdForType(OMObservationType.class);
        omObservation.setId(id);

        // om:phenomenonTime
        NcTimeObjectPropertyType.initPhenomenonTime(omObservation.addNewPhenomenonTime(), stationFeat);

        // om:resultTime
        NcTimeInstantPropertyType.initResultTime(omObservation.addNewResultTime());

        // om:observedProperty
        NcReferenceType.initObservedProperty(omObservation.addNewObservedProperty(), dataVar);

        // om:procedure
        NcOMProcessPropertyType.initProcedure(omObservation.addNewProcedure());

        // om:featureOfInterest
        NcFeaturePropertyType.initFeatureOfInterest(omObservation.addNewFeatureOfInterest(), stationFeat);

        // om:result
        MeasurementTimeseriesDocument measurementTimeseriesDoc = MeasurementTimeseriesDocument.Factory.newInstance();
        NcMeasurementTimeseriesType.initMeasurementTimeseries(
                measurementTimeseriesDoc.addNewMeasurementTimeseries(), stationFeat, dataVar);
        omObservation.setResult(measurementTimeseriesDoc);

        return omObservation;
    }

    private NcOMObservationType() { }
}


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

package ucar.nc2.ui.op;

import thredds.inventory.MCollection;
import thredds.inventory.MFile;
import ucar.nc2.iosp.bufr.Message;
import ucar.nc2.iosp.bufr.MessageScanner;
import ucar.nc2.iosp.bufr.writer.BufrSplitter2;
import ucar.nc2.ui.ReportPanel;
import ucar.unidata.io.RandomAccessFile;
import ucar.util.prefs.PreferencesExt;

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

/**
 * BUFR reports
 *
 * @author caron
 * @since 8/22/13
 */
public class BufrReportPanel extends ReportPanel {

  public static enum Report {
    checkHash, bufrSplitter
  }

  public BufrReportPanel(PreferencesExt prefs) {
    super(prefs);
  }

  @Override
  public Object[] getOptions() {
    return Report.values();
  }

  @Override
  protected void doReport(Formatter f, Object option, MCollection dcm, boolean useIndex, boolean eachFile, boolean extra) throws IOException {
    Report which = (Report) option;
    switch (which) {
      case bufrSplitter:
        doBufrSplitter(f, dcm, useIndex);
        break;
      case checkHash:
         doCheckHash(f, dcm, useIndex);
         break;
     }
  }

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

  private void doCheckHash(Formatter f, MCollection dcm, boolean useIndex) throws IOException {
    f.format("Check Files for hash consistency%n");
    int[] accum = new int[4];
    TrackMessageTypes all = new TrackMessageTypes();

    for (MFile mfile : dcm.getFilesSorted()) {
      String path = mfile.getPath();
      if (path.endsWith(".ncx")) continue;
      f.format("%n %s%n", path);
      try {
        doCheckHash(mfile, f, all, accum);

      } catch (Throwable t) {
        System.out.printf("FAIL on %s%n", mfile.getPath());
        t.printStackTrace();
      }
    }

    f.format("%n================%nTotals countMess=%d countObs = %d%n", accum[0], accum[1]);
    show(all, f);
  }

  private static class Count {
    int nmess = 1;
    int nobs = 0;

    private Count(int nobs) {
      this.nobs = nobs;
    }
  }

  private static class TrackMessageTypes {
    HashMap<Message, Count> map = new HashMap<Message, Count>();

    void add(Message m) {
      Count hashCount = map.get(m);
      if (hashCount == null) {
        map.put(m, new Count(m.getNumberDatasets()));
      } else {
        hashCount.nmess++;
        hashCount.nobs += m.getNumberDatasets();
      }
    }
  }

  private void doCheckHash(MFile ff, Formatter fm, TrackMessageTypes all, int[] accum) throws IOException {
    TrackMessageTypes oneFile = new TrackMessageTypes();

    int countMess = 0;
    int countObs = 0;
    RandomAccessFile raf = null;
    try {
      raf = new RandomAccessFile(ff.getPath(), "r");
      MessageScanner scan = new MessageScanner(raf, 0, true);
      while (scan.hasNext()) {
        Message m = scan.next();
        if (m == null) continue;

        oneFile.add(m);
        all.add(m);

        countMess++;
        countObs += m.getNumberDatasets();
      }
    } finally {
      if (raf != null) raf.close();
    }

    show(oneFile, fm);

    accum[0] += countMess;
    accum[1] += countObs;
  }

  private void show(TrackMessageTypes track, Formatter f) throws IOException {
    List<Message> mess = new ArrayList<Message>(track.map.keySet());
    Collections.sort(mess, new Comparator<Message>() {
      @Override
      public int compare(Message o1, Message o2) {
        return o1.getLookup().getCategoryNo().compareTo(o2.getLookup().getCategoryNo());
      }
    });

    f.format("  nmess / nobs (hash) (ddsHash) category - center %n");
    for (Message m : mess) {
      Count hashCount = track.map.get(m);
      f.format("   %6d/%7d (%8s) (%8s) %s - %s%n", hashCount.nmess, hashCount.nobs,
              Integer.toHexString(m.hashCode()),
              Integer.toHexString(m.dds.getDataDescriptors().hashCode()),
              m.getLookup().getCategoryFullName(), m.getLookup().getCenterNo());
    }
  }

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

  private void doBufrSplitter(Formatter f, MCollection dcm, boolean useIndex) throws IOException {
    long start = System.currentTimeMillis();
    String dirName = dcm.getRoot() +"/split"; // LOOK temp kludge

    f.format("BufrSplitter on files in collection %s, write to %s%n", dcm, dirName);
    BufrSplitter2 splitter = new BufrSplitter2(dirName, f);

    for (MFile mfile : dcm.getFilesSorted()) {
      String path = mfile.getPath();
      if (path.endsWith(".ncx")) continue;
      f.format("%n %s%n", path);
      System.out.printf(" BufrSplitter on %s%n", path);
      long start2 = System.currentTimeMillis();
      try {
        splitter.execute(path);
        long took2 = System.currentTimeMillis() - start2;
        System.out.printf("  %s took %s msecs%n", path, took2);

      } catch (Throwable t) {
        System.out.printf("FAIL on %s%n", mfile.getPath());
        t.printStackTrace();
      }
    }
    splitter.exit();
    long took = (System.currentTimeMillis() - start) /1000;
    System.out.printf("That took %s secs%n", took);
  }
}


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

package ucar.nc2.iosp.misc;

import ucar.nc2.IOServiceProvider;
import ucar.nc2.*;
import ucar.nc2.dataset.conv._Coordinate;
import ucar.nc2.dataset.AxisType;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import ucar.ma2.*;

import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Date;
import java.text.ParseException;

/**
 * UspLightning1
 *
 * @author caron
 */
public class UspLightning1 implements IOServiceProvider {

/*  USPLN data format:

Each 1 minute packet sent has an ASCII header, followed by a record for
each lightning detection during the past 1 minute.

Header
The ASCII header provides information on the creation time of the one
minute packet and ending date and time of the file.

Sample Header:
USPLN-LIGHTNING,2004-10-11T20:45:02,2004-10-11T20:45:02
Description:
Name of Product: USPLN-LIGHTNING
Creation of 1 min Packet (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:45:02
Ending of 1 min Packet (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:45:02

NOTE: All times in UTC

Strike Record Following the header, an individual record is provided for
each lightning strike in a comma delimited format.

Sample Strike Records:
2004-10-11T20:44:02,32.6785331,-105.4344587,-96.1,1
2004-10-11T20:44:05,21.2628231,-86.9596634,53.1,1
2004-10-11T20:44:05,21.2967119,-86.9702106,50.3,1
2004-10-11T20:44:06,19.9044769,-100.7082608,43.1,1
2004-10-11T20:44:11,21.4523434,-82.5202274,-62.8,1
2004-10-11T20:44:11,21.8155306,-82.6708778,80.9,1

Description:

Strike Date/Time (yyyy-mm-ddThh:mm:ss): 2004-10-11T20:44:02

Strike Latitude (deg): 32.6785331
Strike Longitude (deg): -105.4344587
Strike Amplitude (kAmps, see note below): -96.1
Stroke Count (number of strokes per flash): 1

Note: At the present time USPLN data are only provided in stroke format,
so the stroke count will always be 1.

Notes about Decoding Strike Amplitude
The amplitude field is utilized to indicate the amplitude of strokes and
polarity of strokes for all Cloud-to- Ground Strokes.

For other types of detections this field is utilized to provide
information on the type of stroke detected.

The amplitude number for Cloud-to-Ground strokes provides the amplitude
of the stroke and the sign (+/-) provides the polarity of the stroke.

An amplitude of 0 indicates USPLN Cloud Flash Detections rather than
Cloud-to-Ground detections.

Cloud flash detections include cloud-to-cloud, cloud-to-air, and
intra-cloud flashes.

An amplitude of -999 or 999 indicates a valid cloud-to-ground stroke
detection in which an amplitude was not able to be determined. Typically
these are long-range detections.
*/

  private static final String MAGIC = "USPLN-LIGHTNING";

  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    raf.seek(0);
    int n = MAGIC.length();
    byte[] b = new byte[n];
    raf.read(b);
    String got = new String(b);
    return got.equals(MAGIC);
  }

  private ArrayInt.D1 dateArray;
  private ArrayDouble.D1 latArray;
  private ArrayDouble.D1 lonArray;
  private ArrayDouble.D1 ampArray;
  private ArrayInt.D1 nstrokesArray;

  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    int n;

    try {
      n = readAllData(raf);
    } catch (ParseException e) {
      e.printStackTrace();
      throw new IOException("bad data");
    }
    raf.close();

    Dimension recordDim = new Dimension("record", n, true);
    ncfile.addDimension(null, recordDim);

    Variable date = new Variable(ncfile, null, null, "date");
    date.setDimensions("record");
    date.setDataType(DataType.INT);
    String timeUnit = "seconds since 1970-01-01 00:00:00";
    date.addAttribute(new Attribute("long_name", "date of strike"));
    date.addAttribute(new Attribute("units", timeUnit));
    date.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));
    date.setCachedData(dateArray, false);
    ncfile.addVariable(null, date);

    Variable lat = new Variable(ncfile, null, null, "lat");
    lat.setDimensions("record");
    lat.setDataType(DataType.DOUBLE);
    lat.addAttribute(new Attribute("long_name", "latitude"));
    lat.addAttribute(new Attribute("units", "degrees_north"));
    lat.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));
    lat.setCachedData(latArray, false);
    ncfile.addVariable(null, lat);

    Variable lon = new Variable(ncfile, null, null, "lon");
    lon.setDimensions("record");
    lon.setDataType(DataType.DOUBLE);
    lon.addAttribute(new Attribute("long_name", "longitude"));
    lon.addAttribute(new Attribute("units", "degrees_east"));
    lon.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));
    lon.setCachedData(lonArray, false);
    ncfile.addVariable(null, lon);

    Variable amp = new Variable(ncfile, null, null, "strikeAmplitude");
    amp.setDimensions("record");
    amp.setDataType(DataType.DOUBLE);
    amp.addAttribute(new Attribute("long_name", "amplitude of strike"));
    amp.addAttribute(new Attribute("units", "kAmps"));
    amp.addAttribute(new Attribute("missing_value", new Double(999)));
    amp.setCachedData(ampArray, false);
    ncfile.addVariable(null, amp);

    Variable nstrokes = new Variable(ncfile, null, null, "strokeCount");
    nstrokes.setDimensions("record");
    nstrokes.setDataType(DataType.INT);
    nstrokes.addAttribute(new Attribute("long_name", "number of strokes per flash"));
    nstrokes.addAttribute(new Attribute("units", ""));
    nstrokes.setCachedData(nstrokesArray, false);
    ncfile.addVariable(null, nstrokes);

    ncfile.addAttribute(null, new Attribute("title", "USPN Lightning Data"));
    ncfile.addAttribute(null, new Attribute("history", "Read directly by Netcdf Java IOSP"));

    ncfile.addAttribute(null, new Attribute("Conventions", "Unidata Observation Dataset v1.0"));
    ncfile.addAttribute(null, new Attribute("cdm_data_type", "Point"));
    ncfile.addAttribute(null, new Attribute("observationDimension", "record"));

    MAMath.MinMax mm = MAMath.getMinMax(dateArray);
    ncfile.addAttribute(null, new Attribute("time_coverage_start", ((int) mm.min) + " " + timeUnit));
    ncfile.addAttribute(null, new Attribute("time_coverage_end", ((int) mm.max) + " " + timeUnit));

    mm = MAMath.getMinMax(latArray);
    ncfile.addAttribute(null, new Attribute("geospatial_lat_min", new Double(mm.min)));
    ncfile.addAttribute(null, new Attribute("geospatial_lat_max", new Double(mm.max)));

    mm = MAMath.getMinMax(lonArray);
    ncfile.addAttribute(null, new Attribute("geospatial_lon_min", new Double(mm.min)));
    ncfile.addAttribute(null, new Attribute("geospatial_lon_max", new Double(mm.max)));

    ncfile.finish();
  }

  // 2006-10-23T17:59:39,18.415434,-93.480526,-26.8,1
  int readAllData(RandomAccessFile raf) throws IOException, NumberFormatException, ParseException {
    ArrayList records = new ArrayList();

    java.text.SimpleDateFormat isoDateTimeFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    isoDateTimeFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));

    //java.io.RandomAccessFile jraf = raf.getRandomAccessFile();

    raf.seek(0);
    int count = 0;
    while (true) {
      String line = raf.readLine();
      if (line == null) break;
      if (line.startsWith(MAGIC)) continue;

      StringTokenizer stoker = new StringTokenizer(line, ",\r\n");
      while (stoker.hasMoreTokens()) {
        Date d = isoDateTimeFormat.parse(stoker.nextToken());
        double lat = Double.parseDouble(stoker.nextToken());
        double lon = Double.parseDouble(stoker.nextToken());
        double amp = Double.parseDouble(stoker.nextToken());
        String tok = stoker.nextToken();
        int nstrikes = Integer.parseInt(tok);

        Strike s = new Strike(d, lat, lon, amp, nstrikes);
        records.add(s);
        if (count < 10)
          System.out.println(count + " " + isoDateTimeFormat.format(d) + " " + s);
      }

      count++;
    }

    System.out.println("processed " + count + " records");

    int n = records.size();
    int[] shape = new int[]{n};
    dateArray = (ArrayInt.D1) Array.factory(DataType.INT, shape);
    latArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    lonArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    ampArray = (ArrayDouble.D1) Array.factory(DataType.DOUBLE, shape);
    nstrokesArray = (ArrayInt.D1) Array.factory(DataType.INT, shape);

    for (int i = 0; i < records.size(); i++) {
      Strike strike = (Strike) records.get(i);
      dateArray.set(i, strike.d);
      latArray.set(i, strike.lat);
      lonArray.set(i, strike.lon);
      ampArray.set(i, strike.amp);
      nstrokesArray.set(i, strike.n);
    }

    return n;
  }

  private class Strike {
    int d;
    double lat, lon, amp;
    int n;

    Strike(Date d, double lat, double lon, double amp, int n) {
      this.d = (int) (d.getTime() / 1000);
      this.lat = lat;
      this.lon = lon;
      this.amp = amp;
      this.n = n;
    }

    public String toString() {
      return lat + " " + lon + " " + amp + " " + n;
    }

  }

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

  public Array readData(Variable v2, List section) throws IOException, InvalidRangeException {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public Array readNestedData(Variable v2, List section) throws IOException, InvalidRangeException {
    return null;
  }

  public void close() throws IOException {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  public boolean syncExtend() throws IOException {
    return false;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public boolean sync() throws IOException {
    return false;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public void setSpecial(Object special) {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  public String toStringDebug(Object o) {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public String getDetailInfo() {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
  }

  public static void main(String args[]) throws IOException, IllegalAccessException, InstantiationException {
    NetcdfFile.registerIOProvider(UspLightning1.class);
    NetcdfFile ncfile = NetcdfFile.open("R:/testdata/lightning/uspln/uspln_20061023.18");
    System.out.println("ncfile = \n" + ncfile);
  }

}


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

package ucar.nc2.ui.widget;

import thredds.ui.datatype.prefs.DateField;
import thredds.ui.datatype.prefs.DurationField;
import ucar.nc2.ui.event.ActionSourceListener;
import ucar.nc2.ui.event.ActionValueEvent;
import ucar.nc2.ui.event.ActionValueListener;
import ucar.nc2.units.*;
import ucar.util.prefs.ui.Field;
import ucar.util.prefs.ui.FieldValidator;
import ucar.util.prefs.ui.PrefPanel;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Date;

/**
 * Widget to select a point or a range from a date range.
 */

public class RangeDateSelector extends JPanel implements FieldValidator {
  public static final String TIME_START = "start";
  public static final String TIME_END = "end";
  public static final String TIME_DURATION = "duration";
  public static final String TIME_RESOLUTION = "resolution";

  static private final int SLIDER_RESOLUTION = 1000;

  private String title, helpMessage;
  private boolean acceptButton, enableButton, isPointOnly, useLimits;

  private DateType minLimit, maxLimit; // min and max allowed values
  private DateRange dateRange; // data model
  private Scale scale;

  // various widgets to manipulate the model
  private JSlider minSlider, maxSlider;
  private DateField minField, maxField;
  private DurationField durationField, resolutionField;

  // ui helpers
  private PrefPanel pp;
  private JLabel minLabel, maxLabel;
  private JButton helpButton;
  private JToggleButton disableButton;
  private HelpWindow helpWindow;

    // event management
  private ActionSourceListener actionSource;
  private String actionName = "rangeDateSelection";
  private boolean eventOK = true;

  private static boolean debugEvent = false, debugEvent2 = false;

  /**
   * Constructor using info from thredds DQC.
   *
   * @param title widget title displayed to user, may be null
   * @param start starting date as a string
   * @param end ending date as a string
   * @param durationS duration as a String
   * @param minInterval minimum useful interval as a String, may be null.
   * @param acceptButton add an acceptButton
   * @param help optional help text
   * @param pointOnly if user can only select one point, otherwise can select a range of dates.
   * @throws java.text.ParseException
   */
  public RangeDateSelector(String title, String start, String end, String durationS, String minInterval,
                           boolean enableButton, boolean acceptButton, String help, boolean pointOnly) throws Exception {

    this( title,
          new DateRange( (start == null) ? null : new DateType(start, null, null),
                         (end == null) ? null : new DateType(end, null, null),
                         (durationS == null) ? null : new TimeDuration(durationS),
                         (minInterval == null) ? null : new TimeDuration(minInterval)),
          enableButton, acceptButton, help, pointOnly, true);
  }

  /**
   * Constructor.
   * @param title widget title displayed to user, may be null
   * @param range range that the user can select from
   * @param acceptButton add an accept Button
   * @param enableButton add an enable Button
   * @param help optional help text
   * @param pointOnly if user can only select one point, otherwise can select a range of dates.
   */
  public RangeDateSelector(String title, DateRange range,  boolean enableButton, boolean acceptButton, String help,
                           boolean pointOnly, boolean useLimits) {
    this.title = title;
    this.dateRange = range;
    this.acceptButton = acceptButton;
    this.enableButton = enableButton;
    this.helpMessage = help;
    this.isPointOnly = pointOnly;
    this.useLimits = useLimits;

    init();
  }

  private void init() {

    // UI
    // optional top panel
    JPanel topPanel;
    topPanel = new JPanel(new BorderLayout());
    JPanel butts = new JPanel();

    if (title != null)
      topPanel.add(BorderLayout.WEST, new JLabel("  " + title + ":"));

    if (helpMessage != null) {
      helpButton = new JButton("help");
      helpButton.addActionListener(e -> {
          if (helpWindow == null)
            helpWindow = new HelpWindow(null, "Help on " + title, helpMessage);
          helpWindow.show(helpButton);
      });
      butts.add(helpButton);
    }

    if (acceptButton) {
      JButton okButton = new JButton("accept");
      okButton.addActionListener(e -> {
          pp.accept();
          sendEvent();
      });

      butts.add(okButton);
      acceptButton = false; // dont need it in prefpanel
    }

    if (enableButton) {
      disableButton = new JToggleButton("disable", false);
      disableButton.addActionListener(e -> {
          boolean b = !disableButton.getModel().isSelected();
          minField.setEnabled(b);
          maxField.setEnabled(b);
          durationField.setEnabled(b);
          minSlider.setEnabled(b);
          maxSlider.setEnabled(b);
      });

      butts.add(disableButton);
    }

    topPanel.add(BorderLayout.EAST, butts);

    // the sliders
    JPanel sliderPanel = new JPanel();
    sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.Y_AXIS));
    sliderPanel.setBorder(new LineBorder(Color.black, 1, true));

    minSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, 0);
    maxSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, SLIDER_RESOLUTION);

    Border b = BorderFactory.createEmptyBorder(0, 15, 0, 15);
    minSlider.setBorder(b);
    maxSlider.setBorder(b);

    // set this so we can call setDateRange();
    minLabel = new JLabel();
    maxLabel = new JLabel();
    minField = new DateField(TIME_START, isPointOnly ? "value" : "start", dateRange.getStart(), null);
    maxField = new DateField(TIME_END, "end", dateRange.getEnd(), null);
    durationField = new DurationField(TIME_DURATION, "duration", dateRange.getDuration(), null);
    resolutionField = new DurationField(TIME_RESOLUTION, "resolution", dateRange.getResolution(), null);

    minField.addValidator(this);
    maxField.addValidator(this);
    durationField.addValidator(this);
    setDateRange(dateRange);

    JPanel labelPanel = new JPanel(new BorderLayout());
    labelPanel.add(minLabel, BorderLayout.WEST);
    labelPanel.add(maxLabel, BorderLayout.EAST);

    // the fields use a PrefPanel
    pp = new PrefPanel(null, null);
    int row = 0;
    //if (tit != null) {
    //  pp.addComponent(new JLabel(tit), col, 0, null);
    //  col+=2;
    //}
    if (isPointOnly) {
      pp.addField(minField, 0, row, null);
    } else {
      pp.addField(minField, 0, row++, null);
      pp.addField(maxField, 0, row++, null);
      pp.addField(durationField, 0, row++, null);
      pp.addField(resolutionField, 0, row, null);
    }
    pp.finish(acceptButton, BorderLayout.EAST);

    setLayout(new BorderLayout()); // allow width expansion

    // overall layout
    sliderPanel.add(topPanel);
    sliderPanel.add(pp);

    if (useLimits) {
      if (!isPointOnly) sliderPanel.add(maxSlider);
      sliderPanel.add(minSlider);
      sliderPanel.add(labelPanel);
    }

    add(sliderPanel, BorderLayout.NORTH);

    /// event management

    // listen for changes from user manupulation
    maxSlider.addChangeListener(e -> {
        if (debugEvent2) System.out.println("maxSlider event= " + maxSlider.getValue());
        if (!eventOK) return;

        int pos = maxSlider.getValue();
        dateRange.setEnd(scale.slider2world(pos));
        synchUI(false);

        if (dateRange.isPoint())
          minSlider.setValue(pos); // drag min along */
    });

    minSlider.addChangeListener(e -> {
        if (debugEvent2) System.out.println("minSlider event= " + minSlider.getValue());
        if (!eventOK) return;

        int pos = minSlider.getValue();
        dateRange.setStart(scale.slider2world(pos));
        synchUI(false);

        if (dateRange.isPoint() && !isPointOnly)
          maxSlider.setValue(pos); // drag max along
    });

    minField.addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        if (debugEvent) System.out.println("minField event= " + e.getNewValue() + " " + e.getNewValue().getClass().getName());
        if (!eventOK) return;

        DateType val = (DateType) minField.getValue();
        dateRange.setStart(val);
        synchUI(true);
      }
    });

    if (maxField != null) {
      maxField.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
          if (debugEvent) System.out.println("maxField event= " + e.getNewValue());
          if (!eventOK) return;

          DateType val = (DateType) maxField.getValue();
          dateRange.setEnd(val);
          synchUI(true);
        }
      });
    }

    if (durationField != null) {
      durationField.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
          if (debugEvent) System.out.println("durationField event= " + e.getNewValue());
          if (!eventOK) return;

          TimeDuration val = durationField.getTimeDuration();
          dateRange.setDuration( val);
          synchUI(true);
        }
      });
    }

    if (resolutionField != null) {
      resolutionField.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
          if (debugEvent) System.out.println("resolutionField event= " + e.getNewValue());
          if (!eventOK) return;

          TimeDuration val = resolutionField.getTimeDuration();
          dateRange.setResolution( val);
          //synchUI(true);
        }
      });
    }

    // listen for outside changes
    actionSource = new ActionSourceListener(actionName) {
      public void actionPerformed(ActionValueEvent e) {
        if (debugEvent) System.out.println(" actionSource event " + e);
        //?? setSelectedByName( e.getValue().toString());
      }
    };

    // catch resize events on the slider
    /* minSlider.addComponentListener( new ComponentAdapter() {
      public void componentResized( ComponentEvent e) {
        setLabels();
      }
    }); */
  }

  private DateFormatter formatter = new DateFormatter();

  public boolean validate( Field fld, Object editValue, StringBuffer errMessages) {
    if (!useLimits) return true;

    DateType checkVal;

    if (fld == durationField) {
      TimeDuration duration = (TimeDuration) editValue;
      if (dateRange.getEnd().isPresent())
        checkVal = dateRange.getEnd().subtract( duration);
      else
        checkVal = dateRange.getStart().add( duration);
    } else
      checkVal =  (DateType) editValue; // otherwise its one of the dates

    // have to be inside the limits
    Date d = checkVal.getDate();
    if (d.after(maxLimit.getDate()) || d.before(minLimit.getDate())) {
      errMessages.append( "Date ");
      errMessages.append(  formatter.toDateTimeString(d));
      errMessages.append(" must be between ");
      errMessages.append( minLimit.getText());
      errMessages.append( " and ");
      errMessages.append( maxLimit.getText());
      return false;
    }

    return true;
  }

  // set values on the UI
  private void synchUI(boolean slidersOK) {
    eventOK = false;
    if (slidersOK) minSlider.setValue(scale.world2slider(dateRange.getStart()));
    minField.setValue(dateRange.getStart());

    if (maxField != null) {
      if (slidersOK) maxSlider.setValue(scale.world2slider(dateRange.getEnd()));
      maxField.setValue(dateRange.getEnd());
    }

    if (durationField != null)
      durationField.setValue(dateRange.getDuration());

    eventOK = true;
  }

  public void setDateRange(DateRange dateRange) {
    this.dateRange = dateRange;

    this.minLimit = new DateType( dateRange.getStart());
    this.maxLimit = new DateType( dateRange.getEnd());
    this.scale = new Scale( dateRange);

    minLabel.setText(" "+minLimit.getText()+" ");
    maxLabel.setText(" "+maxLimit.getText()+" ");

    if (isPointOnly) {
      minField.setValue( dateRange.getStart());
    } else {
      minField.setValue( dateRange.getStart());
      maxField.setValue( dateRange.getEnd());
      durationField.setValue( dateRange.getDuration());
      resolutionField.setValue( dateRange.getResolution());
    }
  }

  public DateField getMinDateField() { return minField; }
  public DateField getMaxDateField() { return maxField; }
  public DurationField getDurationField() { return durationField; }
  public DurationField getResolutionField() { return resolutionField; }

  public boolean isEnabled() {
    return (null == disableButton) || !disableButton.getModel().isSelected();
  }

  public DateRange getDateRange() {
    if (!pp.accept())
      return null;
    return dateRange;
  }

  public void sendEvent() {
        // gotta do this after the dust settles
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        System.out.println("event range= "+dateRange);
        actionSource.fireActionValueEvent(actionName, this);
      } // run
    }); // invokeLater */
  }

    /** add ActionValueListener listener */
  public void addActionValueListener( ActionValueListener l) { actionSource.addActionValueListener(l); }

    /** remove ActionValueListener listener */
  public void removeActionValueListener( ActionValueListener l) { actionSource.removeActionValueListener(l); }

  private static class Scale {
    private double min; // secs
    private double scale;  // pixels / secs

    Scale( DateRange dateRange) {
      this.min = .001 * dateRange.getStart().getDate().getTime();
      //this.max = .001 * dateRange.getEnd().getDate().getTime();
      //scale = SLIDER_RESOLUTION / (this.max - this.min);

      scale = SLIDER_RESOLUTION / dateRange.getDuration().getValueInSeconds();
      // System.out.println("slider scale= "+scale);
    }

    private int world2slider(DateType val) {
      double msecs = .001 * val.getDate().getTime() - min;
      return (int) (scale * msecs);
    }

    private DateType slider2world(int pval) {
      double val = pval / scale; // secs
      //double floor = Math.floor(val / resolution);
      //double incr = floor * resolution;
      double msecs = 1000 * (min + val);
      return new DateType(false, new java.util.Date( (long) msecs));
    }
  }

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

    JFrame frame = new JFrame("Test Date Range Selector");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
    });

    RangeDateSelector rs1 = new RangeDateSelector("Date Range", "1990-01-01T01:00:00", "1990-01-02T02:00:00",
       null, "15 minute", true, true, "i think im fallin", false);
//    RangeDateSelector rs2 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "1 day", false, true,
//       "i think im fallin\n in love with youuuu ", false);
//    RangeDateSelector rs3 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "10 days", true, true,
//       null, false);
//    RangeDateSelector rs4 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "10 days", false, false,
//       null, true);
//    RangeDateSelector rs5 = new RangeDateSelector("Date", null, "present", "10 days", "1 day", true, false,
//       null, false);

    // simulate what we do in PointObsViewer
    DateRange range = null;
    try {
      range = new DateRange(); // phony
    } catch (Exception e) {
      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }
    RangeDateSelector rs6 = new RangeDateSelector(null, range, false, true, null, false, false);
    DateUnit start = new DateUnit("0 secs since 2005-05-02 23:00:00");
    DateUnit end = new DateUnit("0 secs since 2005-05-02 23:59:59");
    rs6.setDateRange( new DateRange( start.getDate(), end.getDate()));

    Box main = new Box( BoxLayout.Y_AXIS);

    frame.getContentPane().add(main);
    //main.setPreferredSize(new Dimension(400, 200));
    //main.add( new JSlider(), BorderLayout.NORTH);
    main.add( rs1);
    /*main.add( rs2);
    main.add( rs3);
    main.add( rs4);
    main.add( rs5); */
    main.add( rs6);

    frame.pack();
    frame.setLocation(400, 300);
    frame.setVisible(true);
  }

}


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

package ucar.nc2.write;

import ucar.ma2.Section;
import ucar.nc2.Dimension;
import ucar.nc2.FileWriter2;
import ucar.nc2.Variable;

import java.util.List;

/**
 * Default chunking strategy
 *
 * @author caron
 * @since 5/10/14
 */
public class Nc4ChunkingDefault extends Nc4ChunkingStrategy {

  private static final int DEFAULT_CHUNKSIZE_BYTES = (int) Math.pow(2,18); // 256K
  private static final int MIN_VARIABLE_BYTES = (int) Math.pow(2,16); // 65K
  private static final int MIN_CHUNKSIZE_BYTES = (int) Math.pow(2,13); // 8K

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

  private int defaultChunkSize = DEFAULT_CHUNKSIZE_BYTES;
  private int minVariableSize = MIN_VARIABLE_BYTES;
  private int minChunksize = MIN_CHUNKSIZE_BYTES;

  public int getDefaultChunkSize() {
    return defaultChunkSize;
  }

  public void setDefaultChunkSize(int defaultChunkSize) {
    this.defaultChunkSize = defaultChunkSize;
  }

  public int getMinVariableSize() {
    return minVariableSize;
  }

  public void setMinVariableSize(int minVariableSize) {
    this.minVariableSize = minVariableSize;
  }

  public int getMinChunksize() {
    return minChunksize;
  }

  public void setMinChunksize(int minChunksize) {
    this.minChunksize = minChunksize;
  }

  public Nc4ChunkingDefault() {
    super(5, true);
  }

  public Nc4ChunkingDefault(int deflateLevel, boolean shuffle) {
    super(deflateLevel, shuffle);
  }

  @Override
  public boolean isChunked(Variable v) {
    if (v.isUnlimited()) return true;
    // if (getChunkAttribute(v) != null) return true;

    long size = v.getSize() * v.getElementSize();
    return (size > minVariableSize);
  }

  @Override
  public long[] computeChunking(Variable v) {
    /* check attribute
    int[] resultFromAtt = computeChunkingFromAttribute(v);
    if (resultFromAtt != null)
      return convertToLong(resultFromAtt); */

    int maxElements = defaultChunkSize / v.getElementSize();

    // no unlimited dimensions
    if (!v.isUnlimited()) {
      int[] result = fillRightmost(v.getShape(), maxElements);
      return convertToLong(result);
    }

    // unlimited case
    int[] result = computeUnlimitedChunking(v.getDimensions(), v.getElementSize());
    return convertToLong(result);
  }

  private int[] fillRightmost(int shape[], int maxElements) {
    // fill up rightmost dimensions first, until maxElements is reached
    FileWriter2.ChunkingIndex index = new FileWriter2.ChunkingIndex(shape);
    return index.computeChunkShape(maxElements);
  }

  // make it easy to test by using dimension list
  public int[] computeUnlimitedChunking(List<Dimension> dims, int elemSize) {
    int maxElements = defaultChunkSize / elemSize;
    int[] result = fillRightmost(convertUnlimitedShape(dims), maxElements);
    long resultSize = new Section(result).computeSize();
    if (resultSize < minChunksize) {
      maxElements = minChunksize / elemSize;
      result = incrUnlimitedShape(dims, result, maxElements);
    }

    return result;
  }

  private int[] incrUnlimitedShape(List<Dimension> dims, int[] shape, long maxElements) {
    int countUnlimitedDims = 0;
    for (Dimension d : dims) {
      if (d.isUnlimited()) countUnlimitedDims++;
    }
    long shapeSize = new Section(shape).computeSize(); // shape with unlimited dimensions == 1
    int needFactor = (int) (maxElements / shapeSize);

    // distribute needFactor amongst the n unlimited dimensions
    int need;
    if ( countUnlimitedDims <= 1) need = needFactor;
    else if ( countUnlimitedDims == 2) need = (int) Math.sqrt(needFactor);
    else if ( countUnlimitedDims == 3) need = (int) Math.cbrt(needFactor);
    else {
      // nth root?? hmm roundoff !!
      need = (int)  Math.pow(needFactor, 1.0 / countUnlimitedDims);
    }

    int[] result = new int[shape.length];
      int count = 0;
      for (Dimension d : dims) {
        result[count] = (d.isUnlimited()) ? need : shape[count];
        count++;
      }
    return result;
  }

  protected int[] convertUnlimitedShape(List<Dimension> dims) {
    int[] result = new int[dims.size()];
    int count = 0;
    for (Dimension d : dims) {
      result[count++] = (d.isUnlimited()) ? 1 : d.getLength();
    }
    return result;
  }

  protected long[] convertToLong(int[] shape) {
    if (shape.length == 0) shape = new int[1];
    long[] result = new long[shape.length];
    for (int i=0; i<shape.length; i++)
      result[i] = shape[i] > 0 ? shape[i] : 1; // unlimited dim has 0
    return result;
  }

}


/* Copyright */
package thredds.server.catalog.tracker;

import java.io.Closeable;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

/**
 * Put a watch on directories for when catalogs change. not used yet
 *
 * @author caron
 * @since 6/9/2015
 */
public class CatalogWatcher implements Closeable {

  private final WatchService watcher;
  private final DatasetTracker tracker;
  private final boolean enable;
  private final Map<WatchKey, Path> keys;
  private boolean recursive = true;
  private boolean trace = true;

  @SuppressWarnings("unchecked")
  static <T> WatchEvent<T> cast(WatchEvent<?> event) {
    return (WatchEvent<T>) event;
  }

  public CatalogWatcher(DatasetTracker tracker, boolean enable) throws IOException {
    this.tracker = tracker;
    this.enable = enable;
    this.watcher = FileSystems.getDefault().newWatchService();
    this.keys = new HashMap<>();

    // enable trace after initial registration
    this.trace = true;
  }

  /**
   * Register the given directory with the WatchService
   */
  public void register(Path dir) throws IOException {
    if (!enable) return;

    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
    if (trace) {
      Path prev = keys.get(key);
      if (prev == null) {
        System.out.format("CatalogWatcher register: %s%n", dir);
      } else {
        if (!dir.equals(prev)) {
          System.out.format("update: %s -> %s%n", prev, dir);
        }
      }
    }
    keys.put(key, dir);
  }

  /**
   * Register the given directory, and all its sub-directories, with the
   * WatchService.
   */
  public void registerAll(final Path start) throws IOException {
    if (!enable) return;

    // register directory and sub-directories
    Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
              throws IOException {
        register(dir);
        return FileVisitResult.CONTINUE;
      }
    });
  }

  /**
   * Process all events for keys queued to the watcher
   */
  public void processEvents() {
    if (!enable) return;

    for (; ; ) {

      // wait for key to be signalled
      WatchKey key;
      try {
        key = watcher.take();
      } catch (InterruptedException x) {
        return;
      }

      Path dir = keys.get(key);
      if (dir == null) {
        System.err.println("WatchKey not recognized!!");
        continue;
      }

      for (WatchEvent<?> event : key.pollEvents()) {
        WatchEvent.Kind kind = event.kind();

        // TBD - provide example of how OVERFLOW event is handled
        if (kind == OVERFLOW) {
          continue;
        }

        // Context for directory entry event is the file name of entry
        WatchEvent<Path> ev = cast(event);
        Path name = ev.context();
        Path child = dir.resolve(name);

        // print out event
        System.out.format("%s: %s%n", event.kind().name(), child);

        // if directory is created, and watching recursively, then
        // register it and its sub-directories
        if (recursive && (kind == ENTRY_CREATE)) {
          try {
            if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
              registerAll(child);
            }
          } catch (IOException x) {
            // ignore to keep sample readbale
          }
        }
      }

      // reset key and remove from set if directory no longer accessible
      boolean valid = key.reset();
      if (!valid) {
        keys.remove(key);

        // all directories are inaccessible
        if (keys.isEmpty()) {
          break;
        }
      }
    }
  }

  @Override
  public void close() throws IOException {
    watcher.close();
  }
}


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

import ucar.nc2.AttributeContainer;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.time.*;
import ucar.nc2.util.NamedAnything;
import ucar.nc2.util.NamedObject;

import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper class for Time coordinate axes
 *
 * @author caron
 * @since 7/11/2015
 */
@Immutable
public class TimeHelper {

  static public TimeHelper factory(String units, AttributeContainer atts) {
    if (units == null)
      units = atts.findAttValueIgnoreCase(CDM.UDUNITS, null);
    if (units == null)
      units = atts.findAttValueIgnoreCase(CDM.UNITS, null);
    if (units == null)
        throw new IllegalStateException("No units");

    Calendar cal = getCalendarFromAttribute(atts);
    CalendarDateUnit dateUnit;
    try {
      dateUnit = CalendarDateUnit.withCalendar(cal, units); // this will throw exception on failure
      return new TimeHelper( dateUnit);
    } catch (IllegalArgumentException e) {
      return null;
    }
  }

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

  // final Calendar cal;
  final CalendarDateUnit dateUnit;
  // final CalendarDate refDate;
  // final double duration;

  private TimeHelper(CalendarDateUnit dateUnit) {
    // this.cal = cal;
    this.dateUnit = dateUnit;
    //this.refDate = dateUnit.getBaseCalendarDate();
    //this.duration = dateUnit.getTimeUnit().getValueInMillisecs();
  }

  // copy on modify
  public TimeHelper setReferenceDate(CalendarDate refDate) {
    CalendarDateUnit cdUnit = CalendarDateUnit.of(dateUnit.getCalendar(), dateUnit.getCalendarField(), refDate);
    return new TimeHelper(cdUnit);
  }

  public String getUdUnit() {
    return dateUnit.getUdUnit();
  }

  // get offset from runDate, in units of dateUnit
  public double offsetFromRefDate(CalendarDate date) {
    return dateUnit.makeOffsetFromRefDate(date);
  }

  public List<NamedObject> getCoordValueNames(CoverageCoordAxis1D axis) {
    axis.loadValuesIfNeeded();
    List<NamedObject> result = new ArrayList<>();
    for (int i = 0; i < axis.getNcoords(); i++) {
      double value;
      switch (axis.getSpacing()) {
        case regularPoint:
        case irregularPoint:
          value = axis.getCoordMidpoint(i);
          result.add(new NamedAnything(makeDate(value), axis.getAxisType().toString()));
          break;

        case regularInterval:
        case contiguousInterval:
        case discontiguousInterval:
          CoordInterval coord = new CoordInterval(axis.getCoordEdge1(i), axis.getCoordEdge2(i), 3);
          result.add(new NamedAnything(coord, coord + " " + axis.getUnits()));
          break;
      }
    }

    return result;
  }

  public CalendarDate getRefDate() {
    return dateUnit.getBaseCalendarDate();
  }

  public CalendarDate makeDate(double value) {
    return dateUnit.makeCalendarDate(value);
  }

  public CalendarDateRange getDateRange(double startValue, double endValue) {
    CalendarDate start = makeDate( startValue);
    CalendarDate end = makeDate( endValue);
    return CalendarDateRange.of(start, end);
  }

  public double getOffsetInTimeUnits(CalendarDate start, CalendarDate end) {
    return dateUnit.getCalendarPeriod().getOffset(start, end);
  }

  public CalendarDate makeDateInTimeUnits(CalendarDate start, double addTo) {
    return start.add(CalendarPeriod.of((int) addTo, dateUnit.getCalendarField()));
  }

  public static ucar.nc2.time.Calendar getCalendarFromAttribute(AttributeContainer atts) {
    String cal = atts.findAttValueIgnoreCase(CF.CALENDAR, null);
    if (cal == null) return null;
    return ucar.nc2.time.Calendar.get(cal);
  }

  public Calendar getCalendar() {
    return dateUnit.getCalendar();
  }

  public CalendarDateUnit getCalendarDateUnit() {
    return dateUnit;
  }

}