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

package ucar.nc2.grib.grib1.tables;

import javax.annotation.Nullable;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import ucar.nc2.grib.GribResourceReader;
import ucar.nc2.grib.coord.VertCoordType;

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

/**
 * FNMOC local tables
 *
 * @author caron
 * @since 1/14/12
 */
public class FnmocTables extends Grib1Customizer {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(FnmocTables.class);
  private static final String fnmocTableA = "resources/grib1/fnmoc/US058MMTA-ALPdoc.pntabs-prodname-masterModelTableOrdered.GRIB1.TblA.xml";
  private static final String fnmocTable2 = "resources/grib1/fnmoc/US058MMTA-ALPdoc.pntabs-prodname-masterParameterTableOrdered.GRIB1.Tbl2.xml";
  private static final String fnmocTable3 = "resources/grib1/fnmoc/US058MMTA-ALPdoc.pntabs-prodname-masterLevelTypeTableOrdered.GRIB1.Tbl3.xml";

  private static Map<Integer, VertCoordType> levelTypesMap;  // shared by all instances
  private static Map<Integer, String> genProcessMap;  // shared by all instances

  FnmocTables(Grib1ParamTables tables) {
    super(58, tables);
  }

  // genProcess

  @Override
  @Nullable
  public String getGeneratingProcessName(int genProcess) {
    if (genProcessMap == null)
      genProcessMap = readGenProcess(fnmocTableA);
    if (genProcessMap == null) return null;
    return genProcessMap.get(genProcess);
  }

  /*
  <fnmocTable>
    <entry>
      <grib1Id>0004</grib1Id>
      <fnmocId>0004</fnmocId>
      <name>MISC_GRIDS</name>
      <fullName>Miscellaneous Grids</fullName>
      <description>atmospheric model</description>
      <status>current</status>
    </entry>
    <entry>
      <grib1Id>0008</grib1Id>
      <fnmocId>0008</fnmocId>
      <name>STRATO</name>
      <fullName>NOGAPS Stratosphere Functions</fullName>
      <description>atmospheric stratosphere model</description>
      <status>current</status>
    </entry>
   */
  @Nullable
  private Map<Integer, String> readGenProcess(String path) {
    try (InputStream is = GribResourceReader.getInputStream(path)) {
      SAXBuilder builder = new SAXBuilder();
      org.jdom2.Document doc = builder.build(is);
      Element root = doc.getRootElement();

      Map<Integer, String> result = new HashMap<>(200);
      Element fnmocTable = root.getChild("fnmocTable");
      List<Element> params = fnmocTable.getChildren("entry");
      for (Element elem1 : params) {
        int code = Integer.parseInt(elem1.getChildText("grib1Id"));
        String desc = elem1.getChildText("fullName");
        result.put(code, desc);
      }

      return Collections.unmodifiableMap(result);  // all at once - thread safe

    } catch (IOException | JDOMException e) {
      logger.error("Cant read FNMOC Table 1 = " + path, e);
      return null;
    }
  }

  /// levels
  protected VertCoordType getLevelType(int code) {
    if (levelTypesMap == null)
      levelTypesMap = readFnmocTable3(fnmocTable3);
    if (levelTypesMap == null)
      return super.getLevelType(code);

    VertCoordType levelType = levelTypesMap.get(code);
    if (levelType != null) return levelType;
    return super.getLevelType(code);
  }

