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

package dap4.cdm.nc2;

import dap4.cdm.CDMTypeFcns;
import dap4.cdm.CDMUtil;
import dap4.core.data.DSP;
import dap4.core.data.DataCursor;
import dap4.core.dmr.DapType;
import dap4.core.dmr.DapVariable;
import dap4.core.util.Convert;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.core.util.Slice;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.IndexIterator;
import ucar.nc2.Group;

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

import static dap4.core.data.DataCursor.Scheme;

/**
 * CDMArrayAtomic wraps a DataCursor object to present
 * the ucar.ma2.Array interface.
 * CDMArrayAtomic manages a single CDM atomic variable:
 * either top-level or for a member.
 */

/*package*/ class CDMArrayAtomic extends Array implements CDMArray
{
    /////////////////////////////////////////////////////
    // Constants

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

    protected DSP dsp = null;
    protected DapVariable template = null;
    protected DapType basetype = null;

    // CDMArray variables
    protected DataCursor data = null;
    protected Group cdmroot = null;
    protected int elementsize = 0;    // of one element
    protected long dimsize = 0;        // # of elements in array; scalar uses value 1
    protected long totalsize = 0;      // elementsize*dimsize except when isbytestring

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

    /**
     * Constructor
     *
     * @param data DataCursor object providing the actual data
     */
    CDMArrayAtomic(DataCursor data)
            throws DapException
    {
        super(CDMTypeFcns.daptype2cdmtype(((DapVariable) data.getTemplate()).getBaseType()),
                CDMUtil.computeEffectiveShape(((DapVariable) data.getTemplate()).getDimensions()));
        this.dsp = data.getDSP();
        this.data = data;
        this.template = (DapVariable) this.data.getTemplate();
        this.basetype = this.template.getBaseType();

        this.dimsize = DapUtil.dimProduct(this.template.getDimensions());
        this.elementsize = this.basetype.getSize();
    }

    /////////////////////////////////////////////////
    // CDMArray Interface

    @Override
    public DapType
    getBaseType()
    {
        return this.basetype;
    }

    @Override
    public DSP getDSP()
    {
        return this.dsp;
    }

    @Override
    public DapVariable getTemplate()
    {
        return this.template;
    }

    //////////////////////////////////////////////////
    // Accessors

    //////////////////////////////////////////////////
    // Array Interface

    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        DapType basetype = getBaseType();
        String sbt = (basetype == null ? "?" : basetype.toString());
        String st = (template == null ? "?" : template.getShortName());
        buf.append(String.format("%s %s[%d]", sbt, st, dimsize));
        return buf.toString();
    }

    //////////////////////////////////////////////////
    // Array API
    // TODO: add index range checks

    public Class
    getElementType()
    {
        DataType dt = CDMTypeFcns.daptype2cdmtype(this.basetype);
        if(dt == null)
            throw new IllegalArgumentException("Unknown datatype: " + this.basetype);
        return CDMTypeFcns.cdmElementClass(dt);
    }

    public double getDouble(ucar.ma2.Index cdmidx)
    {
        return getDouble(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public float getFloat(ucar.ma2.Index cdmidx)
    {
        return getFloat(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public long getLong(ucar.ma2.Index cdmidx)
    {
        return getLong(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public int getInt(ucar.ma2.Index cdmidx)
    {
        return getInt(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public short getShort(ucar.ma2.Index cdmidx)
    {
        return getShort(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public byte getByte(ucar.ma2.Index cdmidx)
    {
        return getByte(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public char getChar(ucar.ma2.Index cdmidx)
    {
        return getChar(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public boolean getBoolean(ucar.ma2.Index cdmidx)
    {
        return getBoolean(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    public Object getObject(ucar.ma2.Index cdmidx)
    {
        return getObject(CDMUtil.cdmIndexToIndex(cdmidx));
    }

    // Convert int base to Index based

    public double getDouble(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getDouble(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public float getFloat(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getFloat(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public long getLong(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getLong(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public int getInt(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getInt(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public short getShort(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getShort(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public byte getByte(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getByte(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public char getChar(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getChar(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public boolean getBoolean(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getBoolean(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public Object getObject(int offset)
    {
        DapVariable d4var = (DapVariable) getTemplate();
        long[] dimsizes = DapUtil.getDimSizes(d4var.getDimensions());
        return getObject(DapUtil.offsetToIndex(offset, dimsizes));
    }

    public Object getStorage()
    {
        try {
            List<Slice> slices = DapUtil.dimsetToSlices(this.template.getDimensions());
            Object result = this.data.read(slices);
            return result;
        } catch (DapException e) {
            throw new IllegalArgumentException();
        }
    }

    // Unsupported Methods

    public void setDouble(Index ima, double value)
    {
        throw new UnsupportedOperationException();
    }

    public void setFloat(Index ima, float value)
    {
        throw new UnsupportedOperationException();
    }

    public void setLong(Index ima, long value)
    {
        throw new UnsupportedOperationException();
    }

    public void setInt(Index ima, int value)
    {
        throw new UnsupportedOperationException();
    }

    public void setShort(Index ima, short value)
    {
        throw new UnsupportedOperationException();
    }

    public void setByte(Index ima, byte value)
    {
        throw new UnsupportedOperationException();
    }

    public void setChar(Index ima, char value)
    {
        throw new UnsupportedOperationException();
    }

    public void setBoolean(Index ima, boolean value)
    {
        throw new UnsupportedOperationException();
    }

    public void setObject(Index ima, Object value)
    {
        throw new UnsupportedOperationException();
    }

    public void setDouble(int elem, double value)
    {
        throw new UnsupportedOperationException();
    }

    public void setFloat(int elem, float value)
    {
        throw new UnsupportedOperationException();
    }

    public void setLong(int elem, long value)
    {
        throw new UnsupportedOperationException();
    }

    public void setInt(int elem, int value)
    {
        throw new UnsupportedOperationException();
    }

    public void setShort(int elem, short value)
    {
        throw new UnsupportedOperationException();
    }

    public void setByte(int elem, byte value)
    {
        throw new UnsupportedOperationException();
    }

    public void setChar(int elem, char value)
    {
        throw new UnsupportedOperationException();
    }

    public void setBoolean(int elem, boolean value)
    {
        throw new UnsupportedOperationException();
    }

    public void setObject(int elem, Object value)
    {
        throw new UnsupportedOperationException();
    }

    protected void copyTo1DJavaArray(IndexIterator indexIterator, Object o)
    {
        throw new UnsupportedOperationException();
    }

    protected void copyFrom1DJavaArray(IndexIterator iter, Object javaArray)
    {
        throw new UnsupportedOperationException();
    }

    protected Array createView(Index index)
    {
        return this;
    }

    //////////////////////////////////////////////////
    // Internal common extractors

    /**
     * Get the array element at a specific dap4 index as a double
     *
     * @param idx of element to get
     * @return value at index cast to double if necessary.
     */
    protected double getDouble(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.FLOAT64, this.basetype, value);
            return (Double) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a float
     * converting as needed.
     *
     * @param idx of element to get
     * @return value at index cast to float if necessary.
     */
    protected float getFloat(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.FLOAT32, this.basetype, value);
            return (Float) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a long
     *
     * @param idx of element to get
     * @return value at index cast to long if necessary.
     */
    protected long getLong(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.INT64, this.basetype, value);
            return (Long) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a integer
     *
     * @param idx of element to get
     * @return value at index cast to integer if necessary.
     */
    protected int getInt(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.INT32, this.basetype, value);
            return (Integer) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a short
     *
     * @param idx of element to get
     * @return value at index cast to short if necessary.
     */
    protected short getShort(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.INT16, this.basetype, value);
            return (Short) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a byte
     *
     * @param idx of element to get
     * @return value at index cast to byte if necessary.
     */
    protected byte getByte(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.INT8, this.basetype, value);
            return (Byte) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a char
     *
     * @param idx of element to get
     * @return value at index cast to char if necessary.
     */
    protected char getChar(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.CHAR, this.basetype, value);
            return (Character) java.lang.reflect.Array.get(value, 0);
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as a boolean
     *
     * @param idx of element to get
     * @return value at index cast to char if necessary.
     */
    protected boolean getBoolean(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = Convert.convert(DapType.INT64, this.basetype, value);
            return ((Long) java.lang.reflect.Array.get(value, 0)) != 0;
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    /**
     * Get the array element at a specific dap4 index as an Object
     *
     * @param idx of element to get
     * @return value at index cast to Object if necessary.
     */
    protected Object getObject(dap4.core.util.Index idx)
    {
        assert data.getScheme() == Scheme.ATOMIC;
        try {
            Object value = data.read(idx);
            value = java.lang.reflect.Array.get(value, 0);
            return value;
        } catch (IOException ioe) {
            throw new IndexOutOfBoundsException(ioe.getMessage());
        }
    }

    //////////////////////////////////////////////////
    // DataAtomic Interface

    public DapVariable getVariable()
    {
        return this.template;
    }

    public DapType getType()
    {
        return this.basetype;
    }

    /*
    protected Object
    read(long index, DapType datatype, DataAtomic content)
            throws DapException
    {
        Object result;
        int i = (int) index;
        long tmp = 0;
        switch (datatype.getTypeSort()) {
        case Int8:
            result = (Byte) content.getByte(i);
            break;
        case Char:
            result = (Character) content.getChar(i);
            break;
        case SHORT:
            result = (Short) content.getShort(i);
            break;
        case INT:
            result = (Integer) content.getInt(i);
            break;
        case LONG:
            result = (Long) content.getLong(i);
            break;
        case FLOAT:
            result = (Float) content.getFloat(i);
            break;
        case DOUBLE:
            result = (Double) content.getDouble(i);
            break;
        case STRING:
            result = content.getObject(i).toString();
            break;
        case OBJECT:
            result = content.getObject(i);
            break;
        case UBYTE:
            tmp = content.getByte(i) & 0xFF;
            result = (Byte) (byte) tmp;
            break;
        case USHORT:
            tmp = content.getShort(i) & 0xFFFF;
            result = (Short) (short) tmp;
            break;
        case UINT:
            tmp = content.getInt(i) & 0xFFFFFFFF;
            result = (Integer) (int) tmp;
            break;
        case ULONG:
            result = (Long) content.getLong(i);
            break;
        case ENUM1:
            result = read(index, DataType.BYTE, content);
            break;
        case ENUM2:
            result = read(index, DataType.SHORT, content);
            break;
        case ENUM4:
            result = read(index, DataType.INT, content);
            break;
        case OPAQUE:
            result = content.getObject(i);
            break;
        case STRUCTURE:
        case SEQUENCE:
        default:
            throw new DapException("Attempt to read non-atomic value of type: " + datatype);
        }
        return result;
    }
    */

    //////////////////////////////////////////////////
    // Utilities
}


/*
 * (c) 1998-2016 University Corporation for Atmospheric Research/Unidata
 */
package ucar.nc2.iosp.gini;

import ucar.nc2.*;
import ucar.nc2.constants.*;
import ucar.nc2.units.DateFormatter;
import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.LambertConformal;
import ucar.unidata.geoloc.projection.Stereographic;
import ucar.unidata.geoloc.projection.Mercator;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.unidata.util.Parameter;

import java.io.*;
import java.util.*;
import java.util.zip.Inflater;
import java.util.zip.DataFormatException;
import java.text.*;
import java.nio.*;

/**
 * Netcdf header reading and writing for version 3 file format.
 * This is used by Giniiosp.
 */

class Giniheader {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Giniheader.class);

  static private final int GINI_PIB_LEN = 21;   // gini product identification block
  static private final int GINI_PDB_LEN = 512;  // gini product description block
  static private final int GINI_HED_LEN = GINI_PDB_LEN + GINI_PIB_LEN;  // gini product header
  static private final double DEG_TO_RAD = 0.017453292;

  private boolean debug = false;
  int dataStart = 0; // where the data starts
  protected int Z_type = 0;

  static public boolean isValidFile(ucar.unidata.io.RandomAccessFile raf) {
    try {
      return validatePIB(raf);
    } catch (IOException e) {
      return false;
    }
  }

  static private int findWMOHeader(String pib) {
    int pos = pib.indexOf("KNES");
    if (pos == -1) pos = pib.indexOf("CHIZ");

    if (pos != -1) {                    /* 'KNES' or 'CHIZ' found         */
      pos = pib.indexOf("\r\r\n");    /* ----- UPC mod 20030710 -----   */
      if (pos != -1) {                 /* CR CR NL found             */
        pos = pos + 3;
      }
    } else {
      pos = 0;
    }
    return pos;
  }

  static boolean validatePIB(ucar.unidata.io.RandomAccessFile raf) throws IOException {
    int pos = 0;
    raf.seek(pos);

    // gini header process
    String pib = raf.readString(GINI_PIB_LEN + GINI_HED_LEN);

    return findWMOHeader(pib) != 0;
  }

  byte[] readPIB(ucar.unidata.io.RandomAccessFile raf) throws IOException {
    int pos = 0;
    raf.seek(pos);

    // gini header process
    byte[] b = new byte[GINI_PIB_LEN + GINI_HED_LEN];
    byte[] buf = new byte[GINI_HED_LEN];
    byte[] head = new byte[GINI_PDB_LEN];

    raf.readFully(b);
    String pib = new String(b, CDM.utf8Charset);

    pos = findWMOHeader(pib);
    dataStart = pos + GINI_PDB_LEN;

    // Test the next two bytes to see if the image portion looks like
    // it is zlib-compressed
    byte[] b2 = new byte[] {b[pos], b[pos + 1]};
    int pos1 = 0;

    if (Giniiosp.isZlibHed(b2)) {
      Z_type = 1;
      Inflater inflater = new Inflater(false);
      inflater.setInput(b, pos, GINI_HED_LEN);
      try {
        int resultLength = inflater.inflate(buf, 0, GINI_HED_LEN);
        if (resultLength != GINI_HED_LEN) log.warn("GINI: Zlib inflated image header size error");
      } catch (DataFormatException ex) {
        log.error("ERROR on inflation " + ex.getMessage());
        ex.printStackTrace();
        throw new IOException(ex.getMessage());
      }

      int inflatedLen = GINI_HED_LEN - inflater.getRemaining();

      String inf = new String(buf, CDM.utf8Charset);
      pos1 = findWMOHeader(inf);

      System.arraycopy(buf, pos1, head, 0, GINI_PDB_LEN);
      dataStart = pos + inflatedLen;

    } else {
      System.arraycopy(b, pos, head, 0, GINI_PDB_LEN);
    }

    if (pos == 0 && pos1 == 0) {
      throw new IOException("Error on Gini File");
    }

    return head;
  }

  void read(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile) throws IOException {
    int proj;                        /* projection type indicator     */
                                            /* 1 - Mercator                  */
                                            /* 3 - Lambert Conf./Tangent Cone*/
                                            /* 5 - Polar Stereographic       */

    int ent_id;                      /* GINI creation entity          */
    int sec_id;                      /* GINI sector ID                */
    int phys_elem;                   /* 1 - Visible, 2- 3.9IR, 3 - 6.7IR ..*/
    int nx;
    int ny;
    int pole;
    int gyear;
    int gmonth;
    int gday;
    int ghour;
    int gminute;
    int gsecond;
    double lonv;                        /* meridian parallel to y-axis */
    double lon1 = 0.0, lon2 = 0.0, lat1 = 0.0, lat2 = 0.0;
    double latt;
    double imageScale = 0.0;

    byte[] head = readPIB(raf);
    ByteBuffer bos = ByteBuffer.wrap(head);

    Attribute att = new Attribute(CDM.CONVENTIONS, "GRIB");
    ncfile.addAttribute(null, att);

    bos.position(0);

    //sat_id = (int )( raf.readByte());
    Byte nv = bos.get();
    att = new Attribute("source_id", nv);
    ncfile.addAttribute(null, att);

    nv = bos.get();
    ent_id = nv.intValue();
    att = new Attribute("entity_id", nv);
    ncfile.addAttribute(null, att);

    nv = bos.get();
    sec_id = nv.intValue();
    att = new Attribute("sector_id", nv);
    ncfile.addAttribute(null, att);

    nv = bos.get();
    phys_elem = nv.intValue();
    att = new Attribute("phys_elem", nv);
    ncfile.addAttribute(null, att);

    bos.position(bos.position() + 4);

    gyear = (int) (bos.get());
    gyear += (gyear < 50) ? 2000 : 1900; //TODO: Find example where this hack is necessary
    gmonth = (int) (bos.get());
    gday = (int) (bos.get());
    ghour = (int) (bos.get());
    gminute = (int) (bos.get());
    gsecond = (int) (bos.get());

    DateFormat dformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    dformat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
    Calendar cal = Calendar.getInstance();

    cal.set(Calendar.MILLISECOND, 0);

    cal.set(gyear, gmonth - 1, gday, ghour, gminute, gsecond);
    cal.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
    String dstring = dformat.format(cal.getTime());

    Dimension dimT = new Dimension("time", 1, true, false, false);
    ncfile.addDimension(null, dimT);

    String timeCoordName = "time";
    Variable taxis = new Variable(ncfile, null, null, timeCoordName);
    taxis.setDataType(DataType.DOUBLE);
    taxis.setDimensions("time");
    taxis.addAttribute(new Attribute(CDM.LONG_NAME, "time since base date"));
    taxis.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));
    double[] tdata = new double[1];
    tdata[0] = cal.getTimeInMillis();
    Array dataA = Array.factory( DataType.DOUBLE, new int[]{1}, tdata);
    taxis.setCachedData(dataA, false);
    DateFormatter formatter = new DateFormatter();
    taxis.addAttribute(new Attribute(CDM.UNITS, "msecs since " + formatter.toDateTimeStringISO(new Date(0))));
    ncfile.addVariable(null, taxis);

    //att = new Attribute( "Time", dstring);
    //this.ncfile.addAttribute(null, att);
    ncfile.addAttribute(null, new Attribute("time_coverage_start", dstring));
    ncfile.addAttribute(null, new Attribute("time_coverage_end", dstring));
    bos.get();   /* skip a byte for hundreds of seconds */

    nv = bos.get();
    proj = nv.intValue();

    /*
    ** Get grid dimensions
    */

    nx = bos.getShort();
    att = new Attribute("NX", nx);
    ncfile.addAttribute(null, att);

    ny = bos.getShort();
    att = new Attribute("NY", ny);
    ncfile.addAttribute(null, att);

    ProjectionImpl projection = null;
    double dxKm = 0.0, dyKm = 0.0, latin, lonProjectionOrigin;

    switch (proj) {

      case 1:                                                    /* Mercator */
        /*
        ** Get the latitude and longitude of first and last "grid" points
        */

        /* Latitude of first grid point */
        lat1 = readScaledInt(bos);
        att = new Attribute("Latitude0", lat1);
        ncfile.addAttribute(null, att);

        lon1 = readScaledInt(bos);
        att = new Attribute("Longitude0", lon1);
        ncfile.addAttribute(null, att);

        /* Longitude of last grid point */
        bos.get(); /* skip one byte */

        lat2 = readScaledInt(bos);
        att = new Attribute("LatitudeN", lat2);
        ncfile.addAttribute(null, att);

        lon2 = readScaledInt(bos);
        att = new Attribute("LongitudeN", lon2);
        ncfile.addAttribute(null, att);

        /*
        ** Hack to catch incorrect sign of lon2 in header.
        */

        //    if ( lon1 > 0.0 && lon2 < 0.0 ) lon2 *= -1;
        double lon_1 = lon1;
        double lon_2 = lon2;
        if (lon1 < 0) lon_1 += 360.0;
        if (lon2 < 0) lon_2 += 360.0;

        lonv = (lon_1 + lon_2) / 2.0;

        if (lonv > 180.0) lonv -= 360.0;
        if (lonv < -180.0) lonv += 360.0;
        /*
        ** Get the "Latin" parameter.  The ICD describes this value as:
        ** "Latin - The latitude(s) at which the Mercator projection cylinder
        ** intersects the earth."  It should read that this is the latitude
        ** at which the image resolution is that defined by octet 41.
        */
        bos.getInt(); /* skip 4 bytes */
        bos.get();    /* skip 1 byte */

        /* Latitude of proj cylinder intersects */
        latin = readScaledInt(bos);
        att = new Attribute("LatitudeX", latin);
        ncfile.addAttribute(null, att);

        projection = new Mercator(lonv, latin);
        break;

      case 3:                               /* Lambert Conformal             */
      case 5:                               /* Polar Stereographic           */
        /*
        ** Get lat/lon of first grid point
        */
        lat1 = readScaledInt(bos);
        lon1 = readScaledInt(bos);
        /*
        ** Get Lov - the orientation of the grid; i.e. the east longitude of
        ** the meridian which is parallel to the y-aixs
        */
        bos.get(); /* skip one byte */

        lonv = readScaledInt(bos);
        lonProjectionOrigin = lonv;
        att = new Attribute("Lov", lonv);
        ncfile.addAttribute(null, att);

        /*
        ** Get distance increment of grid
        */
        dxKm = readScaledInt(bos);
        att = new Attribute("DxKm", dxKm);
        ncfile.addAttribute(null, att);

        dyKm = readScaledInt(bos);
        att = new Attribute("DyKm", dyKm);
        ncfile.addAttribute(null, att);

        /* calculate the lat2 and lon2 */
        if (proj == 5) {
          latt = 60.0;            /* Fixed for polar stereographic */
          imageScale = (1. + Math.sin(DEG_TO_RAD * latt)) / 2.;
        }

        lat2 = lat1 + dyKm * (ny - 1) / 111.26;

        /* Convert to east longitude */
        if (lonv < 0.) lonv += 360.;
        if (lon1 < 0.) lon1 += 360.;

        lon2 = lon1 + dxKm * (nx - 1) / 111.26 * Math.cos(DEG_TO_RAD * lat1);

        /*
        ** Convert to normal longitude to McIDAS convention
        */
        lonv = (lonv > 180.) ? -(360. - lonv) : lonv;
        lon1 = (lon1 > 180.) ? -(360. - lon1) : lon1;
        lon2 = (lon2 > 180.) ? -(360. - lon2) : lon2;
        /*
        ** Check high bit of octet for North or South projection center
        */
        nv = bos.get();
        pole = nv.intValue();
        pole = (pole > 127) ? -1 : 1;
        att = new Attribute("ProjCenter", pole);
        ncfile.addAttribute(null, att);

        bos.get(); /* skip one byte for Scanning mode */

        latin = readScaledInt(bos);
        att = new Attribute("Latin", latin);
        ncfile.addAttribute(null, att);

        if (proj == 3)
          projection = new LambertConformal(latin, lonProjectionOrigin, latin, latin);
        else // (proj == 5)
          projection = new Stereographic(90.0, lonv, imageScale);

        break;

      default:
        log.warn("GINI: Unimplemented projection!");
    }

    ncfile.addAttribute(null, new Attribute("title", gini_GetEntityID(ent_id)));
    ncfile.addAttribute(null, new Attribute("summary", getPhysElemSummary(phys_elem, ent_id)));
    ncfile.addAttribute(null, new Attribute("id", gini_GetSectorID(sec_id)));
    ncfile.addAttribute(null, new Attribute("keywords_vocabulary", gini_GetPhysElemID(phys_elem, ent_id)));
    ncfile.addAttribute(null, new Attribute("cdm_data_type", FeatureType.GRID.toString()));
    ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, FeatureType.GRID.toString()));
    ncfile.addAttribute(null, new Attribute("standard_name_vocabulary", getPhysElemLongName(phys_elem, ent_id)));
    ncfile.addAttribute(null, new Attribute("creator_name", "UNIDATA"));
    ncfile.addAttribute(null, new Attribute("creator_url", "http://www.unidata.ucar.edu/"));
    ncfile.addAttribute(null, new Attribute("naming_authority", "UCAR/UCP"));
    ncfile.addAttribute(null, new Attribute("geospatial_lat_min", lat1));
    ncfile.addAttribute(null, new Attribute("geospatial_lat_max", lat2));
    ncfile.addAttribute(null, new Attribute("geospatial_lon_min", lon1));
    ncfile.addAttribute(null, new Attribute("geospatial_lon_max", lon2));
    //this.ncfile.addAttribute(null, new Attribute("geospatial_vertical_min", new Float(0.0)));
    //this.ncfile.addAttribute(null, new Attribute("geospatial_vertical_max", new Float(0.0)));

    /*
     * Get the image resolution.
     */
    bos.position(41);  /* jump to 42 bytes of PDB */
    nv = bos.get();      /* Res [km] */
    att = new Attribute("imageResolution", nv);
    ncfile.addAttribute(null, att);
    // if(proj == 1)
    //     dyKm = nv.doubleValue()/dyKm;
    /* compression flag 43 byte */

    nv = bos.get();      /* Res [km] */
    att = new Attribute("compressionFlag", nv);
    ncfile.addAttribute(null, att);

    if (DataType.unsignedByteToShort(nv) == 128) {
      Z_type = 2;
      //out.println( "ReadNexrInfo:: This is a Z file ");
    }

   /* new 47 - 60 */
    bos.position(46);
    nv = bos.get();      /* Cal indicator */
    int navcal = DataType.unsignedByteToShort(nv);
    int[] calcods = null;
    if (navcal == 128)
      calcods = getCalibrationInfo(bos, phys_elem, ent_id);

    // only one data variable per gini file

    String vname = gini_GetPhysElemID(phys_elem, ent_id);
    Variable var = new Variable(ncfile, ncfile.getRootGroup(), null, vname);
    var.addAttribute(new Attribute(CDM.LONG_NAME, getPhysElemLongName(phys_elem, ent_id)));
    var.addAttribute(new Attribute(CDM.UNITS, getPhysElemUnits(phys_elem, ent_id)));
    // var.addAttribute( new Attribute(CDM.MISSING_VALUE, new Byte((byte) 0))); // ??

    // get dimensions
    List<Dimension> dims = new ArrayList<>();

    Dimension dimX = new Dimension("x", nx, true, false, false);
    Dimension dimY = new Dimension("y", ny, true, false, false);

    ncfile.addDimension(null, dimY);
    ncfile.addDimension(null, dimX);

    dims.add(dimT);
    dims.add(dimY);
    dims.add(dimX);

    var.setDimensions(dims);

    // size and beginning data position in file
    long begin = dataStart;
    if (debug) log.warn(" name= " + vname + " velems=" + var.getSize() + " begin= " + begin + "\n");
    if (navcal == 128) {
      var.setDataType(DataType.FLOAT);
      var.setSPobject(new Vinfo(begin, nx, ny, Z_type, calcods));
     /*   var.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
        int numer = calcods[0] - calcods[1];
        int denom = calcods[2] - calcods[3];
        float a  = (numer*1.f) / (1.f*denom);
        float b  = calcods[0] - a * calcods[2];
        var.addAttribute( new Attribute("scale_factor", new Float(a)));
        var.addAttribute( new Attribute("add_offset", new Float(b)));
      */
    } else {
      var.setDataType(DataType.BYTE);
      var.addAttribute(new Attribute(CDM.UNSIGNED, "true"));
      // var.addAttribute(new Attribute("_missing_value", new Short((short)255)));
      var.addAttribute(new Attribute(CDM.SCALE_FACTOR, (short) (1)));
      var.addAttribute(new Attribute(CDM.ADD_OFFSET, (short) (0)));
      var.setSPobject(new Vinfo(begin, nx, ny, Z_type));
    }
    String coordinates = "x y time";
    var.addAttribute(new Attribute(_Coordinate.Axes, coordinates));
    ncfile.addVariable(null, var);

    // add coordinate information. we need:
    // nx, ny, dx, dy,
    // latin, lov, la1, lo1

    // we have to project in order to find the origin
    ProjectionPoint start;
    if (projection != null)
      start = projection.latLonToProj(new LatLonPointImpl(lat1, lon1));
    else
      start = new ProjectionPointImpl();
    if (debug) log.warn("start at proj coord " + start);

    double startx = start.getX();
    double starty = start.getY();

    // create coordinate variables
    Variable xaxis = new Variable(ncfile, null, null, "x");
    xaxis.setDataType(DataType.DOUBLE);
    xaxis.setDimensions("x");
    xaxis.addAttribute(new Attribute(CDM.LONG_NAME, "projection x coordinate"));
    xaxis.addAttribute(new Attribute(CDM.UNITS, "km"));
    xaxis.addAttribute(new Attribute(_Coordinate.AxisType, "GeoX"));
    double[] data = new double[nx];
    if (proj == 1) {
      double lon_1 = lon1;
      double lon_2 = lon2;
      if (lon1 < 0) lon_1 += 360.0;
      if (lon2 < 0) lon_2 += 360.0;
      double dx = (lon_2 - lon_1) / (nx - 1);

      for (int i = 0; i < data.length; i++) {
        double ln = lon1 + i * dx;
        ProjectionPoint pt = projection.latLonToProj(new LatLonPointImpl(lat1, ln));
        data[i] = pt.getX();  // startx + i*dx;
      }
    } else {
      for (int i = 0; i < data.length; i++)
        data[i] = startx + i * dxKm;
    }

    dataA = Array.factory(DataType.DOUBLE, new int[]{nx}, data);
    xaxis.setCachedData(dataA, false);
    ncfile.addVariable(null, xaxis);

    Variable yaxis = new Variable(ncfile, null, null, "y");
    yaxis.setDataType(DataType.DOUBLE);
    yaxis.setDimensions("y");
    yaxis.addAttribute(new Attribute(CDM.LONG_NAME, "projection y coordinate"));
    yaxis.addAttribute(new Attribute(CDM.UNITS, "km"));
    yaxis.addAttribute(new Attribute(_Coordinate.AxisType, "GeoY"));
    data = new double[ny];
    double endy = starty + dyKm * (data.length - 1); // apparently lat1,lon1 is always the lower ledt, but data is upper left
    if (proj == 1) {
      double dy = (lat2 - lat1) / (ny - 1);
      for (int i = 0; i < data.length; i++) {
        double la = lat2 - i * dy;
        ProjectionPoint pt = projection.latLonToProj(new LatLonPointImpl(la, lon1));
        data[i] = pt.getY();  //endyy - i*dy;
      }
    } else {
      for (int i = 0; i < data.length; i++)
        data[i] = endy - i * dyKm;
    }
    dataA = Array.factory(DataType.DOUBLE, new int[]{ny}, data);
    yaxis.setCachedData(dataA, false);
    ncfile.addVariable(null, yaxis);

    // coordinate transform variable
    String shortName = (projection == null) ? "unknown" : projection.getClassName();
    Variable ct = new Variable(ncfile, null, null, shortName);
    ct.setDataType(DataType.CHAR);
    ct.setDimensions("");
    for (Parameter p : projection.getProjectionParameters()) {
      ct.addAttribute(new Attribute(p));
    }
    ct.addAttribute(new Attribute(_Coordinate.TransformType, "Projection"));
    ct.addAttribute(new Attribute(_Coordinate.Axes, "x y "));
    // fake data
    dataA = Array.factory(DataType.CHAR, new int[]{});
    dataA.setChar(dataA.getIndex(), ' ');
    ct.setCachedData(dataA, false);

    ncfile.addVariable(null, ct);
    ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, _Coordinate.Convention));

    var.addAttribute(new Attribute(CF.GRID_MAPPING, ct.getFullName()));

    // finish
    ncfile.finish();
  }

  int[] getCalibrationInfo(ByteBuffer bos, int phys_elem, int ent_id) {

    bos.position(46);
    byte nv = bos.get();      /* Cal indicator */
    int navcal = DataType.unsignedByteToShort(nv);
    int[] calcods = null;
    if (navcal == 128) {    /* Unidata Cal block found; unpack values */
      int scale = 10000;
      int jscale = 100000000;
      byte[] unsb = new byte[8];
      bos.get(unsb);
      bos.position(55);
      nv = bos.get();
      int calcod = DataType.unsignedByteToShort(nv);

      if (calcod > 0) {
        calcods = new int[5 * calcod + 1];
        calcods[0] = calcod;
        for (int i = 0; i < calcod; i++) {

          bos.position(56 + i * 16);
          int minb = bos.getInt() / 10000;        /* min brightness values         */
          int maxb = bos.getInt() / 10000;       /* max brightness values         */
          int mind = bos.getInt();               /* min data values               */
          int maxd = bos.getInt();               /* max data values               */

          int idscal = 1;
          while (mind % idscal == 0 && maxd % idscal == 0) {
            idscal *= 10;
          }
          idscal /= 10;

          if (idscal < jscale) jscale = idscal;

          calcods[1 + i * 5] = mind;
          calcods[2 + i * 5] = maxd;
          calcods[3 + i * 5] = minb;
          calcods[4 + i * 5] = maxb;
          calcods[5 + i * 5] = 0;

        }

        if (jscale > scale) jscale = scale;
        scale /= jscale;

        if (gini_GetPhysElemID(phys_elem, ent_id).contains("Precipitation")) {
          if (scale < 100) {
            jscale /= (100 / scale);
            scale = 100;
          }
        }

        for (int i = 0; i < calcod; i++) {
          calcods[1 + i * 5] /= jscale;
          calcods[2 + i * 5] /= jscale;
          calcods[5 + i * 5] = scale;
        }

      }

    }

    return calcods;
  }

  // Return the string of Sector for the GINI image file

  String gini_GetSectorID(int ent_id) {
    String name;
    switch (ent_id) {
      case 0:
        name = "Northern Hemisphere Composite";
        break;
      case 1:
        name = "East CONUS";
        break;
      case 2:
        name = "West CONUS";
        break;
      case 3:
        name = "Alaska Regional";
        break;
      case 4:
        name = "Alaska National";
        break;
      case 5:
        name = "Hawaii Regional";
        break;
      case 6:
        name = "Hawaii National";
        break;
      case 7:
        name = "Puerto Rico Regional";
        break;
      case 8:
        name = "Puerto Rico National";
        break;
      case 9:
        name = "Supernational";
        break;
      case 10:
        name = "NH Composite - Meteosat/GOES E/ GOES W/GMS";
        break;
      case 11:
        name = "Central CONUS";
        break;
      case 12:
        name = "East Floater";
        break;
      case 13:
        name = "West Floater";
        break;
      case 14:
        name = "Central Floater";
        break;
      case 15:
        name = "Polar Floater";
        break;
      default:
        name = "Unknown-ID";
    }

    return name;
  }

  // Return the channel ID for the GINI image file
  String gini_GetEntityID(int ent_id) {
    String name;
    switch (ent_id) {
      case 2:
        name = "Miscellaneous";
        break;
      case 3:
        name = "JERS";
        break;
      case 4:
        name ="ERS/QuikSCAT/Scatterometer";
        break;
      case 5:
        name = "POES/NPOESS";
        break;
      case 6:
        name = "Composite";
        break;
      case 7:
        name = "DMSP satellite Image";
        break;
      case 8:
        name = "GMS satellite Image";
        break;
      case 9:
        name = "METEOSAT satellite Image";
        break;
      case 10:
        name = "GOES-7 satellite Image";
        break;
      case 11:
        name = "GOES-8 satellite Image";
        break;
      case 12:
        name = "GOES-9 satellite Image";
        break;
      case 13:
        name = "GOES-10 satellite Image";
        break;
      case 14:
        name = "GOES-11 satellite Image";
        break;
      case 15:
        name = "GOES-12 satellite Image";
        break;
      case 16:
        name = "GOES-13 satellite Image";
        break;
      case 17:
        name = "GOES-14 satellite Image";
        break;
      case 18:
        name = "GOES-15 satellite Image";
        break;
      case 19: // GOES-R
        name = "GOES-16 satellite Image";
        break;
      case 99: // special snowflake GEMPAK Composite Images generated by Unidata
        name = "RADAR-MOSIAC Composite Image";
        break;
      default:
        name = "Unknown";
    }

    return name;
  }

  // Return the channel ID for the GINI image file
  String gini_GetPhysElemID(int phys_elem, int ent_id) {
    String name;
    switch (phys_elem) {
      case 1:
        name = "VIS";
        break;
      case 3:
        name = "IR_WV";
        break;
      case 2:
      case 4:
      case 5:
      case 6:
      case 7:
        name = "IR";
        break;
      case 13:
        name = "LI";
        break;
      case 14:
        name = "PW";
        break;
      case 15:
        name = "SFC_T";
        break;
      case 16:
        name = "LI";
        break;
      case 17:
        name = "PW";
        break;
      case 18:
        name = "SFC_T";
        break;
      case 19:
        name = "CAPE";
        break;
      case 20:
        name = "T";
        break;
      case 21:
        name = "WINDEX";
        break;
      case 22:
        name = "DMPI";
        break;
      case 23:
        name = "MDPI";
        break;
      case 25:
        if (ent_id == 99)
          name = "HHC";
        else
          name = "Volcano_imagery";
        break;
      case 26:
        name = "EchoTops";
        break;
      case 27:
        if (ent_id == 99)
          name = "Reflectivity";
        else
          name = "CTP";
        break;
      case 28:
        if (ent_id == 99)
          name = "Reflectivity";
        else
          name = "Cloud_Amount";
        break;
      case 29:
        name = "VIL";
        break;
      case 30:
      case 31:
        name = "Precipitation";
        break;
      case 40:
      case 41:
      case 42:
      case 43:
      case 44:
      case 45:
      case 46:
      case 47:
      case 48:
      case 49:
      case 50:
      case 51:
      case 52:
      case 53:
      case 54:
      case 55:
      case 56:
      case 57:
      case 58:
        name = "sounder_imagery";
        break;
      case 59:
        name = "VIS_sounder";
        break;
      default:
        name = "Unknown";
    }

    return name;
  }

  // ??
  String getPhysElemUnits(int phys_elem, int ent_id) {
    switch (phys_elem) {
      case 1:
      case 3:
      case 2:
      case 4:
      case 5:
      case 6:
      case 7:
      case 13:
      case 14:
      case 15:
      case 16:
      case 17:
      case 18:
      case 19:
      case 20:
      case 21:
      case 22:
      case 23:
      case 25:
      case 43:
      case 48:
      case 50:
      case 51:
      case 52:
      case 55:
      case 57:
      case 59:
        return "N/A";
      case 26:
        return "kft";
      case 27:
        if (ent_id == 99)
          return "dBz";
        else
          return "N/A";
      case 28:
        if (ent_id == 99)
          return "dBz";
        else
          return "N/A";
      case 29:
        return "kg m-2";
      case 30:
        return "IN";
      case 31:
        return "IN";
      default:
        return "Unknown";
    }
  }

  // Return the channel ID for the GINI image file

  String getPhysElemLongName(int phys_elem, int ent_id) {
    switch (phys_elem) {
      case 1:
        return "Imager Visible";
      case 2:
        return "Imager 3.9 micron IR";
      case 3:
        return "Imager 6.7/6.5 micron IR (WV)";
      case 4:
        return "Imager 11 micron IR";
      case 5:
        return "Imager 12 micron IR";
      case 6:
        return "Imager 13 micron IR";
      case 7:
        return "Imager 1.3 micron IR";
      case 13:
        return "Lifted Index LI";
      case 14:
        return "Precipitable Water PW";
      case 15:
        return "Surface Skin Temperature";
      case 16:
        return "Lifted Index LI";
      case 17:
        return "Precipitable Water PW";
      case 18:
        return "Surface Skin Temperature";
      case 19:
        return "Convective Available Potential Energy";
      case 20:
        return "land-sea Temperature";
      case 21:
        return "Wind Index";
      case 22:
        return "Dry Microburst Potential Index";
      case 23:
        return "Microburst Potential Index";
      case 24:
        return "Derived Convective Inhibition";
      case 25:
        if (ent_id == 99)
          return "1km National Hybrid Hydrometeor Classification Composite (Unidata)";
        else
          return "Volcano_imagery";
      case 26:
        if (ent_id == 99)
          return "1 km National Echo Tops Composite (Unidata)";
        else
          return "4 km National Echo Tops";
      case 27:
        if (ent_id == 99)
          return "1 km National Base Reflectivity Composite (Unidata)";
        else
          return "Cloud Top Pressure or Height";
      case 28:
        if (ent_id == 99)
          return "1 km National Reflectivity Composite (Unidata)";
        else
          return "Cloud Amount";
      case 29:
        if (ent_id == 99)
          return "1 km National Vertically Integrated Liquid Water (Unidata)";
        else
          return "4 km National Vertically Integrated Liquid Water";
      case 30:
        if (ent_id == 99)
          return "1 km National 1-hour Precipitation (Unidata)";
        else
          return "Surface wind speeds over oceans and Great Lakes";
      case 31:
        if (ent_id == 99)
          return "4 km National Storm Total Precipitation (Unidata)";
        else
          return "Surface Wetness";
      case 32:
        return "Ice concentrations";
      case 33:
        return "Ice type";
      case 34:
        return "Ice edge";
      case 35:
        return "Cloud water content";
      case 36:
        return "Surface type";
      case 37:
        return "Snow indicator";
      case 38:
        return "Snow/water content";
      case 39:
        return "Derived volcano imagery";
      case 41:
        return "Sounder 14.71 micron imagery";
      case 42:
        return "Sounder 14.37 micron imagery";
      case 43:
        return "Sounder 14.06 micron imagery";
      case 44:
        return "Sounder 13.64 micron imagery";
      case 45:
        return "Sounder 13.37 micron imagery";
      case 46:
        return "Sounder 12.66 micron imagery";
      case 47:
        return "Sounder 12.02 micron imagery";
      case 48:
        return "11.03 micron sounder image";
      case 49:
        return "Sounder 11.03 micron imagery";
      case 50:
        return "7.43 micron sounder image";
      case 51:
        return "7.02 micron sounder image";
      case 52:
        return "6.51 micron sounder image";
      case 53:
        return "Sounder 4.57 micron imagery";
      case 54:
        return "Sounder 4.52 micron imagery";
      case 55:
        return "4.45 micron sounder image";
      case 56:
        return "Sounder 4.13 micron imagery";
      case 57:
        return "3.98 micron sounder image";
      case 58:
        return "Sounder 3.74 micron imagery";
      case 59:
        return "VIS sounder image ";
      default:
        return "unknown physical element " + phys_elem;
    }
  }

  String getPhysElemSummary(int phys_elem, int ent_id) {
    switch (phys_elem) {
      case 1:
        return "Satellite Product Imager Visible";
      case 2:
        return "Satellite Product Imager 3.9 micron IR";
      case 3:
        return "Satellite Product Imager 6.7/6.5 micron IR (WV)";
      case 4:
        return "Satellite Product Imager 11 micron IR";
      case 5:
        return "Satellite Product Imager 12 micron IR";
      case 6:
        return "Satellite Product Imager 13 micron IR";
      case 7:
        return "Satellite Product Imager 1.3 micron IR";
      case 13:
        return "Imager Based Derived Lifted Index LI";
      case 14:
        return "Imager Based Derived Precipitable Water PW";
      case 15:
        return "Imager Based Derived Surface Skin Temperature";
      case 16:
        return "Sounder Based Derived Lifted Index LI";
      case 17:
        return "Sounder Based Derived Precipitable Water PW";
      case 18:
        return "Sounder Based Derived Surface Skin Temperature";
      case 19:
        return "Derived Convective Available Potential Energy CAPE";
      case 20:
        return "Derived land-sea Temperature";
      case 21:
        return "Derived Wind Index WINDEX";
      case 22:
        return "Derived Dry Microburst Potential Index DMPI";
      case 23:
        return "Derived Microburst Day Potential Index MDPI";
      case 43:
        return "Satellite Product 14.06 micron sounder image";
      case 48:
        return "Satellite Product 11.03 micron sounder image";
      case 50:
        return "Satellite Product 7.43 micron sounder image";
      case 51:
        return "Satellite Product 7.02 micron sounder image";
      case 52:
        return "Satellite Product 6.51 micron sounder image";
      case 55:
        return "Satellite Product 4.45 micron sounder image";
      case 57:
        return "Satellite Product 3.98 micron sounder image";
      case 59:
        return "Satellite Product VIS sounder visible image ";
      case 25:
        if (ent_id == 99)
          return "National Hybrid Hydrometeor Classification Composite at Resolution 1 km";
        else
          return "Satellite Derived Volcano_imagery";
      case 26:
        if (ent_id == 99)
          return "Nexrad Level 3 National Echo Tops at Resolution 1 km";
        else
          return "Nexrad Level 3 National Echo Tops at Resolution 4 km";
      case 27:
        if (ent_id == 99)
          return "Nexrad Level 3 Base Reflectivity National Composition at Resolution 1 km";
        else
          return "Gridded Cloud Top Pressure or Height";
      case 28:
        if (ent_id == 99)
          return "Nexrad Level 3 National 248 nm Base Composite Reflectivity at Resolution 2 km";
        else
          return "Gridded Cloud Amount";
      case 29:
        if (ent_id == 99)
          return "Nexrad Level 3 National Vertically Integrated Liquid Water at Resolution 1 km";
        else
          return "Nexrad Level 3 National Vertically Integrated Liquid Water at Resolution 4 km";
      case 30:
        return "Nexrad Level 3 1 Hour Precipitation National Composition at Resolution 2 km";
      case 31:
        return "Nexrad Level 3 Storm Total Precipitation National Composition at Resolution 4 km";
      default:
        return "unknown";
    }
  }

  // Read a scaled, 3-byte integer from file and convert to double
  private double readScaledInt(ByteBuffer buf) {
    // Get the first two bytes
    short s1 = buf.getShort();

    // And the last one as unsigned
    short s2 = DataType.unsignedByteToShort(buf.get());

    // Get the sign bit, converting from 0 or 2 to +/- 1.
    int posneg = 1 - ((s1 & 0x8000) >> 14);

    // Combine the first two bytes (without sign bit) with the last byte.
    // Multiply by proper factor for +/-
    int nn = (((s1 & 0x7FFF) << 8) | s2) * posneg;
    return (double) nn / 10000.0;
  }

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

  // variable info for reading/writing
  static class Vinfo {
    long begin; // offset of start of data from start of file
    int nx;
    int ny;
    int[] levels;
    int compression;

    Vinfo(long begin, int x, int y, int compression) {
      this.begin = begin;
      this.nx = x;
      this.ny = y;
      this.compression = compression;
    }

    Vinfo(long begin, int x, int y, int compression, int[] levels) {
      this.begin = begin;
      this.nx = x;
      this.ny = y;
      this.compression = compression;
      this.levels = levels;
    }

  }

}


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

import ucar.unidata.util.Parameter;

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

/**
 * A CoordinateTransform is an abstraction of a function from a CoordinateSystem to a
 * "reference" CoordinateSystem, such as lat, lon.
 *
 * @author caron
 */

@ThreadSafe
public class CoordinateTransform implements Comparable<CoordinateTransform> {

  protected final String name, authority;
  protected final TransformType transformType;

  // immutable once these are done adding
  protected List<Parameter> params;

  /**
   * Create a Coordinate Transform.
   *
   * @param name          name of transform, must be unique within the NcML.
   * @param authority     naming authority
   * @param transformType type of transform.
   */
  public CoordinateTransform(String name, String authority, TransformType transformType) {
    this.name = name;
    this.authority = authority;
    this.transformType = transformType;
    this.params = new ArrayList<>();
  }

  /**
   * add a parameter
   * @param param add this Parameter
   */
  public void addParameter(Parameter param) {
    params.add(param);
  }

  /**
   * get the name
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * get the naming authority
   * @return the naming authority
   */
  public String getAuthority() {
    return authority;
  }

  /**
   * get the transform type
   * @return the transform type
   */
  public TransformType getTransformType() {
    return transformType;
  }

  /**
   * get list of ProjectionParameter objects.
   * @return list of ProjectionParameter objects.
   */
  public List<Parameter> getParameters() {
    return params;
  }

  /**
   * Convenience function; look up Parameter by name, ignoring case.
   *
   * @param name the name of the attribute
   * @return the Attribute, or null if not found
   */
  public Parameter findParameterIgnoreCase(String name) {
    for (Parameter a : params) {
      if (name.equalsIgnoreCase(a.getName()))
        return a;
    }
    return null;
  }

  /**
   * Instances which have same name, authority and parameters are equal.
   */
  public boolean equals(Object oo) {
    if (this == oo) return true;
    if (!(oo instanceof CoordinateTransform)) return false;

    CoordinateTransform o = (CoordinateTransform) oo;
    if (!getName().equals(o.getName())) return false;
    if (!getAuthority().equals(o.getAuthority())) return false;
    if (!(getTransformType() == o.getTransformType())) return false;

    List<Parameter> oparams = o.getParameters();
    if (params.size() != oparams.size()) return false;

    for (int i = 0; i < params.size(); i++) {
      Parameter att =  params.get(i);
      Parameter oatt = oparams.get(i);
      if (!att.getName().equals(oatt.getName())) return false;
      //if (!att.getValue().equals(oatt.getValue())) return false;
    }
    return true;
  }

  /**
   * Override Object.hashCode() to be consistent with equals.
   */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      result = 37 * result + getName().hashCode();
      result = 37 * result + getAuthority().hashCode();
      result = 37 * result + getTransformType().hashCode();
      for (Parameter att : params) {
        result = 37 * result + att.getName().hashCode();
        //result = 37*result + att.getValue().hashCode();
      }
      hashCode = result;
    }
    return hashCode;
  }

  private volatile int hashCode = 0;

  public String toString() {
    return name;
  }

  @Override
  public int compareTo(CoordinateTransform oct) {
    return name.compareTo(oct.getName());
  }

}


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

package dap4.core.util;

import dap4.core.dmr.DapDimension;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * An odometer capable of dealing with a Multi-slices.
 */

public class MultiOdometer extends Odometer
{

    static protected boolean DEBUG = false;

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

    protected int current; // pointers into multslice list
    protected long[] sizes; // # of subslices in each slice
    protected List<List<Slice>> slicesets; // Set of all combinations of slices
    protected List<Odometer> odomset;  // Odometers created from slicesets

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

    public MultiOdometer()
    {
    }

    public MultiOdometer(List<Slice> set)
            throws DapException
    {
        this(set, null);
    }

    public MultiOdometer(List<Slice> set, List<DapDimension> dimset)
            throws DapException
    {
        super(set, dimset);
        super.ismulti = true;
        this.sizes = new long[this.rank];
        this.odomset = new ArrayList<>();
        for(int i = 0; i < this.rank; i++) {
            Slice sl = set.get(i);
            List<Slice> subslices = sl.getSubSlices();
            this.sizes[i] = subslices.size();
        }
        int truerank = this.rank;
        if(truerank == 0) {
            this.slicesets = null;
            this.odomset = null;
        } else {
            PowerSet ps = new PowerSet(this.sizes);
            long pssize = ps.getTotalSize();
            long[][] setindices = ps.getPowerSet();
            assert setindices.length == pssize;
            this.slicesets = new ArrayList<>();
            if(DEBUG) {
                System.err.printf("Multi: |slicesets| = %d%n", setindices.length);
                System.err.println(ps.toString());
            }
            // Create set of slicsets comprising this MultiOdometer
            for(int i = 0; i < pssize; i++) {
                long[] indexset = setindices[i];
                assert indexset.length == truerank;
                // Pick out the desired set of slices
                List<Slice> subset = new ArrayList<>();
                for(int j=0;j<this.rank;j++) {
                    Slice s0 = set.get(j);
                    Slice ss = s0.getSubSlice((int) indexset[j]);
                    subset.add(ss);
                }
                this.slicesets.add(subset);
            }
            assert this.slicesets.size() == pssize;
            // Create set of odometers comprising this MultiOdometer
            for(int i = 0; i < pssize; i++) {
                Odometer sslodom = Odometer.factory(this.slicesets.get(i),dimset);
                this.odomset.add(sslodom);
            }
        }
        this.current = 0;
    }

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

    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        for(int i = 0; i < rank; i++) {
            Slice s = slice(i);
            if(i == current)
                buf.append("*");
            buf.append(s.toString());
            buf.append(String.format("(%d)", s.getCount()));
        }
        return buf.toString();
    }

    //////////////////////////////////////////////////
    // Iterator API Overrides

    @Override
    public boolean
    hasNext()
    {
        if(this.current >= odomset.size())
            return false;
        Odometer ocurrent = odomset.get(this.current);
        if(ocurrent.hasNext())
            return true;
        // Try to move to next odometer
        this.current++;
        return hasNext();
    }

    @Override
    public Index
    next()
    {
        if(this.current >= odomset.size())
            throw new NoSuchElementException();
        Odometer ocurrent = odomset.get(this.current);
        assert ocurrent.hasNext();
        return ocurrent.next();
    }

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

    /**
     * Compute the total number of elements.
     */
    @Override
    public long
    totalSize()
    {
        long size = 1;
        for(int i = 0; i < this.rank; i++) {
            size *= slice(i).getCount();
        }
        return size;
    }

    @Override
    public List<Odometer>
    getSubOdometers()
    {
        return this.odomset;
    }

}


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

package ucar.nc2.iosp.grads;

import ucar.nc2.constants.CDM;
import ucar.unidata.io.KMPMatch;
import ucar.unidata.io.RandomAccessFile;

import java.io.*;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Class to hold information from a GrADS Data Descriptor File
 *
 * @author Don Murray - CU/CIRES
 * @see "http://www.iges.org/grads/gadoc/descriptorfile.html"
 */
public class GradsDataDescriptorFile {
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GradsDataDescriptorFile.class);

  /**
   * DSET identifier
   */
  public static final String DSET = "DSET";

  /**
   * DTYPE identifier
   */
  public static final String DTYPE = "DTYPE";

  /**
   * INDEX identifier
   */
  public static final String INDEX = "INDEX";

  /**
   * TITLE identifier
   */
  public static final String TITLE = "TITLE";

  /**
   * UNDEF identifier
   */
  public static final String UNDEF = "UNDEF";

  /**
   * UNPACK identifier
   */
  public static final String UNPACK = "UNPACK";

  /**
   * FILEHEADER identifier
   */
  public static final String FILEHEADER = "FILEHEADER";

  /**
   * XYHEADER identifier
   */
  public static final String XYHEADER = "XYHEADER";

  /**
   * THEADER identifier
   */
  public static final String THEADER = "THEADER";

  /**
   * HEADERBYTES identifier
   */
  public static final String HEADERBYTES = "HEADERBYTES";

  /**
   * TRAILERBYTES identifier
   */
  public static final String TRAILERBYTES = "TRAILERBYTES";

  /**
   * OPTIONS identifier
   */
  public static final String OPTIONS = "OPTIONS";

  /**
   * XDEF identifier
   */
  public static final String XDEF = "XDEF";

  /**
   * YDEF identifier
   */
  public static final String YDEF = "YDEF";

  /**
   * ZDEF identifier
   */
  public static final String ZDEF = "ZDEF";

  /**
   * TDEF identifier
   */
  public static final String TDEF = "TDEF";

  /**
   * EDEF identifier
   */
  public static final String EDEF = "EDEF";

  /**
   * EDEF identifier
   */
  public static final String PDEF = "PDEF";

  /**
   * ENDEDEF identifier
   */
  public static final String ENDEDEF = "ENDEDEF";

  /**
   * VARS identifier
   */
  public static final String VARS = "VARS";

  /**
   * ENDVARS identifier
   */
  public static final String ENDVARS = "ENDVARS";

  /**
   * y revesed identifier
   */
  private static final String YREV = "YREV";

  /**
   * template identifier
   */
  private static final String TEMPLATE = "TEMPLATE";

  /**
   * chsub identifier
   */
  private static final String CHSUB = "CHSUB";

  /**
   * chsub identifier
   */
  private static final String CHSUB_TEMPLATE_ID = "%ch";

  /**
   * big endian identifier
   */
  private static final String BIG_ENDIAN = "BIG_ENDIAN";

  /**
   * little endian identifier
   */
  private static final String LITTLE_ENDIAN = "LITTLE_ENDIAN";

  /**
   * little endian identifier
   */
  private static final String BYTESWAPPED = "BYTESWAPPED";

  /**
   * sequential identifier
   */
  private static final String SEQUENTIAL = "SEQUENTIAL";

  /**
   * NO template type
   */
  public static final int NO_TEMPLATE = 0;

  /**
   * time template type
   */
  public static final int TIME_TEMPLATE = 1;

  /**
   * Ensemble template type
   */
  public static final int ENS_TEMPLATE = 2;

  /**
   * Ensemble and time template type
   */
  public static final int ENS_TIME_TEMPLATE = 3;

  static private final KMPMatch matchDSET = new KMPMatch("DSET".getBytes(CDM.utf8Charset));
  static private final KMPMatch matchdset = new KMPMatch("dset".getBytes(CDM.utf8Charset));
  static private final KMPMatch matchENDVARS = new KMPMatch("ENDVARS".getBytes(CDM.utf8Charset));
  static private final KMPMatch matchendvars = new KMPMatch("endvars".getBytes(CDM.utf8Charset));
  public static boolean failFast(RandomAccessFile raf) throws IOException {
    raf.seek(0);
    boolean ok = raf.searchForward(matchDSET, 1000); // look in first 1K
    if (!ok) {
      raf.seek(0);
      ok = raf.searchForward(matchdset, 1000); // look in first 1K
      if (!ok) return true;
    }

    long pos = raf.getFilePointer();
    ok = raf.searchForward(matchENDVARS, 20000); // look in next 20K
    if (!ok) {
      raf.seek(pos);
      ok = raf.searchForward(matchendvars, 20000); // look in next 20K
    }
    return !ok;
  }

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

  boolean error;

  /**
   * the file that this relates to
   */
  private String ddFile;

  /**
   * the data file that this points to
   */
  private String dataFile;

  /**
   * Is this a big endian file?
   */
  boolean bigEndian = false;

  /**
   * missing data value
   */
  private double missingData = Double.NaN;

  /**
   * number of xy header bytes
   */
  private int xyHeaderBytes = 0;

  /**
   * number of file header bytes
   */
  private int fileHeaderBytes = 0;

  /**
   * number of time header bytes
   */
  private int timeHeaderBytes = 0;

  /**
   * number of time trilobytes
   */
  private int timeTrailerBytes = 0;

  /**
   * data type
   */
  private String dataType = null;

  /**
   * The list of variables
   */
  List<GradsVariable> variableList;

  /**
   * The list of dimensions
   */
  List<GradsDimension> dimList;

  /**
   * The list of dimensions
   */
  List<GradsAttribute> attrList;

  /**
   * The XDEF dimension
   */
  private GradsDimension xDim;

  /**
   * The YDEF dimension
   */
  private GradsDimension yDim;

  /**
   * The ZDEF dimension
   */
  private GradsDimension zDim;

  /**
   * The TDEF dimension
   */
  private GradsTimeDimension tDim;

  /**
   * The EDEF dimension
   */
  private GradsEnsembleDimension eDim;

  /**
   * The title
   */
  private String title = null;

  /**
   * grids per timestep
   */
  private int gridsPerTimeStep = 0;

  /**
   * timesteps per file
   */
  private int timeStepsPerFile = 0;

  /**
   * is this a template file
   */
  private boolean isTemplate = false;

  /**
   * type of template
   */
  private int templateType = 0;

  /**
   * is this a sequential file
   */
  private boolean isSequential = false;

  /**
   * is y reversed
   */
  private boolean yReversed = false;

  /**
   * the list of filenames that this ctl points to
   */
  List<String> fileNames;

  /**
   * defines a projection
   */
  private boolean hasProjection = false;

  /**
   * list of chsub parameters
   */
  private List<Chsub> chsubs = null;

  /**
   * the path to the ddf
   */
  private String pathToDDF = null;

  /**
   * Create a GradsDataDescriptorFile from the file
   *
   * @param filename the name of the file
   * @throws IOException problem reading/parsing the file
   */
  public GradsDataDescriptorFile(String filename, int maxLines) throws IOException {
    ddFile = filename;
    parseDDF(maxLines);
    if (error) return;
    getFileNames();
  }

  /**
   * Parse the ctl file
   *
   * @throws IOException problem reading the file
   */
  private void parseDDF(int maxLines) throws IOException {

    //long start2 = System.currentTimeMillis();

    variableList = new ArrayList<>();
    dimList = new ArrayList<>();
    attrList = new ArrayList<>();

    // LOOK not using raf - opened file again
    int count = 0;
    try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(ddFile), CDM.utf8Charset))) {
      boolean inVarSection = false;
      boolean inEnsSection = false;
      String line;
      String original;
      GradsDimension curDim = null;

      while ((original = r.readLine()) != null) {
        count++;
        if (count > maxLines) {
          error = true;
          return;
        }

        original = original.trim();
        if (original.isEmpty()) {
          continue;
        }
        line = original.toLowerCase();

        if (line.startsWith("@ ")) {
          attrList.add(GradsAttribute.parseAttribute(original));
          continue;
        }

        // ignore attribute metadata and comments
        if (line.startsWith("*")) {
          continue;
        }

        if (inEnsSection) {
          if (line.startsWith(ENDEDEF.toLowerCase())) {
            inEnsSection = false;
            continue;  // done skipping ensemble definitions
          }
          // parse the ensemble info
        }
        if (inVarSection) {
          if (line.startsWith(ENDVARS.toLowerCase())) {
            inVarSection = false;
            continue;  // done parsing variables
          }
          GradsVariable var = new GradsVariable(original);
          int numLevels = var.getNumLevels();
          if (numLevels == 0) {
            numLevels = 1;
          }
          gridsPerTimeStep += numLevels;

          // parse a variable
          variableList.add(var);

        } else {
          // not in var section or edef section, look for general metadata
          StringTokenizer st = new StringTokenizer(original);

          String label = st.nextToken();

          // TODO: Handle other options
          if (label.equalsIgnoreCase(OPTIONS)) {
            curDim = null;
            while (st.hasMoreTokens()) {
              String token = st.nextToken();
              if (token.equalsIgnoreCase(BIG_ENDIAN)) {
                bigEndian = true;
              } else if (token.equalsIgnoreCase(LITTLE_ENDIAN)) {
                bigEndian = false;
              } else if (token.equalsIgnoreCase(BYTESWAPPED)) {
                swapByteOrder();
              } else if (token.equalsIgnoreCase(YREV)) {
                yReversed = true;
              } else if (token.equalsIgnoreCase(TEMPLATE)) {
                isTemplate = true;
              } else if (token.equalsIgnoreCase(SEQUENTIAL)) {
                isSequential = true;
              }
            }
          } else if (label.equalsIgnoreCase(CHSUB)) {
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());
            String sub = st.nextToken();
            addChsub(new Chsub(start, end, sub));

          } else if (label.equalsIgnoreCase(DSET)) {
            curDim = null;
            dataFile = st.nextToken();

          } else if (label.equalsIgnoreCase(UNDEF)) {
            curDim = null;
            missingData = Double.parseDouble(st.nextToken());

          } else if (label.equalsIgnoreCase(XYHEADER)) {
            curDim = null;
            xyHeaderBytes = Integer.parseInt(st.nextToken());

          } else if (label.equalsIgnoreCase(FILEHEADER)) {
            curDim = null;
            fileHeaderBytes = Integer.parseInt(st.nextToken());

          } else if (label.equalsIgnoreCase(XDEF)) {
            int xSize = Integer.valueOf(st.nextToken());
            String xMapping = st.nextToken();
            xDim = new GradsDimension(label, xSize, xMapping);
            curDim = xDim;
            dimList.add(xDim);

          } else if (label.equalsIgnoreCase(YDEF)) {
            int ySize = Integer.valueOf(st.nextToken());
            String yMapping = st.nextToken();
            yDim = new GradsDimension(label, ySize, yMapping);
            curDim = yDim;
            dimList.add(yDim);

          } else if (label.equalsIgnoreCase(ZDEF)) {
            int zSize = Integer.valueOf(st.nextToken());
            String zMapping = st.nextToken();
            zDim = new GradsDimension(label, zSize, zMapping);
            curDim = zDim;
            dimList.add(zDim);

          } else if (label.equalsIgnoreCase(TDEF)) {
            int tSize = Integer.valueOf(st.nextToken());
            // we can read the following directly
            // since tdef never uses "levels"
            String tMapping = st.nextToken();
            tDim = new GradsTimeDimension(label, tSize, tMapping);
            curDim = tDim;
            dimList.add(tDim);

          } else if (label.equalsIgnoreCase(EDEF)) {
            int eSize = Integer.valueOf(st.nextToken());
            // Check if EDEF entry is the short or extended version
            if (st.nextToken().equalsIgnoreCase(GradsEnsembleDimension.NAMES)) {
              inEnsSection = false;
              String eMapping = GradsEnsembleDimension.NAMES;
              eDim = new GradsEnsembleDimension(label, eSize, eMapping);
              curDim = eDim;
              dimList.add(curDim);

            } else {
              // TODO: handle list of ensembles
              curDim = null;
              inEnsSection = true;
            }

          } else if (label.equalsIgnoreCase(PDEF)) {
            curDim = null;
            hasProjection = true;

          } else if (label.equalsIgnoreCase(VARS)) {
            curDim = null;
            inVarSection = true;

          } else if (label.equalsIgnoreCase(DTYPE)) {
            curDim = null;
            dataType = st.nextToken();

          } else if (label.equalsIgnoreCase(TITLE)) {
            curDim = null;
            title = original.substring(original.indexOf(" ")).trim();

          } else if (curDim != null) {
            curDim.addLevel(label);
          }
          // get the rest of the tokens
          if (curDim != null) {
            while (st.hasMoreTokens()) {
              curDim.addLevel(st.nextToken());
            }
          }
        }

      }  // end parsing loop
      //System.out.println("Time to parse file = "
      //                   + (System.currentTimeMillis() - start2));

      // update the units for the zDimension if they are specified as
      // an attribute
      if (zDim != null) {
        for (GradsAttribute attr : attrList) {
          if (attr.getVariable().equalsIgnoreCase(ZDEF) &&
                  attr.getType().equalsIgnoreCase(GradsAttribute.STRING) &&
                  attr.getName().equalsIgnoreCase("units")) {
            zDim.setUnit(attr.getValue());
            break;
          }
        }
      }

    } catch (IOException ioe) {
      log.error("Error parsing metadata for " + ddFile);
      throw new IOException("error parsing metadata for " + ddFile);
    }
  }

  /**
   * Swap the byte order from the system default
   */
  private void swapByteOrder() {
    // NB: we are setting bigEndian to be opposite the system arch
    String arch = System.getProperty("os.arch");
    if (arch.equals("x86") ||                    // Windows, Linux
            arch.equals("arm") ||                // Window CE
            arch.equals("x86_64") ||         // Windows64, Mac OS-X
            arch.equals("amd64") ||      // Linux64?
            arch.equals("alpha")) {  // Utrix, VAX, DECOS
      bigEndian = true;
    } else {
      bigEndian = false;
    }
  }

  /**
   * Get the dimensions
   *
   * @return the dimensions
   */
  public List<GradsDimension> getDimensions() {
    return dimList;
  }

  /**
   * Get the variables
   *
   * @return the variables
   */
  public List<GradsVariable> getVariables() {
    return variableList;
  }

  /**
   * Get the attributes
   *
   * @return the attributes
   */
  public List<GradsAttribute> getAttributes() {
    return attrList;
  }

  /**
   * Get the ensemble dimension
   *
   * @return the ensemble dimension
   */
  public GradsEnsembleDimension getEnsembleDimension() {
    return eDim;
  }

  /**
   * Get the time dimension
   *
   * @return the time dimension
   */
  public GradsTimeDimension getTimeDimension() {
    return tDim;
  }

  /**
   * Get the Z dimension
   *
   * @return the Z dimension
   */
  public GradsDimension getZDimension() {
    return zDim;
  }

  /**
   * Get the Y dimension
   *
   * @return the Y dimension
   */
  public GradsDimension getYDimension() {
    return yDim;
  }

  /**
   * Get the X dimension
   *
   * @return the X dimension
   */
  public GradsDimension getXDimension() {
    return xDim;
  }

  /**
   * Get the data file path
   *
   * @return the data file path
   */
  public String getDataFile() {
    return dataFile;
  }

  /**
   * Get the data descriptor file path
   *
   * @return the data descriptor file path
   */
  public String getDataDescriptorFile() {
    return ddFile;
  }

  /**
   * Get the missing value
   *
   * @return the missing value
   */
  public double getMissingValue() {
    return missingData;
  }

  /**
   * Get the number of grids per timestep
   *
   * @return the number of grids per timestep
   */
  public int getGridsPerTimeStep() {
    return gridsPerTimeStep;
  }

  /**
   * Get the number of timesteps per file and the starting offset
   *
   * @param filename the filename to check
   * @return the starting index and number of times in that file
   */
  public int[] getTimeStepsPerFile(String filename) {
    if (chsubs != null) {
      for (Chsub ch : chsubs) {
        if (filename.contains(ch.subString)) {
          return new int[]{ch.numTimes, ch.startTimeIndex};
        }
      }
    }
    return new int[]{timeStepsPerFile, 0};
  }

  /**
   * Get whether this is using a template or not
   *
   * @return whether this is using a template or not
   */
  public boolean isTemplate() {
    return isTemplate;
  }

  /**
   * Get the type of template this is
   *
   * @return the type of template
   */
  public int getTemplateType() {
    return templateType;
  }

  /**
   * Get whether this is using a projection or no
   *
   * @return whether this is using a projection or not
   */
  public boolean hasProjection() {
    return hasProjection;
  }

  /**
   * Get whether this is a sequentially written file
   *
   * @return true if this is sequential
   */
  public boolean isSequential() {
    return isSequential;
  }

  /**
   * Get whether y axis is reversed or not
   *
   * @return whether y axis is reversed or not
   */
  public boolean isYReversed() {
    return yReversed;
  }

  /**
   * Get the number of xy header bytes
   *
   * @return the number of xy header bytes
   */
  public int getXYHeaderBytes() {
    return xyHeaderBytes;
  }

  /**
   * Get the number of file header bytes
   *
   * @return the number of file header bytes
   */
  public int getFileHeaderBytes() {
    return fileHeaderBytes;
  }

  /**
   * Get the number of time header bytes
   *
   * @return the number of time header bytes
   */
  public int getTimeHeaderBytes() {
    return timeHeaderBytes;
  }

  /**
   * Get the number of time trailer bytes
   *
   * @return the number of time trailer bytes
   */
  public int getTimeTrailerBytes() {
    return timeTrailerBytes;
  }

  /**
   * Is this a big endian file
   *
   * @return true if big endian
   */
  public boolean isBigEndian() {
    return bigEndian;
  }

  /**
   * Get the title
   *
   * @return the title
   */
  public String getTitle() {
    return title;
  }

  /**
   * Get the data type.  Only support raw binary
   *
   * @return type or null
   */
  public String getDataType() {
    return dataType;
  }

  /**
   * Return a String representation of this object
   *
   * @return a String representation of this object
   */
  public String toString() {
    StringBuilder buf = new StringBuilder();
    buf.append("Parsed: ");
    buf.append(ddFile);
    buf.append("\n");
    buf.append("Data file: ");
    buf.append(dataFile);
    buf.append("\n");
    for (GradsDimension dim : dimList) {
      buf.append(dim.toString());
    }
    for (GradsVariable var : variableList) {
      buf.append(var.toString());
    }

    return buf.toString();
  }

  /**
   * Get the file name for the particular time and ensemble index
   *
   * @param eIndex ensemble index
   * @param tIndex time index
   * @return the appropriate filename
   */
  public String getFileName(int eIndex, int tIndex) {

    String dataFilePath = dataFile;

    if ((getTemplateType() == ENS_TEMPLATE) || (getTemplateType() == ENS_TIME_TEMPLATE)) {
      dataFilePath = getEnsembleDimension().replaceFileTemplate(dataFilePath, eIndex);
    }

    dataFilePath = getTimeDimension().replaceFileTemplate(dataFilePath, tIndex);
    if ((chsubs != null) && (dataFilePath.contains(CHSUB_TEMPLATE_ID))) {
      for (Chsub ch : chsubs) {
        if ((tIndex >= ch.startTimeIndex) && (tIndex <= ch.endTimeIndex)) {
          dataFilePath = dataFilePath.replace(CHSUB_TEMPLATE_ID, ch.subString);
          break;
        }
      }
    }
    return getFullPath(dataFilePath);
  }

  /**
   * Get the list of filenames
   *
   * @return the filenames
   * @throws IOException file does not exist.
   */
  private List<String> getFileNames() throws IOException {
    if (fileNames == null) {
      fileNames = new ArrayList<>();
      timeStepsPerFile = tDim.getSize();
      if (!isTemplate()) {  // single file
        fileNames.add(getFullPath(getDataFile()));
      } else {               // figure out template type
        long start = System.currentTimeMillis();
        List<String> fileSet = new ArrayList<>();
        String template = getDataFile();
        if (GradsTimeDimension.hasTimeTemplate(template)) {
          if (template.contains(GradsEnsembleDimension.ENS_TEMPLATE_ID)) {
            templateType = ENS_TIME_TEMPLATE;
          } else {
            templateType = TIME_TEMPLATE;
          }
        } else {  // not time - either ens or chsub
          if (template.contains(GradsEnsembleDimension.ENS_TEMPLATE_ID)) {
            templateType = ENS_TEMPLATE;
          } else {
            templateType = TIME_TEMPLATE;
          }
        }
        if (templateType == ENS_TEMPLATE) {
          for (int e = 0; e < eDim.getSize(); e++) {
            fileSet.add(
                    getFullPath(
                            eDim.replaceFileTemplate(template, e)));
          }
        } else if ((templateType == TIME_TEMPLATE)
                || (templateType == ENS_TIME_TEMPLATE)) {
          int numens = (templateType == TIME_TEMPLATE)
                  ? 1
                  : eDim.getSize();
          for (int t = 0; t < tDim.getSize(); t++) {
            for (int e = 0; e < numens; e++) {
              String file = getFileName(e, t);
              if (!fileSet.contains(file)) {
                fileSet.add(file);
              }
            }
          }
          // this'll be a bogus number if chsub was used
          timeStepsPerFile = tDim.getSize()
                  / (fileSet.size() / numens);
        }
        //System.out.println("Time to generate file list = "
        //                   + (System.currentTimeMillis() - start));
        fileNames.addAll(fileSet);
      }
      //long start2 = System.currentTimeMillis();
      // now make sure they exist
      for (String file : fileNames) {
        File f = new File(file);
        if (!f.exists()) {
          log.error("File: " + f + " does not exist");
          throw new IOException("File: " + f + " does not exist");
        }
      }
      //System.out.println("Time to check file list = "
      //                   + (System.currentTimeMillis() - start2));
    }
    return fileNames;
  }

  /**
   * Get the path to the Data Descriptor File
   *
   * @return the path to the Data Descriptor File
   */
  private String getDDFPath() {
    if (pathToDDF == null) {
      int lastSlash = ddFile.lastIndexOf("/");
      if (lastSlash < 0) {
        lastSlash = ddFile.lastIndexOf(File.separator);
      }
      pathToDDF = (lastSlash < 0)
              ? ""
              : ddFile.substring(0, lastSlash + 1);

    }
    return pathToDDF;
  }

  /**
   * Get the full path for a given filename
   *
   * @param filename the raw filename
   * @return the full filename
   */
  private String getFullPath(String filename) {

    String file;
    String ddfPath = getDDFPath();
    if (filename.startsWith("^")) {
      file = filename.replace("^", "");
      file = ddfPath + file;
    } else {
      File f = new File(filename);
      if (!f.isAbsolute()) {
        file = ddfPath + filename;
      } else {
        file = filename;
      }
    }

    return file;
  }

  /**
   * Add a Chsub
   *
   * @param sub the chsub
   */
  private void addChsub(Chsub sub) {
    if (chsubs == null) {
      chsubs = new ArrayList<>();
    }
    chsubs.add(sub);
  }

  /**
   * Class to handle the CHSUB parameters
   */
  protected static class Chsub {

    /**
     * start time index (0 based)
     */
    protected int startTimeIndex = 0;

    /**
     * end time index (0 based)
     */
    protected int endTimeIndex = 0;

    /**
     * number of times
     */
    protected int numTimes = 0;

    /**
     * substitution string
     */
    protected String subString = null;

    /**
     * Create a new Chsub
     *
     * @param start the start index (1 based)
     * @param end   the start index (1 based)
     * @param sub   the subsitution string
     */
    Chsub(int start, int end, String sub) {
      startTimeIndex = start - 1;  // zero based
      endTimeIndex = end - 1;    // zero based
      numTimes = endTimeIndex - startTimeIndex + 1;
      subString = sub;
    }

    /**
     * Get a String representation of this CHSUB
     *
     * @return a String representation of this CHSUB
     */
    public String toString() {
      return "CHSUB " + startTimeIndex + " " + endTimeIndex + " "
              + subString;
    }
  }

}


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

// $Id: $

package ucar.nc2.iosp.netcdf3;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.iosp.IospHelper;

import java.util.List;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;

/**
 * Experimental.
 * @author john
 */
public class N3outputStreamWriter extends N3streamWriter {

  public N3outputStreamWriter(ucar.nc2.NetcdfFile ncfile) {
    super(ncfile);
  }

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

  public void writeDataAll(DataOutputStream stream) throws IOException {
    for (Vinfo vinfo : vinfoList) {
      if (!vinfo.isRecord) {
        Variable v = vinfo.v;
        assert filePos == vinfo.offset;
        if (debugPos) System.out.println(" writing at "+filePos+" should be "+vinfo.offset+" "+v.getFullName());
        int nbytes = writeDataFast(v, stream, v.read());
        filePos += nbytes;
        filePos += pad(stream, nbytes, (byte) 0);
      }
    }

    // see if it has a record dimension we can use
    // see if it has a record dimension we can use
    boolean useRecordDimension = ncfile.hasUnlimitedDimension();
    if (useRecordDimension) {
      ncfile.sendIospMessage(NetcdfFile.IOSP_MESSAGE_ADD_RECORD_STRUCTURE);
    }

    // write record data
    if (useRecordDimension) {
      boolean first = true;
      int nrec = 0;

      Structure recordVar = (Structure) ncfile.findVariable("record");
      try (StructureDataIterator ii = recordVar.getStructureIterator()) {
        while (ii.hasNext()) {
          StructureData sdata = ii.next();
          int count = 0;

          for (Vinfo vinfo : vinfoList) {
            if (vinfo.isRecord) {
              Variable v = vinfo.v;
              int nbytes = writeDataFast(v, stream, sdata.getArray(v.getShortName()));
              count += nbytes;
              count += pad(stream, nbytes, (byte) 0);
              if (first && debugWriteData) System.out.println(v.getShortName() + " wrote " + count + " bytes");
            }
          }
          if (first && debugWriteData) {
            System.out.println("wrote " + count + " bytes");
            first = false;
          }
          nrec++;
        }
      }
      if (debugWriteData) System.out.println("wrote " + nrec + " records");
      stream.flush();

      // remove the record structure this is rather fishy, perhaps better to leave it
      ncfile.sendIospMessage(NetcdfFile.IOSP_MESSAGE_REMOVE_RECORD_STRUCTURE);
      ncfile.finish();
    }
  }

  public void writeNonRecordData(Variable v, DataOutputStream stream, Array data) throws IOException {
    Vinfo vinfo = vinfoMap.get(v);
    if (debugWriteData)
      System.out.println("Write " + v.getFullName() + " at filePos= " + filePos + " vinfo.offset= " + vinfo.offset);
    if (filePos != vinfo.offset) throw new IllegalStateException();

    filePos += writeData(v, stream, data);
    if (vinfo.pad > 0) {
      byte[] dummy = new byte[vinfo.pad];
      stream.write(dummy);
      filePos += vinfo.pad;
    }
  }

  private int recno = 0;
  private boolean first = true;

  public void writeRecordData(DataOutputStream stream, List<Variable> varList) throws IOException {
    long want = recStart + ((long) recno) * recSize;
    if (debugWriteData) System.out.println("Write record at filePos= " + filePos + " should be= " + want);
    if (filePos != want) throw new IllegalStateException();

    for (Variable v : varList) {
      if (first && debugWriteData)
        System.out.println("  write record var " + v.getNameAndDimensions() + " filePos=" + filePos);
      filePos += writeData(v, stream, v.read());

      Vinfo vinfo = vinfoMap.get(v);
      if (vinfo.pad > 0) {
        byte[] dummy = new byte[vinfo.pad];
        stream.write(dummy);
        filePos += vinfo.pad;
      }
    }
    first = false;

    recno++;
  }

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

  private long writeData(Variable v, DataOutputStream stream, Array values) throws java.io.IOException {
    DataType dataType = v.getDataType();
    IndexIterator ii = values.getIndexIterator();

    if (dataType == DataType.BYTE) {
      while (ii.hasNext())
        stream.write(ii.getByteNext());
      return values.getSize();

    } else if (dataType == DataType.CHAR) {
      while (ii.hasNext())
        stream.write(ii.getByteNext());
      return values.getSize();

    } else if (dataType == DataType.SHORT) {
      while (ii.hasNext())
        stream.writeShort(ii.getShortNext());
      return 2 * values.getSize();

    } else if (dataType == DataType.INT) {
      while (ii.hasNext())
        stream.writeInt(ii.getIntNext());
      return 4 * values.getSize();

    } else if (dataType == DataType.FLOAT) {
      while (ii.hasNext())
        stream.writeFloat(ii.getFloatNext());
      return 4 * values.getSize();

    } else if (dataType == DataType.DOUBLE) {
      while (ii.hasNext())
        stream.writeDouble(ii.getDoubleNext());
      return 8 * values.getSize();
    }

    throw new IllegalStateException("dataType= " + dataType);
  }

  private int writeDataFast(Variable v, DataOutputStream stream, Array values) throws java.io.IOException {
    DataType dataType = v.getDataType();

    if (dataType == DataType.BYTE) {
      byte[] pa = (byte[]) values.get1DJavaArray(byte.class);
      for (int i = 0; i < pa.length; i++)
        stream.write(pa[i]);
      return pa.length;

    } else if (dataType == DataType.CHAR) {
      byte[] pa = IospHelper.convertCharToByte((char[]) values.get1DJavaArray(char.class));
      for (int i = 0; i < pa.length; i++)
        stream.write(pa[i]);
      return pa.length;

    } else if (dataType == DataType.SHORT) {
      short[] pa = (short[]) values.get1DJavaArray(short.class);
      for (int i = 0; i < pa.length; i++)
        stream.writeShort(pa[i]);
      return 2 * pa.length;

    } else if (dataType == DataType.INT) {
      int[] pa = (int[]) values.get1DJavaArray(int.class);
      for (int i = 0; i < pa.length; i++)
        stream.writeInt(pa[i]);
      return 4 * pa.length;

    } else if (dataType == DataType.FLOAT) {
      float[] pa = (float[]) values.get1DJavaArray(float.class);
      for (int i = 0; i < pa.length; i++)
        stream.writeFloat(pa[i]);
      return 4 * pa.length;

    } else if (dataType == DataType.DOUBLE) {
      double[] pa = (double[]) values.get1DJavaArray(double.class);
      for (int i = 0; i < pa.length; i++)
        stream.writeDouble(pa[i]);
      return 8 * pa.length;
    }

    throw new IllegalStateException("dataType= " + dataType);
  }

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

  public static void writeFromFile(NetcdfFile fileIn, String fileOutName) throws IOException {
    try (DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileOutName), 10 * 1000))) {
      N3outputStreamWriter writer = new N3outputStreamWriter(fileIn);
      int numrec = fileIn.getUnlimitedDimension() == null ? 0 : fileIn.getUnlimitedDimension().getLength();
      writer.writeHeader(stream, numrec);
      writer.writeDataAll(stream);
    }
  }

  static public void main(String args[]) throws IOException {
    writeFromFile(NetcdfFile.open("C:/data/metars/Surface_METAR_20070331_0000.nc"), "C:/temp/streamOut.nc");
  }
}


package ucar.nc2.grib.coord;

import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarPeriod;

/**
 * Time intervals represented by start and end CalendarDate.
 */
@Immutable
public class TimeCoordIntvDateValue implements Comparable<TimeCoordIntvDateValue> {

  private final CalendarDate start, end;

  public TimeCoordIntvDateValue(CalendarPeriod period, CalendarDate end) {
    this.end = end;
    this.start = end.subtract(period);
  }

  public TimeCoordIntvDateValue(CalendarDate start, CalendarPeriod period) {
    this.start = start;
    this.end = start.add(period);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    TimeCoordIntvDateValue that = (TimeCoordIntvDateValue) o;
    return start.equals(that.start) &&
        end.equals(that.end);
  }

  @Override
  public int hashCode() {
    return Objects.hash(start, end);
  }

  public TimeCoordIntvDateValue(CalendarDate start, CalendarDate end) {
    this.start = start;
    this.end = end;
  }

  public CalendarDate getStart() {
    return start;
  }

  public CalendarDate getEnd() {
    return end;
  }

  // Calculate the offset in units of timeUnit from the given reference date?
  public TimeCoordIntvValue convertReferenceDate(CalendarDate refDate, CalendarPeriod timeUnit) {
    if (timeUnit == null) {
      throw new IllegalArgumentException("null time unit");
    }
    int startOffset = timeUnit.getOffset(refDate, start);   // LOOK wrong - not dealing with value ??
    int endOffset = timeUnit.getOffset(refDate, end);
    return new TimeCoordIntvValue(startOffset, endOffset);
  }

  public int compareTo(@Nonnull TimeCoordIntvDateValue that) {  // first compare start, then end
    int c1 = start.compareTo(that.start);
    return (c1 == 0) ? end.compareTo(that.end) : c1;
  }

  @Override
  public String toString() {
    return String.format("(%s,%s)", start, end);
  }

}