  /*
      <entry>
      <grib1Id>222</grib1Id>
      <fnmocId>mid_cld</fnmocId>
      <name>mid_cld</name>
      <description>middle cloud layer</description>
      <status>deprecated</status>
    </entry>

  <fnmocTable>
    <entry>
      <grib1Id>001</grib1Id>
      <fnmocId>surface</fnmocId>
      <name>surface</name>
      <description>ground or water surface (the atmosphere's lower boundary, land/sea surface)</description>
      <status>current</status>
    </entry>
   */
  @Nullable
  private HashMap<Integer, VertCoordType> readFnmocTable3(String path) {
    try (InputStream is = GribResourceReader.getInputStream(path)) {
      SAXBuilder builder = new SAXBuilder();
      org.jdom2.Document doc = builder.build(is);
      Element root = doc.getRootElement();

      HashMap<Integer, VertCoordType> result = new HashMap<>(200);
      Element fnmocTable = root.getChild("fnmocTable");
      List<Element> params = fnmocTable.getChildren("entry");
      for (Element elem1 : params) {
        int code = Integer.parseInt(elem1.getChildText("grib1Id"));
        if (code < 129) continue;
        String desc = elem1.getChildText("description");
        String abbrev = elem1.getChildText("name");
        String units = elem1.getChildText("units");
        if (units == null) units = (code == 219) ? "Pa" : "";
        String datum = elem1.getChildText("datum");
        boolean isLayer = elem1.getChild("isLayer") != null;
        boolean isPositiveUp = elem1.getChild("isPositiveUp")  != null;
        VertCoordType lt = new VertCoordType(code, desc, abbrev, units, datum, isPositiveUp, isLayer);
        result.put(code, lt);
      }

      return result;  // all at once - thread safe

    } catch (IOException | JDOMException e) {
      logger.error("Cant read FnmocTable3 = " + path, e);
      return null;
    }
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
// $Id: LocalDatasetSource.java 63 2006-07-12 21:50:51Z edavis $

package thredds.cataloggen.config;

import thredds.catalog.*;

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

/**
 * Concrete implementation of DatasetSource for local disk sources.
 *
 * @author Ethan Davis
 * @version $Id: LocalDatasetSource.java 63 2006-07-12 21:50:51Z edavis $
 */

public class LocalDatasetSource extends DatasetSource
{
  static private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(LocalDatasetSource.class);
  // private static Log log = LogFactory.getLog( LocalDatasetSource.class);

  private File accessPointHeaderFile = null;
  private File accessPointFile = null;

  LocalDatasetSource()
  {
      this.type = DatasetSourceType.getType( "Local");
  }

  protected InvDataset createDataset( String datasetLocation, String prefixUrlPath )
          throws IOException
  {
    if ( datasetLocation == null) throw new NullPointerException( "Dataset location cannot be null.");
    this.checkAccessPoint();
    return( new LocalInvDataset( null, new File( datasetLocation), prefixUrlPath));
  }

  /**
   * Build an unnamed InvCatalog for this DatasetSource and return the
   * top-level InvDataset. The ResultService for this DatasetSource is
   * used to create the InvService for the new InvCatalog. Each InvDataset
   * in the catalog is named with the location of the object they represent
   * on the dataset source.
   *
   * @return the top-level dataset of the newly constructed InvCatalog.
   *
   * @throws IOException if top-level dataset does not exist or is not a collection dataset.
   */
  protected InvCatalog createSkeletonCatalog( String prefixUrlPath ) throws IOException
  {
    this.checkAccessPoint();

    // Create catalog.
    InvCatalogImpl catalog = new InvCatalogImpl( null, null, null );
                                                 //this.getName(), null, null);

    // Create service.
    InvService service = new InvService( this.getResultService().getName(),
                                         this.getResultService().getServiceType().toString(),
                                         this.getResultService().getBase(),
                                         this.getResultService().getSuffix(),
                                         this.getResultService().getDescription());
    for ( Iterator it = this.getResultService().getProperties().iterator(); it.hasNext(); )
    {
      service.addProperty( (InvProperty) it.next() );
    }
    for ( Iterator it = this.getResultService().getServices().iterator(); it.hasNext(); )
    {
      service.addService( (InvService) it.next() );
    }

    // Add service to catalog.
    catalog.addService( service);

    // Create top-level dataset.
    File apFile = new File( this.getAccessPoint() );
    InvDatasetImpl topDs = new LocalInvDataset( null, apFile, prefixUrlPath);

    // Set the serviceName (inherited by all datasets) in top-level dataset.
    ThreddsMetadata tm = new ThreddsMetadata( false);
    tm.setServiceName( service.getName());
    InvMetadata md = new InvMetadata( topDs, null, XMLEntityResolver.CATALOG_NAMESPACE_10, "", true, true, null, tm);
    ThreddsMetadata tm2 = new ThreddsMetadata( false);
    tm2.addMetadata( md);
    topDs.setLocalMetadata(tm2);

    // Add top-level dataset to catalog.
    catalog.addDataset( topDs);

    // Tie up any loose ends in catalog with finish().
    ((InvCatalogImpl) catalog).finish();

    return( catalog);
  }

  private void checkAccessPoint()
          throws IOException
  {
    if ( accessPointHeaderFile == null )
    {
      // Check that the accessPointHeader local file exists.
      File aphFile = new File( this.getResultService().getAccessPointHeader() );
      if ( !aphFile.exists() ) throw new IOException( "The accessPointHeader local file does not exist <" + aphFile.getPath() + ">." );

      // Check that the accessPoint local file exists.
      File apFile = new File( this.getAccessPoint() );
      if ( !apFile.exists() ) throw new IOException( "The accessPoint local file does not exist <" + apFile.getPath() + ">." );
      if ( !apFile.isDirectory() ) throw new IOException( "The accessPoint local file is not a directory <" + apFile.getPath() + ">." );

      // Check that accessPoint file starts with accessPointHeader.
      if ( ! apFile.getPath().startsWith( aphFile.getPath() )
           && ! apFile.getCanonicalPath().startsWith( aphFile.getCanonicalPath() ) )
      {
        String tmpMsg = "The accessPoint <" + apFile.getPath() + " or " + apFile.getCanonicalPath() + "> must start with the accessPointHeader <" + aphFile.getPath() + " or " + aphFile.getCanonicalPath() + ">.";
        logger.debug( "checkAccessPoint(): {}", tmpMsg );
        throw new IOException( tmpMsg );
      }

      // Save the accessPointHeader as a URI so encodings and "/" vs "\" issues are handled.
      accessPointHeaderFile = aphFile;
      accessPointFile = apFile;
    }
  }

  /**
   * Return true if the given dataset is a collection dataset, false otherwise.
   *
   * @param dataset - the InvDataset to test for being a collection dataset.
   * @return true if the given dataset is a collection dataset, false otherwise.
   * @throws NullPointerException if the given InvDataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  protected boolean isCollection( InvDataset dataset)
  {
    return( ((LocalInvDataset) dataset).isDirectory());
  }

  /**
   * Return a list of the InvDatasets contained in the given collection dataset
   * on this DatasetSource.
   *
   * @param dataset - the collection dataset to be expanded.
   * @return A list of the InvDatasets contained in the given collection dataset.
   * @throws IllegalArgumentException when given dataset is not a collection dataset.
   * @throws NullPointerException if given dataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  protected List expandThisLevel( InvDataset dataset, String prefixUrlPath )
  {
    if ( dataset == null) throw new NullPointerException( "Given dataset cannot be null.");
    if ( ! isCollection( dataset)) throw new IllegalArgumentException( "Dataset \"" + dataset.getName() + "\" is not a collection dataset.");

    // Deal with all files in this directory.
    File theDir = new File( ((LocalInvDataset) dataset).getLocalPath() );
    File[] allFiles = theDir.listFiles();
    InvDataset curDs = null;
    ArrayList list = new ArrayList();
    for ( int i = 0; i < allFiles.length; i++)
    {
      try
      {
        curDs = new LocalInvDataset( dataset, allFiles[ i], prefixUrlPath );
      }
      catch ( IOException e )
      {
        // Given file doesn't exist or is not under the accessPointHeader directory, skip.
        continue;
      }

      list.add( curDs);
    }

    return( list);
  }

  /**
   * An InvDatasetImpl subclass to deal with datasets on local disk.
   */
  private class LocalInvDataset extends InvDatasetImpl
  {
    private String relativePath;
    private String catRelativePath;
    private String localPath;
    private boolean directory;

    LocalInvDataset( InvDataset parent, File file, String prefixUrlPath) throws IOException
    {
      super( (InvDatasetImpl) parent, null, null, null, null );
      if (logger.isDebugEnabled())
        logger.debug( "LocalInvDataset(): parent=" + ( parent == null ? "" : parent.getName() ) + "; file=" + file.getPath());

      if ( ! file.exists()) throw new IOException( "File <" + file.getPath() + "> does not exist.");
      this.directory = file.isDirectory();

      // Determine the datasets urlPath by removing accessPointHeader from localPath.
      // (Use URIs to handle encoding and slash vs backslash issues.)
      String uriStringFromFile = file.toURI().toString();
      String uriStringFromCanonicalFile = file.getCanonicalFile().toURI().toString();
      String aphUriStringFromFile = accessPointHeaderFile.toURI().toString();
      String aphUriStringFromCanonicalFile = accessPointHeaderFile.getCanonicalFile().toURI().toString();
      String apUriStringFromFile = accessPointFile.toURI().toString();
      String apUriStringFromCanonicalFile = accessPointFile.getCanonicalFile().toURI().toString();
      relativePath = null;

      if (logger.isDebugEnabled()) {
        logger.debug( "LocalInvDataset():               file URI=" + uriStringFromFile );
        logger.debug( "LocalInvDataset():     canonical file URI=" + uriStringFromCanonicalFile );
        logger.debug( "LocalInvDataset():           aph file URI=" + aphUriStringFromFile );
        logger.debug( "LocalInvDataset(): aph canonical file URI=" + aphUriStringFromCanonicalFile );
      }

      // NOTE: Started using File.getCanonicalFile() to deal with comparisons containing this (".") or parent ("..") directories.
      //       But that caused caused comparison problems for symbolic links (e.g., on motherlode "/usr/local/apache/htdocs/motherlode/dods/model"
      //       does not start with "/usr/local/apache/htdocs/motherlode" because "model" is a link to "/data/ldm/pub/decoded/netcdf/GRIB").
      if ( ! uriStringFromFile.startsWith( aphUriStringFromFile ) )
      {
        if ( ! uriStringFromCanonicalFile.startsWith( aphUriStringFromCanonicalFile ) )
        {
          String tmpMsg = "File <" + uriStringFromFile + " or " + uriStringFromCanonicalFile + "> must start with accessPointHeader <" + aphUriStringFromFile + " or " + aphUriStringFromCanonicalFile + ">.";
          throw new IOException( tmpMsg );
        }
        else
        {
          relativePath = uriStringFromCanonicalFile.substring( aphUriStringFromCanonicalFile.length() );
          catRelativePath = uriStringFromCanonicalFile.substring( apUriStringFromCanonicalFile.length());
          localPath = file.getCanonicalPath();
        }
      }
      else
      {
        relativePath = uriStringFromFile.substring( aphUriStringFromFile.length() );
        catRelativePath = uriStringFromFile.substring( apUriStringFromFile.length() );
        localPath = file.getAbsolutePath();
      }

      if (logger.isDebugEnabled()) {
        logger.debug( "LocalInvDataset():           relativePath=" + relativePath );
        logger.debug( "LocalInvDataset():              localPath=" + localPath );
      }

      if ( ! this.directory )
      {
        // Check if service base is relative to the catalog URL the accessPointHeader.
        if ( LocalDatasetSource.this.getResultService().getBase().equals( "" )
             && ! LocalDatasetSource.this.getResultService().getServiceType().equals( ServiceType.COMPOUND) )
        {
//          String tmpUrlPath = prefixUrlPath == null || prefixUrlPath.equals( "")
//                              ? catRelativePath
//                              : prefixUrlPath + "/" + catRelativePath;
          this.setUrlPath( catRelativePath  );
        }
        else
        {
          String tmpUrlPath = prefixUrlPath == null || prefixUrlPath.equals( "" )
                              ? relativePath
                              : prefixUrlPath + "/" + relativePath;
          this.setUrlPath( tmpUrlPath );
        }

        // @todo Should make this optional, maybe with DatasetEnhancer1 (but by then only have dataset, not file).
        if ( LocalDatasetSource.this.isAddDatasetSize() )
        {
          this.setDataSize( file.length());
        }
      }
      String dsName = relativePath.endsWith( "/")
                      ? relativePath.substring( 0, relativePath.length() - 1)
                      : relativePath;
      int index = dsName.lastIndexOf('/');
      if ( index != -1 ) dsName = dsName.substring( index + 1);
      this.setName( dsName);
    }

    String getLocalPath() { return( this.localPath); }
    void setLocalPath( String localPath ) { this.localPath = localPath; }

    public boolean isDirectory() { return( this.directory); }
  }
}


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

package ucar.nc2.iosp.mcidas;

import edu.wisc.ssec.mcidas.McIDASUtil;
import ucar.nc2.grib.grib1.tables.Grib1Customizer;
import ucar.nc2.iosp.grid.*;

/**
 * McIDASLookup
 * get all the information about a McIDAS file.
 * @author dmurray
 */
public final class McIDASLookup implements GridTableLookup {

  /**
   * sample grid
   */
  private McIDASGridRecord sample;
  private Grib1Customizer cust;

  /**
   * Gets a representative grid for this lookup
   */
  public McIDASLookup(McIDASGridRecord sample) {
    this.sample = sample;
    if (sample.hasGribInfo())
      cust = Grib1Customizer.factory(0, 0, 0, null); // WMO standard? Maybe they use NCEP. haha
  }

  public String getShapeName(GridDefRecord gds) {
    return "Spherical";
  }

  /**
   * gets the grid type.
   */
  public final String getGridName(GridDefRecord gds) {
    return gds.toString();
  }

  /**
   * gets parameter table, then grib1 parameter based on number.
   *
   * @param gr GridRecord
   * @return Parameter
   */
  public final GridParameter getParameter(GridRecord gr) {
    McIDASGridRecord mgr = (McIDASGridRecord) gr;
    String name = mgr.getParameterName();
    String desc = mgr.getGridDescription();
    if (desc.trim().equals("")) {
      desc = name;
    }
    String unit = visad.jmet.MetUnits.makeSymbol(mgr.getParamUnitName());
    return new GridParameter(0, name, desc, unit);
  }

  /**
   * gets the DisciplineName.
   */
  public final String getDisciplineName(GridRecord gr) {
    // all disciplines are the same in McIDAS
    return "Meteorological Products";
  }

  /**
   * gets the CategoryName.
   */
  public final String getCategoryName(GridRecord gr) {
    // no categories in McIDAS
    return "Meteorological Parameters";
  }

  /**
   * gets the LevelName.
   */
  public final String getLevelName(GridRecord gr) {

    if (cust != null) {
      String result = cust.getLevelNameShort( gr.getLevelType1());
      if (result != null) return result;
    }

    String levelUnit = getLevelUnit(gr);
    if (levelUnit != null) {
      int level1 = (int) gr.getLevel1();
      int level2 = (int) gr.getLevel2();
      if (levelUnit.equalsIgnoreCase("hPa")) {
        return "pressure";
      } else if (level1 == 1013) {
        return "mean sea level";
      } else if (level1 == 0) {
        return "tropopause";
      } else if (level1 == 1001) {
        return "surface";
      } else if (level2 != 0) {
        return "layer";
      }
    }

    return "";
  }

  /**
   * gets the LevelDescription.
   */
  public final String getLevelDescription(GridRecord gr) {
    if (cust != null) {
      String result = cust.getLevelDescription( gr.getLevelType1());
      if (result != null) return result;
    }

    // TODO:  flesh this out
    return getLevelName(gr);
  }

  /**
   * gets the LevelUnit.
   */
  public final String getLevelUnit(GridRecord gr) {
    if (cust != null) {
      String result = cust.getLevelUnits( gr.getLevelType1());
      if (result != null) return result;
    }

    return visad.jmet.MetUnits.makeSymbol(((McIDASGridRecord) gr).getLevelUnitName());
  }

  public final String getTimeRangeUnitName(int tunit) {
    return "hour";
  }

  /**
   * gets the BaseTime Forecastime.
   *
   * @return BaseTime
   */
  public final java.util.Date getFirstBaseTime() {
    return sample.getReferenceTime();
  }

  /**
   * is this a LatLon grid.
   */
  public final boolean isLatLon(GridDefRecord gds) {
    return getProjectionName(gds).equals("EQUI");
  }

  /**
   * gets the ProjectionType.
   */
  public final int getProjectionType(GridDefRecord gds) {
    String name = getProjectionName(gds).trim();
    switch (name) {
      case "MERC":
        return Mercator;
      case "CONF":
        return LambertConformal;
      case "PS":
        return PolarStereographic;
      default:
        return -1;
    }
  }

  /**
   * is this a VerticalCoordinate.
   */
  public final boolean isVerticalCoordinate(GridRecord gr) {
    if (cust != null) {
      return cust.isVerticalCoordinate(gr.getLevelType1());
    }

    int type = gr.getLevelType1();
    if (((McIDASGridRecord) gr).hasGribInfo()) {

      if (type == 20) {
        return true;
      }
      if (type == 100) {
        return true;
      }
      if (type == 101) {
        return true;
      }
      if ((type >= 103) && (type <= 128)) {
        return true;
      }
      if (type == 141) {
        return true;
      }
      if (type == 160) {
        return true;
      }
    } else if (getLevelUnit(gr).equals("hPa")) {
      return true;
    }
    return false;
  }

  /**
   * is this a PositiveUp VerticalCoordinate.
   */
  public final boolean isPositiveUp(GridRecord gr) {
    if (cust != null) {
      return cust.isPositiveUp( gr.getLevelType1());
    }

    int type = gr.getLevelType1();
    if (((McIDASGridRecord) gr).hasGribInfo()) {

      if (type == 103) {
        return true;
      }
      if (type == 104) {
        return true;
      }
      if (type == 105) {
        return true;
      }
      if (type == 106) {
        return true;
      }
      if (type == 111) {
        return true;
      }
      if (type == 112) {
        return true;
      }
      if (type == 125) {
        return true;
      } else if (getLevelUnit(gr).equals("hPa")) {
        return false;
      }
    }
    return true;
  }

  /**
   * gets the MissingValue.
   *
   * @return MissingValue
   */
  public final float getFirstMissingValue() {
    return McIDASUtil.MCMISSING;
  }

  /**
   * Is this a layer?
   *
   * @param gr record to check
   * @return true if a layer
   */
  public boolean isLayer(GridRecord gr) {
    if (cust != null) {
      return cust.isLayer( gr.getLevelType1());
    }

    if (gr.getLevel2() == 0) {
      return false;
    }
    return true;
  }

  /**
   * Get the projection name
   *
   * @param gds the projection name
   * @return the name or null if not set
   */
  private String getProjectionName(GridDefRecord gds) {
    return gds.getParam(GridDefRecord.PROJ);
  }

  // CF Conventions Global Attributes

  /**
   * gets the CF title.
   *
   * @return title
   */
  public final String getTitle() {
    return "GRID data";
  }

  /**
   * Institution for CF conventions
   *
   * @return Institution
   */
  public String getInstitution() {
    return null;

  }

  /**
   * gets the Source, Generating Process or Model.
   *
   * @return source
   */
  public final String getSource() {
    return null;

  }

  /**
   * comment for CF conventions.
   *
   * @return comment
   */
  public final String getComment() {
    return null;
  }

  /**
   * Get the grid type for labelling
   *
   * @return the grid type
   */
  public String getGridType() {
    return "McIDAS";
  }

}


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

import com.google.protobuf.ByteString;
import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft2.coverage.*;
import ucar.nc2.iosp.IospHelper;
import ucar.nc2.stream.NcStream;
import ucar.nc2.stream.NcStreamDataCol;
import ucar.nc2.stream.NcStreamProto;
import ucar.nc2.time.Calendar;
import ucar.nc2.time.CalendarDateRange;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.ProjectionRect;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.util.List;
import java.util.zip.DeflaterOutputStream;

/**
 * Server side for Cdmrf
 *
 * @author caron
 * @since 5/1/2015
 */
public class CdmrfWriter {
  // static private final Logger logger = LoggerFactory.getLogger(CdmrfWriter.class);

  //  must start with this "CDFF"
  static public final byte[] MAGIC_START = new byte[]{0x43, 0x44, 0x46, 0x46};
  static public final int MAX_INLINE_NVALUES = 500;
  private static final boolean show = false;

  public long sendHeader(OutputStream out, CoverageCollection gridDataset, String location) throws IOException {
    long size = 0;

    CdmrFeatureProto.CoverageDataset.Builder headerBuilder = encodeHeader(gridDataset, location);
    CdmrFeatureProto.CoverageDataset header = headerBuilder.build();

    // header message
    size += NcStream.writeBytes(out, NcStream.MAGIC_HEADERCOV);
    byte[] b = header.toByteArray();
    size += NcStream.writeVInt(out, b.length); // len
    if (show) System.out.println("Write Header len=" + b.length);

    // payload
    size += NcStream.writeBytes(out, b);
    if (show) System.out.println(" header size=" + size);

    return size;
  }

  /* message CoverageDataset {
      required string name = 1;
      repeated Attribute atts = 2;
      required Rectangle latlonRect = 3;
      optional Rectangle projRect = 4;
      required CalendarDateRange timeRange = 5;

      repeated CoordSys coordSys = 6;
      repeated CoordTransform coordTransforms = 7;
      repeated CoordAxis coordAxes = 8;
      repeated Coverage grids = 9;
    } */
  CdmrFeatureProto.CoverageDataset.Builder encodeHeader(CoverageCollection gridDataset, String location) {
    CdmrFeatureProto.CoverageDataset.Builder builder = CdmrFeatureProto.CoverageDataset.newBuilder();
    builder.setName(location);
    builder.setCoverageType(convertCoverageType(gridDataset.getCoverageType()));
    builder.setDateRange(encodeDateRange(gridDataset.getCalendarDateRange()));
    if (gridDataset.getLatlonBoundingBox() != null)
      builder.setLatlonRect(encodeRectangle(gridDataset.getLatlonBoundingBox()));
    if (gridDataset.getProjBoundingBox() != null)
      builder.setProjRect(encodeRectangle(gridDataset.getProjBoundingBox()));

    for (Attribute att : gridDataset.getGlobalAttributes())
      builder.addAtts(NcStream.encodeAtt(att));

    for (CoverageCoordSys gcs : gridDataset.getCoordSys())
      builder.addCoordSys(encodeCoordSys(gcs));

    for (CoverageTransform gct : gridDataset.getCoordTransforms())
      builder.addCoordTransforms(encodeCoordTransform(gct));

    for (CoverageCoordAxis axis : gridDataset.getCoordAxes())
      builder.addCoordAxes(encodeCoordAxis(axis));

    for (Coverage grid : gridDataset.getCoverages())
      builder.addGrids(encodeGrid(grid));

    return builder;
  }

  /* message Rectangle {
      required double startx = 1;
      required double starty = 2;
      required double incx = 3;
      required double incy = 4;
    } */
  CdmrFeatureProto.Rectangle.Builder encodeRectangle(LatLonRect rect) {
    CdmrFeatureProto.Rectangle.Builder builder = CdmrFeatureProto.Rectangle.newBuilder();
    //     this(r.getLowerLeftPoint(), r.getUpperRightPoint().getLatitude() - r.getLowerLeftPoint().getLatitude(), r.getWidth());
    LatLonPoint ll = rect.getLowerLeftPoint();
    LatLonPoint ur = rect.getUpperRightPoint();
    builder.setStartx(ll.getLongitude());
    builder.setStarty(ll.getLatitude());
    builder.setIncx(rect.getWidth());
    builder.setIncy(ur.getLatitude() - ll.getLatitude());
    return builder;
  }

  CdmrFeatureProto.Rectangle.Builder encodeRectangle(ProjectionRect rect) {
    CdmrFeatureProto.Rectangle.Builder builder = CdmrFeatureProto.Rectangle.newBuilder();
    //      this(r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY());
    builder.setStartx(rect.getMinX());
    builder.setStarty(rect.getMaxX());
    builder.setIncx(rect.getWidth());
    builder.setIncy(rect.getHeight());
    return builder;
  }

  /* message CalendarDateRange {
        required int64 start = 1;
        required int64 end = 2;
        required int32 calendar = 3; // calendar ordinal
      } */
  CdmrFeatureProto.CalendarDateRange.Builder encodeDateRange(CalendarDateRange dateRange) {
    CdmrFeatureProto.CalendarDateRange.Builder builder = CdmrFeatureProto.CalendarDateRange.newBuilder();

    builder.setStart(dateRange.getStart().getMillis());
    builder.setEnd(dateRange.getEnd().getMillis());
    Calendar cal = dateRange.getStart().getCalendar();
    builder.setCalendar(convertCalendar(cal));
    return builder;
  }

  /*
  message Coverage {
    required string name = 1; // short name
    required DataType dataType = 2;
    optional bool unsigned = 3 [default = false];
    repeated Attribute atts = 4;
    required string coordSys = 5;
  }
   */
  CdmrFeatureProto.Coverage.Builder encodeGrid(Coverage grid) {
    CdmrFeatureProto.Coverage.Builder builder = CdmrFeatureProto.Coverage.newBuilder();
    builder.setName(grid.getName());
    builder.setDataType(NcStream.convertDataType(grid.getDataType()));

    for (Attribute att : grid.getAttributes())
      builder.addAtts(NcStream.encodeAtt(att));

    builder.setUnits(grid.getUnitsString());
    builder.setDescription(grid.getDescription());
    builder.setCoordSys(grid.getCoordSysName());

    return builder;
  }

  /* message CoordSys {
    required string name = 1;               // must be unique in dataset's CoordSys
    repeated string axisNames = 2;
    repeated string transformNames = 3;
    optional CoverageType coverageType = 5;
  }
    } */
  CdmrFeatureProto.CoordSys.Builder encodeCoordSys(CoverageCoordSys gcs) {
    CdmrFeatureProto.CoordSys.Builder builder = CdmrFeatureProto.CoordSys.newBuilder();
    builder.setName(gcs.getName());
    builder.setCoverageType(convertCoverageType(gcs.getCoverageType()));

    for (String axis : gcs.getAxisNames())
      builder.addAxisNames(axis);

    for (String gct : gcs.getTransformNames())
      builder.addTransformNames(gct);
    return builder;
  }

  /* message CoordTransform {
      required bool isHoriz = 1;
      required string name = 2;
      repeated Attribute params = 3;
    } */
  CdmrFeatureProto.CoordTransform.Builder encodeCoordTransform(CoverageTransform gct) {
    CdmrFeatureProto.CoordTransform.Builder builder = CdmrFeatureProto.CoordTransform.newBuilder();
    builder.setIsHoriz(gct.isHoriz());
    builder.setName(gct.getName());
    for (Attribute att : gct.getAttributes())
      builder.addParams(NcStream.encodeAtt(att));
    return builder;
  }

  /* message CoordAxis {
      required string name = 1;
      required DataType dataType = 2;
      required int32 axisType = 3;    // ucar.nc2.constants.AxisType ordinal
      required int64 nvalues = 4;
      required string units = 5;
      optional bool isRegular = 6;
      required double min = 7;        // required ??
      required double max = 8;
      optional double resolution = 9;
    } */
  CdmrFeatureProto.CoordAxis.Builder encodeCoordAxis(CoverageCoordAxis axis) {
    CdmrFeatureProto.CoordAxis.Builder builder = CdmrFeatureProto.CoordAxis.newBuilder();
    builder.setName(axis.getName());
    builder.setDataType(NcStream.convertDataType(axis.getDataType()));
    builder.setAxisType(convertAxisType(axis.getAxisType()));
    builder.setNvalues(axis.getNcoords());
    if (axis.getUnits() != null) builder.setUnits(axis.getUnits());
    if (axis.getDescription() != null) builder.setDescription(axis.getDescription());
    builder.setDepend(convertDependenceType(axis.getDependenceType()));
    for (String s : axis.getDependsOnList())
      builder.addDependsOn(s);

    if (axis instanceof LatLonAxis2D) {
      LatLonAxis2D latlon2D = (LatLonAxis2D) axis;
      for (int shape : latlon2D.getShape())
       builder.addShape(shape);
    }

    for (Attribute att : axis.getAttributes())
      builder.addAtts(NcStream.encodeAtt(att));

    builder.setSpacing(convertSpacing(axis.getSpacing()));
    builder.setStartValue(axis.getStartValue());
    builder.setEndValue(axis.getEndValue());
    builder.setResolution(axis.getResolution());

    if (!axis.isRegular() && axis.getNcoords() < MAX_INLINE_NVALUES) {
      double[] values = axis.getValues();
      ByteBuffer bb = ByteBuffer.allocate(8 * values.length);
      DoubleBuffer db = bb.asDoubleBuffer();
      db.put(values);
      builder.setValues(ByteString.copyFrom(bb.array()));
    }

    return builder;
  }

  static public CdmrFeatureProto.AxisType convertAxisType(AxisType dtype) {
    switch (dtype) {
      case RunTime:
        return CdmrFeatureProto.AxisType.RunTime;
      case Ensemble:
        return CdmrFeatureProto.AxisType.Ensemble;
      case Time:
        return CdmrFeatureProto.AxisType.Time;
      case GeoX:
        return CdmrFeatureProto.AxisType.GeoX;
      case GeoY:
        return CdmrFeatureProto.AxisType.GeoY;
      case GeoZ:
        return CdmrFeatureProto.AxisType.GeoZ;
      case Lat:
        return CdmrFeatureProto.AxisType.Lat;
      case Lon:
        return CdmrFeatureProto.AxisType.Lon;
      case Height:
        return CdmrFeatureProto.AxisType.Height;
      case Pressure:
        return CdmrFeatureProto.AxisType.Pressure;
      case RadialAzimuth:
        return CdmrFeatureProto.AxisType.RadialAzimuth;
      case RadialDistance:
        return CdmrFeatureProto.AxisType.RadialDistance;
      case RadialElevation:
        return CdmrFeatureProto.AxisType.RadialElevation;
      case Spectral:
        return CdmrFeatureProto.AxisType.Spectral;
      case TimeOffset:
        return CdmrFeatureProto.AxisType.TimeOffset;
    }
    throw new IllegalStateException("illegal data type " + dtype);
  }

  static public CdmrFeatureProto.Calendar convertCalendar(Calendar type) {
    switch (type) {
      case gregorian:
        return CdmrFeatureProto.Calendar.gregorian;
      case proleptic_gregorian:
        return CdmrFeatureProto.Calendar.proleptic_gregorian;
      case noleap:
        return CdmrFeatureProto.Calendar.noleap;
      case all_leap:
        return CdmrFeatureProto.Calendar.all_leap;
      case uniform30day:
        return CdmrFeatureProto.Calendar.uniform30day;
      case julian:
        return CdmrFeatureProto.Calendar.julian;
      case none:
        return CdmrFeatureProto.Calendar.none;
    }
    throw new IllegalStateException("illegal data type " + type);
  }

    //   public enum Type {Coverage, Curvilinear, Grid, Swath, Fmrc}

  static public CdmrFeatureProto.CoverageType convertCoverageType(FeatureType type) {
    switch (type) {
      case COVERAGE:
        return CdmrFeatureProto.CoverageType.General;
      case CURVILINEAR:
        return CdmrFeatureProto.CoverageType.Curvilinear;
      case GRID:
        return CdmrFeatureProto.CoverageType.Grid;
      case SWATH:
        return CdmrFeatureProto.CoverageType.Swath;
      case FMRC:
        return CdmrFeatureProto.CoverageType.Fmrc;
    }
    throw new IllegalStateException("illegal CoverageType " + type);
  }

  static public CdmrFeatureProto.DependenceType convertDependenceType(CoverageCoordAxis.DependenceType type) {
    switch (type) {
      case independent:
        return CdmrFeatureProto.DependenceType.independent;
      case dependent:
        return CdmrFeatureProto.DependenceType.dependent;
      case scalar:
        return CdmrFeatureProto.DependenceType.scalar;
      case twoD:
        return CdmrFeatureProto.DependenceType.twoD;
      case fmrcReg:
        return CdmrFeatureProto.DependenceType.fmrcReg;
    }
    throw new IllegalStateException("illegal data type " + type);
  }

  static public CdmrFeatureProto.AxisSpacing convertSpacing(CoverageCoordAxis.Spacing type) {
    switch (type) {
      case regularPoint:
        return CdmrFeatureProto.AxisSpacing.regularPoint;
      case irregularPoint:
        return CdmrFeatureProto.AxisSpacing.irregularPoint;
      case contiguousInterval:
        return CdmrFeatureProto.AxisSpacing.contiguousInterval;
      case discontiguousInterval:
        return CdmrFeatureProto.AxisSpacing.discontiguousInterval;
      case regularInterval:
        return CdmrFeatureProto.AxisSpacing.regularInterval;
    }
    throw new IllegalStateException("illegal data type " + type);
  }

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

  /*
  message GeoReferencedArray {
    required string gridName = 1;          // full escaped name.
    required DataType dataType = 2;
    optional bool bigend = 3 [default = true];
    optional uint32 version = 4 [default = 0];
    optional Compress compress = 5 [default = NONE];
    optional uint32 uncompressedSize = 6;

    repeated uint32 shape = 7;            // the shape of the returned array
    repeated string axisName = 8;         // each dimension corresponds to this axis
    required string coordSysName = 9;     // must have coordAxis corresponding to shape
  }

  message DataResponse {
    repeated CoordAxis coordAxes = 1;              // may be shared if asking for multiple grids
    repeated CoordSys coordSys = 2;                // may be shared if asking for multiple grids

    repeated GeoReferencedArray data = 4;
  }
   */

  public CdmrFeatureProto.CoverageDataResponse encodeDataResponse(Iterable<CoverageCoordAxis> axes,
        Iterable<CoverageCoordSys> coordSys, Iterable<CoverageTransform> transforms, List<GeoReferencedArray> arrays, boolean deflate) {

    CdmrFeatureProto.CoverageDataResponse.Builder builder = CdmrFeatureProto.CoverageDataResponse.newBuilder();
    for (CoverageCoordAxis axis : axes)
      builder.addCoordAxes( encodeCoordAxis(axis));
    for (CoverageCoordSys cs : coordSys)
      builder.addCoordSys(encodeCoordSys(cs));
    for (CoverageTransform t : transforms)
      builder.addCoordTransforms(encodeCoordTransform(t));
    for (GeoReferencedArray array : arrays)
      builder.addGeoArray(encodeGeoReferencedArray(array, deflate));

    return builder.build();
  }

  public CdmrFeatureProto.GeoReferencedArray.Builder encodeGeoReferencedArray(GeoReferencedArray geoArray, boolean deflate) {
    CdmrFeatureProto.GeoReferencedArray.Builder builder = CdmrFeatureProto.GeoReferencedArray.newBuilder();
    builder.setCoverageName(geoArray.getCoverageName());
    builder.setDataType(NcStream.convertDataType(geoArray.getDataType()));
    builder.setVersion(3); // set to >= 3 for proto3
    builder.setBigend(ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);

    int shape[] = geoArray.getData().getShape();
    for (int aShape : shape) builder.addShape(aShape);

    CoverageCoordSys csys = geoArray.getCoordSysForData();
    for (String axisName : csys.getAxisNames()) // geoArray.getAxisNames())  // LOOK could use csys.getAxisNames(), but order may be incorrect, must match shape
      builder.addAxisName(axisName);

    builder.setCoordSysName(csys.getName());

    if (deflate) {
      builder.setCompress(NcStreamProto.Compress.DEFLATE);
      long uncompressedSize = geoArray.getData().getSizeBytes();
      builder.setUncompressedSize(uncompressedSize);

    } else {
      builder.setPrimdata(NcStreamDataCol.copyArrayToByteString( geoArray.getData()));
    }

    return builder;
  }

  private long sendData(Array data, OutputStream out, boolean deflate) throws IOException, InvalidRangeException {

    // length of data uncompressed
    long uncompressedLength = data.getSizeBytes();
    long size = 0;

    if (deflate) {
      // write to an internal buffer, so we can find out the size
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      DeflaterOutputStream dout = new DeflaterOutputStream(bout);
      IospHelper.copyToOutputStream(data, dout);

      // write internal buffer to output stream
      dout.close();
      int deflatedSize = bout.size();
      size += NcStream.writeVInt(out, deflatedSize);
      bout.writeTo(out);
      size += deflatedSize;

    } else {
      size += NcStream.writeVInt(out, (int) uncompressedLength);
      size += IospHelper.copyToOutputStream(data, out);
    }

    return size;
  }

  private int writeBytes(OutputStream out, byte[] b) throws IOException {
    out.write(b);
    return b.length;
  }
}


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

package thredds.inventory;

import org.slf4j.Logger;
import thredds.filesystem.MFileOS;
import thredds.filesystem.MFileOS7;
import ucar.nc2.util.CloseableIterator;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

/**
 * A MCollection defined by a glob filter.  Experimental.
 *
 * From http://blog.eyallupu.com/2011/11/java-7-working-with-directories.html
 *
 * The 'glob' Syntax
 The glob (stands for globbing) syntax is a 'simplified' form of regular expressions with awareness to path
 components (directories), the syntax is composed of the following syntactic tokens:
  1) The '*' character matches zero or more characters from the path elements without crossing directory boundaries
   (unlike regular expression this is not a Kleene star and it has nothing to do with the preceding part of the expression)
 2) The '**' characters match zero or more characters crossing directory boundaries
 3) The '?' character matches exactly one character of a name component
 4) '[' and ']' can be used to match a single character in the path name from a set of characters
    '-' (hyphen) can be used to specify a range of characters. If hyphen has to be included in the characters set it must be the first in the set
    '!' as the first character in the set can be used as a negation expression
 5) '{' and '}' can group sub patterns, the group matches if any of the sub patterns matches (comma is used to separate between the groups)
 6 ) The dot '.' character represents a dot (unlike regular expressions in which a dot is a replacement for any character)
 7) and finally: special characters escaping is done using backslash

 The '**' expression is the only one to cross directory boundaries, all other expressions are bound within a single element
  (either a directory or a filename), below is a sample usage of PathMatcher followed by few pattern examples:

 *
 *
 *
 * @author caron
 * @since 5/19/14
 */
public class CollectionGlob extends CollectionAbstract {
  PathMatcher matcher;
  boolean debug = false;
  int depth = 0;

  public CollectionGlob(String collectionName, String glob, Logger logger) {
    super(collectionName, logger);

    matcher = FileSystems.getDefault().getPathMatcher("glob:"+glob);
    System.out.printf("glob=%s%n", glob);

    // lets suppose the first "*" indicates the top dir
    int pos = glob.indexOf("*");
    this.root = glob.substring(0, pos-1);
    String match = glob.substring(pos);

    // count how far to recurse. LAME!!! why doesnt java provide the right thing !!!!
    pos = glob.indexOf("**");
    if (pos > 0)
      depth = Integer.MAX_VALUE;
    else {
      // count the "/" !!
      for (char c : match.toCharArray())
        if (c == '/') depth++;
    }

    if (debug) System.out.printf(" CollectionGlob.MFileIterator topPath='%s' depth=%d%n", this.root, this.depth);

  }

  @Override
  public void close() {

  }

  @Override
  public Iterable<MFile> getFilesSorted() throws IOException {
    return makeFileListSorted();
  }

  @Override
  public CloseableIterator<MFile> getFileIterator() throws IOException {
    return new MyFileIterator(this.root);
  }

  // from http://blog.eyallupu.com/2011/11/java-7-working-with-directories.html
  public static DirectoryStream newDirectoryStream(Path dir, String glob) throws IOException {
    FileSystem fs = dir.getFileSystem();
    final PathMatcher matcher = fs.getPathMatcher("glob:" + glob);
    DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
        public boolean accept(Path entry)  {
            return matcher.matches(entry.getFileName());
        }
    };
    return fs.provider().newDirectoryStream(dir, filter);
  }

  private class MyFileIterator implements CloseableIterator<MFile> {
    DirectoryStream<Path> dirStream;
    Iterator<Path> dirStreamIterator;
    MFile nextMFile;
    int count = 0, total = 0;
    Stack<Path> subdirs = new Stack<>();
    int currDepth = 0;

    MyFileIterator(String topDir) throws IOException {
      Path topPath = Paths.get(topDir);
      dirStream = Files.newDirectoryStream(topPath);
      dirStreamIterator = dirStream.iterator();
    }

    public boolean hasNext() {
      while (true) {

        try {
               // if (debug && count % 100 == 0) System.out.printf("%d ", count);
          while (!dirStreamIterator.hasNext()) {
            dirStream.close();
            if (subdirs.isEmpty()) {
              nextMFile = null;
              return false;
            }
            currDepth++;                             // LOOK wrong
            Path nextSubdir = subdirs.pop();
            dirStream = Files.newDirectoryStream(nextSubdir);
            dirStreamIterator = dirStream.iterator();
          }

          total++;
          Path nextPath = dirStreamIterator.next();
          BasicFileAttributes attr = Files.readAttributes(nextPath, BasicFileAttributes.class);
          if (attr.isDirectory()) {
            if (currDepth < depth) subdirs.push(nextPath);
            continue;
          }

          if (!matcher.matches(nextPath)) {
            // if (debug) System.out.printf(" SKIP %s%n ", nextPath);
            continue;
          }

          nextMFile = new MFileOS7(nextPath, attr);
          return true;
          // if (debug) System.out.printf("  OK  %s%n ", nextMFile);

       } catch (IOException e) {
         throw new RuntimeException(e);
       }
       // if (filter == null || filter.accept(nextMFile)) return true;
      }
    }

    public MFile next() {
      if (nextMFile == null) throw new NoSuchElementException();
      count++;
      return nextMFile;
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }

    // better alternative is for caller to send in callback (Visitor pattern)
    // then we could use the try-with-resource
    public void close() throws IOException {
      if (debug) System.out.printf("  OK=%d total=%d%n ", count, total);
      dirStream.close();
    }
  }
}


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

import ucar.nc2.AttributeContainer;
import ucar.nc2.Attribute;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.transform.*;
import ucar.ma2.DataType;
import ucar.ma2.Array;
import ucar.nc2.ft2.coverage.CoverageTransform;
import ucar.unidata.geoloc.ProjectionImpl;
import ucar.unidata.util.Parameter;

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

/**
 * Manager for Coordinate Transforms.
 * @author john caron
 */
public class CoordTransBuilder {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CoordTransBuilder.class);
  static private List<Transform> transformList = new ArrayList<>();
  static private boolean userMode = false;

  static private final boolean loadWarnings = false;

  // search in the order added
  static {
    registerTransform(CF.ALBERS_CONICAL_EQUAL_AREA, AlbersEqualArea.class);
    registerTransform(CF.AZIMUTHAL_EQUIDISTANT, AzimuthalEquidistant.class);
    registerTransform("flat_earth", FlatEarth.class);
    registerTransform(CF.GEOSTATIONARY, Geostationary.class);
    registerTransform(CF.LAMBERT_AZIMUTHAL_EQUAL_AREA, LambertAzimuthal.class);
    registerTransform(CF.LAMBERT_CONFORMAL_CONIC , LambertConformalConic.class);
    registerTransform(CF.LAMBERT_CYLINDRICAL_EQUAL_AREA , LambertCylindricalEqualArea.class);
    registerTransform(CF.LATITUDE_LONGITUDE, LatLon.class);
    registerTransformMaybe("mcidas_area", "ucar.nc2.iosp.mcidas.McIDASAreaTransformBuilder"); // optional - needs visad.jar
    registerTransform(CF.MERCATOR, Mercator.class);
    registerTransform("MSGnavigation", MSGnavigation.class);
    registerTransform(CF.ORTHOGRAPHIC, Orthographic.class);
    registerTransform(CF.POLAR_STEREOGRAPHIC, PolarStereographic.class);
    registerTransform("polyconic", PolyconicProjection.class); // ghansham@sac.isro.gov.in 1/8/2012
    registerTransform(CF.ROTATED_LATITUDE_LONGITUDE, RotatedPole.class);
    registerTransform("rotated_latlon_grib", RotatedLatLon.class);
    registerTransform(CF.SINUSOIDAL, Sinusoidal.class);
    registerTransform(CF.STEREOGRAPHIC, Stereographic.class);
    registerTransform(CF.TRANSVERSE_MERCATOR, TransverseMercator.class);
    registerTransform("UTM", UTM.class);
    registerTransform(CF.VERTICAL_PERSPECTIVE, VerticalPerspective.class);

    // registerTransform("atmosphere_ln_pressure_coordinate", VAtmLnPressure.class); // DO NOT USE: see CF1Convention.makeAtmLnCoordinate()
    registerTransform("atmosphere_hybrid_height_coordinate", CFHybridHeight.class);
    registerTransform("atmosphere_hybrid_sigma_pressure_coordinate", CFHybridSigmaPressure.class);
    registerTransform("atmosphere_sigma_coordinate", CFSigma.class);
    registerTransform("ocean_s_coordinate", CFOceanS.class);
    registerTransform("ocean_sigma_coordinate", CFOceanSigma.class);
    registerTransform("explicit_field", VExplicitField.class);
    registerTransform("existing3DField", VExplicitField.class); // deprecate

    //-sachin 03/25/09
    registerTransform("ocean_s_coordinate_g1", VOceanSG1.class);
    registerTransform("ocean_s_coordinate_g2", VOceanSG2.class);

    // further calls to registerTransform are by the user
    userMode = true;
  }

   /**
    * Register a class that implements a Coordinate Transform.
    * @param transformName name of transform. This name is used in the datasets to identify the transform, eg CF names.
    * @param c class that implements CoordTransBuilderIF.
    */
  static public void registerTransform( String transformName, Class c) {
    if (!(VertTransformBuilderIF.class.isAssignableFrom( c)) && !(HorizTransformBuilderIF.class.isAssignableFrom( c)))
      throw new IllegalArgumentException("Class "+c.getName()+" must implement VertTransformBuilderIF or HorizTransformBuilderIF");

    // fail fast - check newInstance works
    try {
      c.newInstance();
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("CoordTransBuilderIF Class "+c.getName()+" cannot instantiate, probably need default Constructor");
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("CoordTransBuilderIF Class "+c.getName()+" is not accessible");
    }

    // user stuff gets put at top
    if (userMode)
      transformList.add( 0, new Transform( transformName, c));
    else
      transformList.add( new Transform( transformName, c));

  }

  /**
   * Register a class that implements a Coordinate Transform.
   * @param transformName name of transform. This name is used in the datasets to identify the transform, eg CF names.
   * @param className name of class that implements CoordTransBuilderIF.
   * @throws ClassNotFoundException if Class.forName( className) fails
   */
  static public void registerTransform( String transformName, String className) throws ClassNotFoundException {
    Class c = Class.forName( className);
    registerTransform( transformName, c);
  }

  /**
   * Register a class that implements a Coordinate Transform.
   * @param transformName name of transform. This name is used in the datasets to identify the transform, eg CF names.
   * @param className name of class that implements CoordTransBuilderIF.
   */
  static public void registerTransformMaybe( String transformName, String className) {
    Class c;
    try {
      c = Class.forName( className);
    } catch (ClassNotFoundException e) {
      if (loadWarnings) log.warn("Coordinate Transform Class "+className+" not found.");
      return;
    }
    registerTransform( transformName, c);
  }

  static private class Transform {
    String transName;
    Class transClass;
    Transform(String transName,  Class transClass) {
      this.transName = transName;
      this.transClass = transClass;
    }
  }

  /**
   * Make a CoordinateTransform object from the parameters in a Coordinate Transform Variable, using an intrinsic or registered CoordTransBuilder.
   * @param ds enclosing dataset, only used for vertical transforms
   * @param ctv the Coordinate Transform Variable - container for the transform parameters
   * @param parseInfo pass back information about the parsing.
   * @param errInfo pass back error information.
   * @return CoordinateTransform, or null if failure.
   */
  static public CoordinateTransform makeCoordinateTransform (NetcdfDataset ds, AttributeContainer ctv, Formatter parseInfo, Formatter errInfo) {
    // standard name
    String transform_name = ctv.findAttValueIgnoreCase("transform_name", null);
    if (null == transform_name)
      transform_name = ctv.findAttValueIgnoreCase("Projection_Name", null);

    // these names are from CF - dont want to have to duplicate
    if (null == transform_name)
      transform_name = ctv.findAttValueIgnoreCase(CF.GRID_MAPPING_NAME, null);
    if (null == transform_name)
      transform_name = ctv.findAttValueIgnoreCase(CF.STANDARD_NAME, null);

    if (null == transform_name) {
      parseInfo.format("**Failed to find Coordinate Transform name from Variable= %s%n", ctv);
      return null;
    }

    transform_name = transform_name.trim();

    // do we have a transform registered for this ?
    Class builderClass = null;
    for (Transform transform : transformList) {
      if (transform.transName.equals(transform_name)) {
        builderClass = transform.transClass;
        break;
      }
    }
    if (null == builderClass) {
      parseInfo.format("**Failed to find CoordTransBuilder name= %s from Variable= %s%n", transform_name, ctv);
      return null;
    }

         // get an instance of that class
    Object builderObject;
    try {
      builderObject = builderClass.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
      log.error("Cant create new instance "+builderClass.getName(), e);
      return null;
    }

    if (null == builderObject) { // cant happen - because this was tested in registerTransform()
      parseInfo.format("**Failed to build CoordTransBuilder object from class= %s for Variable= %s%n", builderClass.getName(), ctv);
      return null;
    }

    CoordinateTransform ct;
    if (builderObject instanceof VertTransformBuilderIF){
      VertTransformBuilderIF vertBuilder = (VertTransformBuilderIF) builderObject;
      vertBuilder.setErrorBuffer(errInfo);
      ct = vertBuilder.makeCoordinateTransform(ds, ctv);

    } else if (builderObject instanceof HorizTransformBuilderIF){
      HorizTransformBuilderIF horizBuilder = (HorizTransformBuilderIF) builderObject;
      horizBuilder.setErrorBuffer(errInfo);
      String units = AbstractTransformBuilder.getGeoCoordinateUnits(ds, ctv); // barfola
      ct = horizBuilder.makeCoordinateTransform(ctv, units);

    } else {
      log.error("Illegals class "+builderClass.getName());
      return null;
    }

    if (ct != null) {
      parseInfo.format(" Made Coordinate transform %s from variable %s: %s%n", transform_name, ctv.getName(), builderObject.getClass().getName());
    }

    return ct;
  }

  /**
   * Create a "dummy" Coordinate Transform Variable based on the given CoordinateTransform.
   * This creates a scalar Variable with dummy data, and adds the Parameters of the CoordinateTransform
   * as attributes.
   * @param ds for this dataset
   * @param ct based on the CoordinateTransform
   * @return the Coordinate Transform Variable. You must add it to the dataset.
   */
  static public VariableDS makeDummyTransformVariable(NetcdfDataset ds, CoordinateTransform ct) {
    VariableDS v = new VariableDS( ds, null, null, ct.getName(), DataType.CHAR, "", null, null);
    List<Parameter> params = ct.getParameters();
    for (Parameter p : params) {
      if (p.isString())
        v.addAttribute(new Attribute(p.getName(), p.getStringValue()));
      else {
        double[] data = p.getNumericValues();
        Array dataA = Array.factory(DataType.DOUBLE, new int[]{data.length}, data);
        v.addAttribute(new Attribute(p.getName(), dataA));
      }
    }
    v.addAttribute( new Attribute(_Coordinate.TransformType, ct.getTransformType().toString()));

    // fake data
    Array data = Array.factory(DataType.CHAR, new int[] {}, new char[] {' '});
    v.setCachedData(data, true);

    return v;
  }

  /**
   * Make a CoordinateTransform object from the parameters in a GridCoordTransform, using an intrinsic or
   * registered CoordTransBuilder.
   * @param errInfo pass back error information.
   * @return CoordinateTransform, or null if failure.
   */
  static public ProjectionImpl makeProjection(CoverageTransform gct, Formatter errInfo) {
    // standard name
    String transform_name = gct.findAttValueIgnoreCase(CF.GRID_MAPPING_NAME, null);

    if (null == transform_name) {
      errInfo.format("**Failed to find Coordinate Transform name from GridCoordTransform= %s%n", gct);
      return null;
    }

    transform_name = transform_name.trim();

    // do we have a transform registered for this ?
    Class builderClass = null;
    for (Transform transform : transformList) {
      if (transform.transName.equals(transform_name)) {
        builderClass = transform.transClass;
        break;
      }
    }
    if (null == builderClass) {
      errInfo.format("**Failed to find CoordTransBuilder name= %s from GridCoordTransform= %s%n", transform_name, gct);
      return null;
    }

      // get an instance of that class
    HorizTransformBuilderIF builder;
    try {
      builder = (HorizTransformBuilderIF) builderClass.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
      log.error("Cant create new instance "+builderClass.getName(), e);
      return null;
    }
    if (null == builder) { // cant happen - because this was tested in registerTransform()
      errInfo.format("**Failed to build CoordTransBuilder object from class= %s for GridCoordTransform= %s%n", builderClass.getName(), gct);
      return null;
    }

    String units = gct.findAttValueIgnoreCase(CDM.UNITS, null);
    builder.setErrorBuffer( errInfo);
    ProjectionCT ct = builder.makeCoordinateTransform(gct, units);
    assert ct != null;

    return ct.getProjection();
  }

}


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

package thredds.ui.catalog;

import thredds.client.catalog.*;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.XMLStore;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Formatter;

//import thredds.catalog.ui.tools.CatalogSearcher;

/**
 * A Swing widget for THREDDS clients that combines a CatalogChooser, and optionally a QueryChooser
 * PropertyChangeEvent events are thrown to notify you of various
 * user actions; see addPropertyChangeListener.
 *

 * You can use the ThreddsDatasetChooser:
 *

 *
 add the components into your own JTabbedPanel.
 *
 wrapped in a JDialog for popping up
 *
 as a standalone application through its main() method
 *

 * Example:
 *

 * datasetChooser = new ThreddsDatasetChooser( prefs, tabbedPane);
 * datasetChooser.addPropertyChangeListener(  new java.beans.PropertyChangeListener() {
 * public void propertyChange( java.beans.PropertyChangeEvent e) {
 * if (e.getPropertyName().equals("Dataset")) {
 * thredds.catalog.InvDataset ds = (thredds.catalog.InvDataset) e.getNewValue();
 * setDataset( ds);
 * }
 * }
 * }
 * });
 *

 *

 * To use as popup dialog box:
 *

 * ThreddsDatasetChooser datasetChooser = new ThreddsDatasetChooser( prefs, null);
 * JDialog datasetChooserDialog = datasetChooser.makeDialog("Open THREDDS dataset", true);
 * datasetChooserDialog.show();
 *

 *

 * When using as a standalone application, the default behavior is to write the dataURLs of the
 * selections to standard out. Copy main() and make changes as needed.
 *

 * java -classpath clientUI.jar;... thredds.catalog.ui.ThreddsDatasetChooser
 *

 *
 * @author John Caron
 */

public class ThreddsDatasetChooser extends JPanel {
  private final static String FRAME_SIZE = "FrameSize";

  private CatalogChooser catalogChooser;
  private JTabbedPane tabbedPane;

  private boolean doResolve = false;  // shoul we resolve Resolver datasets?
  private boolean pipeOut = true;  // send results to standard out
  private boolean messageOut = false;  // send results to popup message
  private JFrame frame; // need for popup messages

  private boolean debugResolve = false;

  /**
   * Usual Constructor.
   * Create a CatalogChooser and a QueryChooser widget, add them to a JTabbedPane.
   *
   * @param prefs persistent storage, may be null
   * @param tabs  add panels to this JTabbedPane, may be null if you are using as Dialog.
   */
  public ThreddsDatasetChooser(PreferencesExt prefs, JTabbedPane tabs) {
    this(prefs, tabs, null, false, false, false);
  }

  /**
   * General Constructor.
   * Create a CatalogChooser and a QueryChooser widget, add them to a JTabbedPane.
   * Optionally write to stdout and/or pop up event messsages.
   *
   * @param prefs         persistent storage
   * @param tabs          add to this JTabbedPane
   * @param frame         best if non-null when messageOutP = true, otherwise null
   * @param pipeOutput    send selection message to System.out
   * @param messageOutput send selection to popup message
   */
  public ThreddsDatasetChooser(PreferencesExt prefs, JTabbedPane tabs, JFrame frame,
                               boolean pipeOutput, boolean messageOutput, boolean addDqc) {

    this.frame = frame;
    this.pipeOut = pipeOutput;
    this.messageOut = messageOutput;

    // create the catalog chooser
    PreferencesExt node = (prefs == null) ? null : (PreferencesExt) prefs.node("catChooser");
    catalogChooser = new CatalogChooser(node, true, true, true);
    catalogChooser.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
      public void propertyChange(java.beans.PropertyChangeEvent e) {

        if (e.getPropertyName().equals("InvAccess")) {
          firePropertyChangeEvent(e);
          return;
        }

        // see if this dataset is really a qc
        if (e.getPropertyName().equals("Dataset") || e.getPropertyName().equals("CoordSys") || e.getPropertyName().equals("File")) {
          firePropertyChangeEvent(e);
        }
      }
    });

    // the overall UI
    tabbedPane = (tabs == null) ? new JTabbedPane(JTabbedPane.TOP) : tabs;
    tabbedPane.addTab("Catalog Chooser", catalogChooser);
    tabbedPane.setSelectedComponent(catalogChooser);

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

  /**
   * If you want resolver datasets to be resolved (default false).
   * If true, may throw "Datasets" event.
   *
   * @param doResolve
   */
  public void setDoResolve(boolean doResolve) {
    this.doResolve = doResolve;
  }

  /**
   * Get the component CatalogChooser
   */
  public CatalogChooser getCatalogChooser() {
    return catalogChooser;
  }

  /**
   * save the state
   */
  public void save() {
    catalogChooser.save();
  }

  /**
   * Fires a PropertyChangeEvent:
   *

   *
  propertyName = "Dataset" or "File", getNewValue() = InvDataset chosen.
   *
  propertyName = "Datasets", getNewValue() = InvDataset[] chosen. This can only happen if
   * you have set doResolve = true, and the resolved dataset is a list of datasets.
   *
  propertyName = "InvAccess" getNewValue() = InvAccess chosen.
   *

   */
  private void firePropertyChangeEvent(PropertyChangeEvent event) {
    // System.out.println("firePropertyChangeEvent "+((InvDatasetImpl)ds).dump());
    if (pipeOut)
      pipeEvent(event);
    if (messageOut)
      messageEvent(event);

    firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue());
  }

  private void messageEvent(java.beans.PropertyChangeEvent e) {
    Formatter buff = new Formatter();
    buff.format("Event propertyName = %s", e.getPropertyName());
    Object newValue = e.getNewValue();
    if (newValue != null)
      buff.format(", class = %s", newValue.getClass().getName());
    buff.format("%n");

    if (e.getPropertyName().equals("Dataset")) {
      showDatasetInfo(buff, (thredds.client.catalog.Dataset) e.getNewValue());

    } else if (e.getPropertyName().equals("Datasets")) {
      Object[] ds = (Object[]) e.getNewValue();
      buff.format(" element class = %s%n", ds[0].getClass().getName());

      for (int i = 0; i < ds.length; i++)
        if (ds[i] instanceof Dataset)
          showDatasetInfo(buff, (Dataset) ds[i]);
    }

    JOptionPane.showMessageDialog(frame, buff);
  }

  private void pipeEvent(java.beans.PropertyChangeEvent e) {
    Formatter buff = new Formatter();

    if (e.getPropertyName().equals("Dataset")) {
      getAccessURLs(buff, (Dataset) e.getNewValue());

    } else if (e.getPropertyName().equals("Datasets")) {
      Object[] ds = (Object[]) e.getNewValue();
      for (int i = 0; i < ds.length; i++)
        if (ds[i] instanceof Dataset)
          getAccessURLs(buff, (Dataset) ds[i]);
    }

    System.out.println(buff);
  }

  private void getAccessURLs(Formatter buff, Dataset ds) {
    for (Access ac : ds.getAccess())
      buff.format("%s %s %n", ac.getStandardUrlName(), ac.getService().getType());

  }

  private void showDatasetInfo(Formatter buff, thredds.client.catalog.Dataset ds) {
    buff.format(" Dataset = %s", ds.getName());
    buff.format(", dataType = %s%n", ds.getFeatureType());
    for (Access ac : ds.getAccess()) {
      buff.format("  service = %s, url = %s%n", ac.getService().getType(), ac.getStandardUrlName());
      //System.out.println("  url = "+ac.getStandardUrlName());
    }
  }

  /**
   * Wrap this in a JDialog component.
   *
   * @param parent put dialog on top of this, may be null
   * @param title  dialog window title
   * @param modal  is modal
   */
  public JDialog makeDialog(JFrame parent, String title, boolean modal) {
    return new Dialog(frame, title, modal);
  }

  private class Dialog extends JDialog {

    private Dialog(JFrame frame, String title, boolean modal) {
      super(frame, title, modal);

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

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

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

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

  /**
   * Standalone application.
   *
   * @param args recognized values:
   *

 -usePopup to popup messages

   */
  public static void main(String args[]) {
    boolean usePopup = false;

    for (int i = 0; i < args.length; i++) {
      if (args[i].equals("-usePopup"))
        usePopup = true;
    }

    try {
      store = XMLStore.createFromFile("ThreddsDatasetChooser", null);
      p = store.getPreferences();
    } catch (IOException e) {
      System.out.println("XMLStore Creation failed " + e);
    }

    // put it together in a JFrame
    final JFrame frame = new JFrame("Thredds Dataset Chooser");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        chooser.save();
        Rectangle bounds = frame.getBounds();
        p.putBeanObject(FRAME_SIZE, bounds);
        try {
          store.save();
        } catch (IOException ioe) {
          ioe.printStackTrace();
        }

        System.exit(0);
      }
    });

    chooser = new ThreddsDatasetChooser(p, null, frame, true, usePopup, false);
    chooser.setDoResolve(true);

    //
    frame.getContentPane().add(chooser);
    Rectangle bounds = (Rectangle) p.getBean(FRAME_SIZE, new Rectangle(50, 50, 800, 450));
    frame.setBounds(bounds);

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

  private static ThreddsDatasetChooser chooser;
  private static PreferencesExt p;
  private static XMLStore store;
}


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

package ucar.unidata.geoloc.vertical;

import ucar.ma2.*;
import ucar.ma2.ArrayDouble.D1;

import ucar.nc2.Dimension;
import ucar.nc2.Variable;
import ucar.nc2.NetcdfFile;
import ucar.unidata.util.Parameter;

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

/**
 * This implements a VerticalTransform using an existing 3D variable.
 * This is a common case when the 3D pressure or height field is stored in the file.
 *
 * @author john
 */
public class VTfromExistingData extends VerticalTransformImpl {

  /**
   * The name of the Parameter whose value is the variable that contains the 2D Height or Pressure field
   */
  public final static String existingDataField = "existingDataField";

  /**
   * The variable that contains the 2D Height or Pressure field
   */
  private Variable existingData;

  /**
   * Constructor.
   *
   * @param ds      containing Dataset
   * @param timeDim time Dimension
   * @param params  list of transformation Parameters
   */
  public VTfromExistingData(NetcdfFile ds, Dimension timeDim, List<Parameter> params) {
    super(timeDim);
    String vname = getParameterStringValue(params, existingDataField);
    this.existingData = ds.findVariable(vname);
    units = existingData.getUnitsString();
  }

  public ArrayDouble.D3 getCoordinateArray(int timeIndex) throws IOException, InvalidRangeException {
    Array data = readArray(existingData, timeIndex);

    // copy for now - better to just return Array, with promise its rank 3
    int[] shape = data.getShape();
    ArrayDouble.D3 ddata = (ArrayDouble.D3) Array.factory(DataType.DOUBLE, shape);
    MAMath.copyDouble(ddata, data);
    return ddata;
  }

  /**
   * Get the 1D vertical coordinate array for this time step and point
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @param xIndex    the x index
   * @param yIndex    the y index
   * @return vertical coordinate array
   * @throws java.io.IOException problem reading data
   * @throws ucar.ma2.InvalidRangeException _more_
   */
  public D1 getCoordinateArray1D(int timeIndex, int xIndex, int yIndex)
  		throws IOException, InvalidRangeException {

	    ArrayDouble.D3 ddata = getCoordinateArray(timeIndex);
	    int[] origin = new int[]{0, yIndex, xIndex };
	    int[] shape = new int[]{ ddata.getShape()[0] , 1, 1 };

	    return (ArrayDouble.D1)ddata.section(origin, shape).reduce();
  }

}


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

package thredds.server.catalog;

import javax.annotation.concurrent.Immutable;

/**
 * A DataRoot matches URLs to the objects that can serve them.
 * A DataRootPathMatcher manages a hash tree of path -> DataRoot
 * <p/>
 * Possible design:
 * catKey : which catalog defined this?   not present at the moment
 * directory : if its a simple DataRoot
 * fc        : prob is this drags in entire configCat
 * dscan     : ditto
 *
 * @author caron
 * @since 1/23/2015
 */
@Immutable
public class DataRoot {
  private static final boolean show = false;
  public enum Type {datasetRoot, datasetScan, catalogScan, featureCollection}

  private final String path;          // match this path to 1 of the following:
  private final String dirLocation;   // 1) this directory  (not null)
  private final String name;   // 1) this directory  (not null)
  private final DatasetScan datasetScan;     // 2) the DatasetScan that created this (may be null)
  private final FeatureCollectionRef featCollection; // 3) the FeatureCollection that created this (may be null)
  private final CatalogScan catScan;  // 4) the CatalogScan that created this (may be null)
  private final Type type;
  private final String restrict;

  public DataRoot(FeatureCollectionRef featCollection) {
    this.path = featCollection.getPath();
    this.dirLocation = featCollection.getTopDirectoryLocation();
    this.datasetScan = null;
    this.catScan = null;
    this.featCollection = featCollection;
    this.type = Type.featureCollection;
    this.name = featCollection.getCollectionName();
    this.restrict = featCollection.getRestrictAccess();
    show();
  }

  public DataRoot(DatasetScan scan) {
    this.path = scan.getPath();
    this.dirLocation = scan.getScanLocation();
    this.datasetScan = scan;
    this.catScan = null;
    this.featCollection = null;
    this.type = Type.datasetScan;
    this.name = scan.getName();
    this.restrict = scan.getRestrictAccess();
    show();
  }

  public DataRoot(CatalogScan catScan) {
    this.path = catScan.getPath();
    this.dirLocation = catScan.getLocation();
    this.datasetScan = null;
    this.catScan = catScan;
    this.featCollection = null;
    this.type = Type.catalogScan;
    this.name = catScan.getName();
    this.restrict = null;
    show();
  }

  public DataRoot(String path, String dirLocation, String restrict) {
    this.path = path;
    this.dirLocation = dirLocation;
    this.datasetScan = null;
    this.catScan = null;
    this.featCollection = null;
    this.type = Type.datasetRoot;
    this.name = null;
    this.restrict = restrict;
    show();
  }

  private void show() {
    if (show) System.out.printf(" DataRoot %s==%s%n", path, dirLocation);
  }

  public String getPath() {
    return path;
  }

  public String getDirLocation() {
    return dirLocation;
  }

  public Type getType() {
    return type;
  }

  public DatasetScan getDatasetScan() {
    return datasetScan;
  }

  public CatalogScan getCatalogScan() {
    return catScan;
  }

  public FeatureCollectionRef getFeatureCollection() {
    return featCollection;
  }

  public String getName() {
    return name;
  }

  public String getRestrict() {
    return restrict;
  }

  // used by PathMatcher
  public String toString() {
    return path;
  }

  /**
   * Instances which have same path are equal.
   */
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    DataRoot root = (DataRoot) o;
    return path.equals(root.path);
  }

  public int hashCode() {
    return path.hashCode();
  }

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

  public String getFileLocationFromRequestPath(String reqPath) {

    if (datasetScan != null) {
      // LOOK should check to see if its been filtered out by scan
      return getFileLocationFromRequestPath(reqPath, datasetScan.getPath(), datasetScan.getScanLocation(), false);

    } else if (catScan != null) {
      // LOOK should check to see if its allowed in fc
      return getFileLocationFromRequestPath(reqPath, catScan.getPath(), catScan.getLocation(), false);

    } else if (featCollection != null) {
      // LOOK should check to see if its allowed in fc
      return getFileLocationFromRequestPath(reqPath, featCollection.getPath(), featCollection.getTopDirectoryLocation(), true);

    } else {  // must be a datasetRoot
      // LOOK should check to see if it exists ??
      return getFileLocationFromRequestPath(reqPath, getPath(), getDirLocation(), false);
    }

  }

  private String getFileLocationFromRequestPath(String reqPath, String rootPath, String rootLocation, boolean isFeatureCollection) {
    if (reqPath == null) return null;
    if (reqPath.length() == 0) return null;

    if (reqPath.startsWith("/"))
      reqPath = reqPath.substring(1);

    if (!reqPath.startsWith(rootPath))
      return null;

    // remove the matching part, the rest is the "data directory"
    String locationReletive = reqPath.substring(rootPath.length());
    if (isFeatureCollection && locationReletive.startsWith("/files"))
      locationReletive = locationReletive.substring(7); // LOOK maybe only if its an fc ?? its a kludge here

    if (locationReletive.startsWith("/"))
      locationReletive = locationReletive.substring(1);

    if (!rootLocation.endsWith("/"))
      rootLocation = rootLocation + "/";

    // put it together
    return (locationReletive.length() > 1) ? rootLocation + locationReletive : rootLocation;
  }
}


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

package ucar.nc2.ui.op;

import thredds.inventory.bdb.MetadataManager;
import ucar.nc2.ui.widget.BAMutil;
import ucar.nc2.ui.widget.IndependentWindow;
import ucar.nc2.ui.widget.PopupMenu;
import ucar.nc2.ui.widget.TextHistoryPane;
import ucar.unidata.util.StringUtil2;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.BeanTable;

import java.awt.BorderLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

/**
 * Show the info stored in the MetadataManager
 *
 * @author caron
 * @since Jan 11, 2010
 */
public class FmrcCollectionTable extends JPanel {
  private PreferencesExt prefs;

  private BeanTable collectionNameTable, dataTable;
  private JSplitPane split;

  private TextHistoryPane infoTA;
  private IndependentWindow infoWindow;

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

    collectionNameTable = new BeanTable(CollectionBean.class, (PreferencesExt) prefs.node("DatasetBean"), false);
    collectionNameTable.addListSelectionListener(e -> {
        CollectionBean bean = (CollectionBean) collectionNameTable.getSelectedBean();
        setCollection(bean.name);
    });

    PopupMenu varPopup = new PopupMenu(collectionNameTable.getJTable(), "Options");
    varPopup.addAction("Show Collection Stats", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        CollectionBean bean = (CollectionBean) collectionNameTable.getSelectedBean();
        if (bean == null) return;
        showCollectionInfo(bean.name);
      }
    });
    varPopup.addAction("delete", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        CollectionBean bean = (CollectionBean) collectionNameTable.getSelectedBean();
        if (bean == null) return;
        try {
          MetadataManager.deleteCollection(bean.name);
        } catch (Exception e2) {
          e2.printStackTrace();
        }
        refresh();
      }
    });

    dataTable = new BeanTable(DataBean.class, (PreferencesExt) prefs.node("DataBean"), false);
    dataTable.addListSelectionListener(e -> {
        DataBean bean = (DataBean) dataTable.getSelectedBean();
        showData(bean);
    });
    varPopup = new PopupMenu(dataTable.getJTable(), "Options");
    varPopup.addAction("delete", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        CollectionBean cbean = (CollectionBean) collectionNameTable.getSelectedBean();
        DataBean bean = (DataBean) dataTable.getSelectedBean();
        if ((cbean == null) || (bean == null)) return;
        MetadataManager.delete(cbean.name, bean.getKey());
        setCollection(cbean.name);
      }
    });

    // the info window
    infoTA = new TextHistoryPane(false, 5000, 50, true, false, 14);
    infoWindow = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), infoTA);
    infoWindow.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));

    split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, collectionNameTable, dataTable);
    split.setDividerLocation(prefs.getInt("splitPos", 500));

    setLayout(new BorderLayout());
    add(split, BorderLayout.CENTER);
    refresh();
  }

  private void showData(DataBean bean) {
    infoTA.setText(bean.getValue());
    infoWindow.show();
  }

  // private MetadataManager mm;

  public void save() {
    collectionNameTable.saveState(false);
    dataTable.saveState(false);
    prefs.putBeanObject("InfoWindowBounds", infoWindow.getBounds());
    prefs.putInt("splitPos", split.getDividerLocation());
  }

  public void refresh() {
    List<CollectionBean> beanList = new ArrayList<CollectionBean>();
    for (String name : MetadataManager.getCollectionNames()) {
      beanList.add(new CollectionBean(name));
    }
    collectionNameTable.setBeans(beanList);
  }

  private void setCollection(String name) {
    List<DataBean> beans = new ArrayList<DataBean>();
    MetadataManager mm = null;
    try {
      mm = new MetadataManager(name);

      for (MetadataManager.KeyValue data : mm.getContent()) {
        beans.add(new DataBean(data));
      }
      dataTable.setBeans(beans);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (mm != null) mm.close();
    }
  }

  public void showInfo(Formatter result) throws IOException {
    MetadataManager.showEnvStats(result);
  }

  public void showCollectionInfo(String name) {
    MetadataManager mm = null;
    try {
      Formatter f = new Formatter();
      mm = new MetadataManager(name);
      mm.showStats(f);
      infoTA.setText(f.toString());
      infoTA.gotoTop();
      infoWindow.show();
    } catch (IOException e) {
      e.printStackTrace();
    }  finally {
      if (mm != null) mm.close();
    }
  }

  public class CollectionBean {
    String name;

    // no-arg constructor
    public CollectionBean() {
    }

    // create from a dataset
    public CollectionBean(String name) {
      this.name = name;
    }

    public String getName() throws IOException {
      return name;
    }

    public String getNameDecoded() throws IOException {
      return StringUtil2.unescape(name);
    }

  }

  public class DataBean {
    MetadataManager.KeyValue data;

    // no-arg constructor
    public DataBean() {
    }

    // create from a dataset
    public DataBean(MetadataManager.KeyValue data) {
      this.data = data;
    }

    public String getKey() {
      return data.key;
    }
    public String getValue() {
      return data.value;
    }
    public int getSize() {
      return data.value.length();
    }
  }

}


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

package ucar.nc2.iosp.bufr.writer;

import com.google.common.escape.Escaper;
import com.google.common.xml.XmlEscapers;
import ucar.nc2.constants.CDM;
import ucar.nc2.iosp.bufr.BufrIosp2;
import ucar.nc2.iosp.bufr.Message;
import ucar.nc2.iosp.bufr.MessageScanner;
import ucar.nc2.*;
import ucar.nc2.util.Indent;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.SequenceDS;
import ucar.nc2.dataset.StructureDS;
import ucar.ma2.*;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.StringUtil2;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.XMLOutputFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.util.Formatter;

/**
 * Write BUFR to an ad-hoc XML format
 *
 * @author caron
 * @since Aug 9, 2008
 */
public class Bufr2Xml {

  // private Formatter out = new Formatter(System.out);
  private XMLStreamWriter staxWriter;
  private Indent indent;
  private Escaper escaper = XmlEscapers.xmlAttributeEscaper();

  public Bufr2Xml(Message message, NetcdfFile ncfile, OutputStream os, boolean skipMissing) throws IOException {
    indent = new Indent(2);
    indent.setIndentLevel(0);

    try {
      XMLOutputFactory fac = XMLOutputFactory.newInstance();
      staxWriter = fac.createXMLStreamWriter(os, "UTF-8");

      staxWriter.writeStartDocument("UTF-8", "1.0");
      //staxWriter.writeCharacters("\n");
      //staxWriter.writeStartElement("bufrMessage");

      writeMessage(message, ncfile);

      staxWriter.writeCharacters("\n");
      staxWriter.writeEndDocument();
      staxWriter.flush();

    } catch (XMLStreamException e) {
      throw new IOException(e.getMessage());
    }
  }

  void writeMessage(Message message, NetcdfFile ncfile) {

    try {
      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeStartElement("bufrMessage");
      staxWriter.writeAttribute("nobs", Integer.toString(message.getNumberDatasets()));
      indent.incr();

      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());

      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeStartElement("edition");
      staxWriter.writeCharacters(Integer.toString(message.is.getBufrEdition()));
      staxWriter.writeEndElement();

      String header = message.getHeader().trim();
      if (header.length() > 0) {
        staxWriter.writeCharacters("\n");
        staxWriter.writeCharacters(indent.toString());
        staxWriter.writeStartElement("header");
        staxWriter.writeCharacters(header);
        staxWriter.writeEndElement();
      }

      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeStartElement("tableVersion");
      staxWriter.writeCharacters(message.getLookup().getTableName());
      staxWriter.writeEndElement();

      staxWriter.writeStartElement("center");
      staxWriter.writeCharacters(message.getLookup().getCenterName());
      staxWriter.writeEndElement();

      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeStartElement("category");
      staxWriter.writeCharacters(message.getLookup().getCategoryFullName());
      staxWriter.writeEndElement();

      SequenceDS obs = (SequenceDS) ncfile.findVariable(BufrIosp2.obsRecord);
      StructureDataIterator sdataIter = obs.getStructureIterator(-1);

      writeSequence(obs, sdataIter);

      // ending
      indent.decr();
      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeEndElement();

    } catch (IOException|XMLStreamException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
  }

  /* void writeStructureArray(StructureDS s, ArrayStructure data, Indent indent) throws IOException, XMLStreamException {
    StructureDataIterator sdataIter = data.getStructureDataIterator();
    while (sdataIter.hasNext()) {
      StructureData sdata = sdataIter.next();
      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeStartElement(s.getShortName());

      indent.incr();
      for (StructureMembers.Member m : sdata.getMembers()) {
        Variable v = s.findVariable(m.getName());

        if (m.getDataType().isString() || m.getDataType().isNumeric()) {
          writeVariable((VariableDS) v, sdata.getArray(m), indent);

        } else if (m.getDataType() == DataType.STRUCTURE) {
          writeStructureArray((StructureDS) v, (ArrayStructure) sdata.getArray(m), indent);

        } else if (m.getDataType() == DataType.SEQUENCE) {
          writeSequence((SequenceDS) v, (ArraySequence) sdata.getArray(m), indent);
        }

        if (m.getDataType().isString() || m.getDataType().isNumeric()) {
          writeVariable((VariableDS) v, sdata.getArray(m), indent);
        }
      }
      indent.decr();

      staxWriter.writeCharacters("\n");
      staxWriter.writeCharacters(indent.toString());
      staxWriter.writeEndElement();
    }
  } */

  // iterate through the observations

  private void writeSequence(StructureDS s, StructureDataIterator sdataIter) throws IOException, XMLStreamException {

    int count = 0;
    try {
      while (sdataIter.hasNext()) {
        //out.format("%sSequence %s count=%d%n", indent, s.getShortName(), count++);
        StructureData sdata = sdataIter.next();

        staxWriter.writeCharacters("\n");
        staxWriter.writeCharacters(indent.toString());
        staxWriter.writeStartElement("struct");
        staxWriter.writeAttribute("name", escaper.escape(s.getShortName()));
        staxWriter.writeAttribute("count", Integer.toString(count++));

        for (StructureMembers.Member m : sdata.getMembers()) {
          Variable v = s.findVariable(m.getName());
          indent.incr();

          if (m.getDataType().isString() || m.getDataType().isNumeric()) {
            writeVariable((VariableDS) v, sdata.getArray(m));

          } else if (m.getDataType() == DataType.STRUCTURE) {
            StructureDS sds = (StructureDS) v;
            ArrayStructure data = (ArrayStructure) sdata.getArray(m);
            writeSequence(sds, data.getStructureDataIterator());

          } else if (m.getDataType() == DataType.SEQUENCE) {
            SequenceDS sds = (SequenceDS) v;
            ArraySequence data = (ArraySequence) sdata.getArray(m);
            writeSequence(sds, data.getStructureDataIterator());
          }
          indent.decr();
        }

        staxWriter.writeCharacters("\n");
        staxWriter.writeCharacters(indent.toString());
        staxWriter.writeEndElement();
      }
    } finally {
      sdataIter.close();
    }
  }

  void writeVariable(VariableDS v, Array mdata) throws XMLStreamException, IOException {
    staxWriter.writeCharacters("\n");
    staxWriter.writeCharacters(indent.toString());

    // complete option
    staxWriter.writeStartElement("data");
    String name = v.getShortName();
    staxWriter.writeAttribute("name", escaper.escape(name));

    String units = v.getUnitsString();
    if ((units != null) && !units.equals(name) && !units.startsWith("Code"))
      staxWriter.writeAttribute(CDM.UNITS, escaper.escape(v.getUnitsString()));

    Attribute att = v.findAttribute(BufrIosp2.fxyAttName);
    String desc = (att == null) ? "N/A" : att.getStringValue();
    staxWriter.writeAttribute("bufr", escaper.escape(desc));

    if (v.getDataType() == DataType.CHAR) {
      ArrayChar ac = (ArrayChar) mdata;
      staxWriter.writeCharacters(ac.getString()); // turn into a string

    } else {

      int count = 0;
      mdata.resetLocalIterator();
      while (mdata.hasNext()) {
        if (count > 0) staxWriter.writeCharacters(" ");
        count++;

        if (v.getDataType().isNumeric()) {
          double val = mdata.nextDouble();

          if (v.isMissing(val)) {
            staxWriter.writeCharacters("missing");

          } else if ((v.getDataType() == DataType.FLOAT) || (v.getDataType() == DataType.DOUBLE)) {
            writeFloat(v, val);

          } else {  // numeric, not float
            staxWriter.writeCharacters(mdata.toString());
          }

        } else {  // not numeric
          String s = StringUtil2.filter7bits(mdata.next().toString());
          staxWriter.writeCharacters(escaper.escape(s));
        }
      }
    }

    staxWriter.writeEndElement();
  }

  private void writeFloat(Variable v, double val) throws XMLStreamException {
    Attribute bitWidthAtt = v.findAttribute("BUFR:bitWidth");
    int sigDigits;
    if (bitWidthAtt == null)
      sigDigits = 7;
    else {
      int bitWidth = bitWidthAtt.getNumericValue().intValue();
      if (bitWidth < 30) {
        double sigDigitsD = Math.log10(2 << bitWidth);
        sigDigits = (int) (sigDigitsD + 1);
      } else {
        sigDigits = 7;
      }
    }

    Formatter stringFormatter = new Formatter();
    String format = "%." + sigDigits + "g";
    stringFormatter.format(format, val);
    staxWriter.writeCharacters(stringFormatter.toString());
  }

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

    //String filename = "C:/temp/cache/uniqueMessages.bufr";
    String filename = "C:/data/formats/bufr/uniqueExamples.bufr";
    int size = 0;
    int count = 0;

    try (RandomAccessFile raf = new RandomAccessFile(filename, "r");
         OutputStream out = new FileOutputStream("C:/data/formats/bufr/uniqueE/" + count + ".xml")) {
      MessageScanner scan = new MessageScanner(raf);
      while (scan.hasNext()) {
        Message message = scan.next();
        if (message == null || !message.isTablesComplete() ||
                !message.isBitCountOk()) continue;
        byte[] mbytes = scan.getMessageBytesFromLast(message);
        NetcdfFile ncfile = NetcdfFile.openInMemory("test", mbytes, "ucar.nc2.iosp.bufr.BufrIosp");
        NetcdfDataset ncd = new NetcdfDataset(ncfile);
        new Bufr2Xml(message, ncd, out, true);
        out.close();
        count++;
        size += message.getMessageSize();
      }

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

    System.out.printf("total size= %f Kb %n", .001 * size);
  }
}