/* Copyright 2009, UCAR/Unidata and OPeNDAP, Inc.
   See the COPYRIGHT file for more information. */

/****************************************************/

package opendap.dap.parsers;

import java.io.InputStream;
import java.util.*;

import opendap.dap.*;
import ucar.nc2.util.EscapeStrings;

import static opendap.dap.parsers.Dap2Lex.*;

public abstract class Dap2Parse
{

    // Define some error states
    static protected int EBADTYPE = 0;
    static protected int EDIMSIZE = 1;
    static protected int EDDS = 2;
    static protected int EDAS = 3;

    // Track what kind of file we parsed; use int instead of enum to simplify access
    static public final int DapNUL = 0;
    static public final int DapDAS = 1;
    static public final int DapDDS = 2;
    static public final int DapERR = 3;

    static final int NA = DGrid.ARRAY;

    BaseTypeFactory factory = null;
    Dap2Lex lexstate = null;

    Dap2Parse parsestate = null; /* Slight kludge */

    DDS ddsobject = null;
    DAS dasobject = null;
    DAP2Exception errobject = null;

    int parseClass = DapNUL;

    protected int dapdebug = 0;

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

    // This contructor is needed because generated Dap2Parse
    // constructor requires it.
    public Dap2Parse()
    {
        this(null);
    }

    public Dap2Parse(BaseTypeFactory factory)
    {
        parsestate = this;
        if(factory == null) {
            throw new RuntimeException("Dap2parse: no factory specified");
        }
        this.factory = factory;
    }

    /**
     * **********************************************
     */
    /* Access into the Dap2Parser for otherwise inaccessible fields */
    abstract public boolean parse(String text) throws ParseException;

    abstract public int getDebugLevel();

    abstract public void setDebugLevel(int level);

    abstract public void setURL(String url);

    abstract public String getURL();

    /**
     * **********************************************
     */
    /* Utilities */

    String strdup(String s)
    {
        return s;
    }

    /**
     * **********************************************
     */

    public DDS getDDS()
    {
        return ddsobject;
    }

    public DAS getDAS()
    {
        return dasobject;
    }

    public DAP2Exception getERR()
    {
        return errobject;
    }

    /**
     * **********************************************
     */
    /* Parse invocation interface */
    public int dapparse(String text, DDS dds, DAS das, DAP2Exception err) throws ParseException
    {
        //setDebugLevel(1);
        ddsobject = dds;
        dasobject = das;
        errobject = (err == null ? new DAP2Exception() : err);

        dapdebug = getDebugLevel();
        Boolean accept = parse(text);
        if(!accept)
            throw new ParseException("Dap2 Parser returned false");
        return parseClass;
    }

    // Call this to parse a DDS
    public int
    ddsparse(String text, DDS dds) throws ParseException
    {
        return dapparse(text, dds, null, null);
    }

    // Call this to parse a DAS
    public int
    dasparse(String text, DAS das)
            throws ParseException
    {
        return dapparse(text, null, das, null);
    }

    // Call this to parse an error{} body
    public int
    errparse(String text, DAP2Exception err)
            throws ParseException
    {
        return dapparse(text, null, null, err);
    }

    /**
     * **********************************************
     */
    /* Parser core */

    /* Use the initial keyword to indicate what we are parsing */
    void
    tagparse(Dap2Parse parsestate, int kind) throws ParseException
    {
        /* Create the storage object corresponding to what we are parsing */
        String expected = parseactual();
        switch (kind) {
        case SCAN_DATASET:
            parseClass = DapDDS;
            if(ddsobject == null)
                throw new ParseException("DapParse: found DDS, expected " + expected);
            break;
        case SCAN_ATTR:
            parseClass = DapDAS;
            if(dasobject == null)
                throw new ParseException("DapParse: found DAS, expected " + parseactual());
            lexstate.dassetup();
            break;
        case SCAN_ERROR:
            parseClass = DapERR;
            if(errobject == null)
                throw new ParseException("DapParse: found error{}, expected " + parseactual());
            break;
        default:
            throw new ParseException("Unknown tag argument: " + kind);
        }
    }

    private String parseactual()
    {
        String actual = "";
        if(ddsobject != null) actual = actual + " DDS";
        if(dasobject != null) actual = actual + " DAS";
        if(errobject != null) actual = actual + " error{}";
        return "one of" + actual;
    }

    Object
    datasetbody(Dap2Parse state, Object name, Object decls)
            throws ParseException
    {
        if(ddsobject == null)
            throw new ParseException("No DDS object to which it attach decls");
        ddsobject.setEncodedName((String) name);
        for(Object o : (List<Object>) decls) {
            ddsobject.addVariable((BaseType) o);
        }
        if(dapdebug > 0) {
            System.err.println("datasetbody: |");
            ddsobject.print(System.err);
            System.err.println("|");
        }
        return null;
    }

    Object
    attributebody(Dap2Parse state, Object attrlist)
            throws ParseException
    {
        try {
            if(dasobject == null)
                throw new ParseException("No DAS for attributes");
            for(Object o : (List<Object>) attrlist) {
                if(o instanceof Attribute) {
                    Attribute a = (Attribute) o;
                    Iterator it = a.getValuesIterator();
                    while(it.hasNext()) {
                        dasobject.appendAttribute(a.getEncodedName(),
                                a.getType(),
                                (String) it.next()); /*UGH*/
                    }
                } else if(o instanceof AttributeTable) {
                    AttributeTable aset = (AttributeTable) o;
                    dasobject.addAttributeTable(aset.getEncodedName(), aset);
                } else
                    throw new Exception("attribute body: unknown object: " + o);
            }

        } catch (ParseException pe) {
            throw pe;
        } catch (Exception e) {
            throw new ParseException(e);
        }
        return null;
    }

    void
    errorbody(Dap2Parse state,
              Object code, Object msg, Object ptype, Object prog)
            throws ParseException
    {
        errobject.setErrorMessage(msg.toString());
        errobject.setProgramSource((String) prog);
        try {
            int n = (code == null ? -1 : Integer.decode((String) code));
            errobject.setErrorCode(n);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Error code is not a legal integer");
        }
        try {
            int n = (ptype == null ? -1 : Integer.decode((String) ptype));
            errobject.setProgramType(n);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Error program type is not a legal integer");
        }
    }

    void
    unrecognizedresponse(Dap2Parse state)
            throws ParseException
    {
        errorbody(state, "0", state.lexstate.text, null, null);
    }

    Object
    declarations(Dap2Parse state, Object decls, Object decl)
            throws ParseException
    {
        List<Object> alist = (List<Object>) decls;
        if(alist == null)
            alist = new ArrayList<Object>();
        else
            alist.add((BaseType) decl);
        return alist;
    }

    Object
    arraydecls(Dap2Parse state, Object arraydecls, Object arraydecl)
            throws ParseException
    {
        List<Object> alist = (List<Object>) arraydecls;
        if(alist == null)
            alist = new ArrayList<Object>();
        else
            alist.add(arraydecl);
        return alist;
    }

    Object
    arraydecl(Dap2Parse state, Object name, Object size)
            throws ParseException
    {
        int value;
        DArrayDimension dim;
        try {
            value = Integer.decode((String) size);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Dimension " + name + " is not an integer: " + size, nfe);
        }
        dim = new DArrayDimension(value, clearName((String) name));
        return dim;
    }

    Object
    attrlist(Dap2Parse state, Object attrlist, Object attrtuple)
            throws ParseException
    {
        List<Object> alist = (List<Object>) attrlist;
        if(alist == null)
            alist = new ArrayList<Object>();
        else {
            String dupname;
            if(attrtuple != null) {/* null=>alias encountered, ignore */
                alist.add(attrtuple);
                if((dupname = scopeduplicates(alist)) != null) {
                    dap_parse_error(state, "Duplicate attribute names in same scope: %s", dupname);
                    /* Remove this attribute */
                    alist.remove(alist.size() - 1);
                }
            }
        }
        return alist;
    }

    Object
    attrvalue(Dap2Parse state, Object valuelist, Object value, Object etype)
            throws ParseException
    {
        List<Object> alist = (List<Object>) valuelist;
        if(alist == null) alist = new ArrayList<Object>();
        /* Watch out for null values */
        if(value == null) value = "";
        alist.add(value);
        return alist;
    }

    Object
    attribute(Dap2Parse state, Object name, Object values, Object etype)
            throws ParseException
    {
        Attribute att;
        int typ = (Integer) etype;
        att = new Attribute(clearName((String) name), attributetypefor((Integer) etype));
        try {
            for(Object o : (List<Object>) values) {
                // If this is a string, then we need to unescape any backslashes
                if(typ == SCAN_STRING)
                    att.appendValue(unescapeAttributeString((String) o), true);
                else
                    att.appendValue((String) o, true);
            }
        } catch (Exception e) {
            throw new ParseException(e);
        }
        return att;
    }

    Object
    attrset(Dap2Parse state, Object name, Object attributes)
            throws ParseException
    {
        try {

            AttributeTable attset;
            attset = new AttributeTable(clearName((String) name));
            for(Object o : (List<Object>) attributes) {
                if(o instanceof Attribute) {
                    /* ugh, Attributetable needs an additional addAttribute fcn*/
                    Attribute a = (Attribute) o;
                    Iterator it = a.getValuesIterator();
                    while(it.hasNext()) {
                        attset.appendAttribute(a.getClearName(), a.getType(), (String) it.next());
                    }
                } else if(o instanceof AttributeTable) {
                    AttributeTable at = (AttributeTable) o;
                    attset.addContainer(at.getEncodedName(), at);
                } else
                    throw new ParseException("attrset: unexpected object: " + o);
            }
            return attset;

        } catch (Exception e) {
            throw new ParseException(e);
        }
    }

    Object
    makebase(Dap2Parse state, Object name, Object etype, Object dimensions)
            throws ParseException
    {
        BaseType node;
        List<Object> dims = (List<Object>) dimensions;
        node = basetypefor((Integer) etype, ((String) name));
        if(dims.size() > 0) {
            DArray array = state.factory.newDArray();
            array.addVariable(node);
            dimension(array, dims);
            node = array;
        }
        return node;
    }

    void
    dimension(DArray node, List<Object> dimensions)
            throws ParseException
    {
        int i;
        /* Interface requires rebuilding the dimensions */
        for(Object o : dimensions) {
            DArrayDimension dim = (DArrayDimension) o;
            node.appendDim(dim.getSize(), dim.getClearName());
        }
    }

    Object
    makestructure(Dap2Parse state, Object name, Object dimensions, Object fields)
            throws ParseException
    {
        BaseType node;
        String dupname;
        List<Object> dimset = (List<Object>) dimensions;
        if((dupname = scopeduplicates((List<Object>) fields)) != null) {
            dap_parse_error(state, "Duplicate structure field names in same scope: %s.%s", ((String) name), dupname);
            return (Object) null;
        }
        node = factory.newDStructure(clearName(((String) name)));
        List<Object> list = (List<Object>) fields;
        for(Object o : list) {
            ((DStructure) node).addVariable((BaseType) o, NA);
        }
        /* If this is dimensioned, then we need to wrap with DArray */
        if(dimset.size() > 0) {
            DArray anode = factory.newDArray();
            anode.addVariable(node);
            dimension(anode, (List<Object>) dimensions);
            node = anode;
        }
        return node;
    }

    Object
    makesequence(Dap2Parse state, Object name, Object members)
            throws ParseException
    {
        DSequence node;
        String dupname;
        if((dupname = scopeduplicates((List<Object>) members)) != null) {
            dap_parse_error(state, "Duplicate sequence member names in same scope: %s.%s", ((String) name), dupname);
            return (Object) null;
        }
        node = factory.newDSequence(clearName(((String) name)));
        List<Object> list = (List<Object>) members;
        for(Object o : list) {
            node.addVariable((BaseType) o, NA);
        }
        return node;
    }

    Object
    makegrid(Dap2Parse state, Object name, Object arraydecl0, Object mapdecls0)
            throws ParseException
    {
        DGrid node;
        DArray arraydecl = (DArray) arraydecl0;
        List<Object> mapdecls = (List<Object>) mapdecls0;
        /* Check for duplicate map names */
        String dupname;
        if((dupname = scopeduplicates(mapdecls)) != null) {
            dap_parse_error(state, "Duplicate grid map names in same scope: %s.%s", ((String) name), dupname);
            return (Object) null;
        }
        node = factory.newDGrid(clearName(((String) name)));
        node.addVariable(arraydecl, DGrid.ARRAY);
        for(Object m : mapdecls) {
            node.addVariable((BaseType) m, DGrid.MAPS);
        }
        return node;
    }

    String
    flatten(String s)
    {
        boolean whitespace = false;
        StringBuilder buf = new StringBuilder();
        for(int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
            case '\r':
            case '\n':
                break;
            case '\t':
                buf.append(' ');
                break;
            default:
                buf.append(c);
                break;
            }
        }
        return buf.toString();
    }

    int
    dapsemanticerror(Dap2Parse state, int kind, String msg)
            throws ParseException
    {
        return daperror(state, msg);
    }

    int
    daperror(Dap2Parse state, String msg)
            throws ParseException
    {
        dap_parse_error(state, msg);
        return 0;
    }

    void
    dap_parse_error(Dap2Parse state, String fmt, Object... args)
            throws ParseException
    {
        lexstate.lexerror(String.format(fmt, args));
        String tmp = null;
        tmp = flatten(lexstate.getInput());
        throw new ParseException("context: " + tmp + "^");
    }

    BaseType
    basetypefor(Integer etype, String name)
            throws ParseException
    {
        switch (etype) {
        case SCAN_BYTE:
            return factory.newDByte(clearName(name));
        case SCAN_INT16:
            return factory.newDInt16(clearName(name));
        case SCAN_UINT16:
            return factory.newDUInt16(clearName(name));
        case SCAN_INT32:
            return factory.newDInt32(clearName(name));
        case SCAN_UINT32:
            return factory.newDUInt32(clearName(name));
        case SCAN_FLOAT32:
            return factory.newDFloat32(clearName(name));
        case SCAN_FLOAT64:
            return factory.newDFloat64(clearName(name));
        case SCAN_URL:
            return factory.newDURL(clearName(name));
        case SCAN_STRING:
            return factory.newDString(clearName(name));
        default:
            throw new ParseException("basetypefor: illegal type: " + etype);
        }
    }

    int
    attributetypefor(Integer etype)
            throws ParseException
    {
        switch (etype) {
        case SCAN_BYTE:
            return Attribute.BYTE;
        case SCAN_INT16:
            return Attribute.INT16;
        case SCAN_UINT16:
            return Attribute.UINT16;
        case SCAN_INT32:
            return Attribute.INT32;
        case SCAN_UINT32:
            return Attribute.UINT32;
        case SCAN_FLOAT32:
            return Attribute.FLOAT32;
        case SCAN_FLOAT64:
            return Attribute.FLOAT64;
        case SCAN_URL:
            return Attribute.URL;
        case SCAN_STRING:
            return Attribute.STRING;
        default:
            throw new ParseException("attributetypefor: illegal type: " + etype);
        }
    }

    String
    scopeduplicates(List<Object> list)
            throws ParseException
    {
        for(int i = 0; i < list.size(); i++) {
            Object io = list.get(i);
            String iname = extractname(io);
            for(int j = i + 1; j < list.size(); j++) {
                Object jo = list.get(j);
                String jname = extractname(jo);
                if(iname.equals(jname))
                    return iname;
            }
        }
        return null;
    }

    /* Since there is no common parent class for BaseType, Attribute,
       and AttributeTable, provide a type specific name getter.
    */

    String
    extractname(Object o)
            throws ParseException
    {
        if(o instanceof BaseType)
            return ((BaseType) o).getClearName();
        if(o instanceof Attribute)
            return ((Attribute) o).getClearName();
        if(o instanceof AttributeTable)
            return ((AttributeTable) o).getClearName();
        throw new ParseException("extractname: illegal object class: " + o);
    }

    String
    dapdecode(Dap2Lex lexer, Object name)
    {
        return EscapeStrings.unescapeDAPIdentifier((String) name);
    }

    String unescapeAttributeString(String s)
    {
        StringBuilder news = new StringBuilder();
        for(char c : s.toCharArray()) {
            if(c == '\\') continue;
            news.append(c);
        }
        return news.toString();
    }

    // Because we fixed this in dap.y (name: rule),
    // we do not need to do it again here.
    String clearName(String name)
    {
        //OLD:return EscapeStrings.unEscapeDAPIdentifier(name);
        return name;
    }

}


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

package ucar.nc2.iosp.mcidas;

import edu.wisc.ssec.mcidas.*;

import ucar.nc2.iosp.grid.*;

import ucar.unidata.io.RandomAccessFile;

import java.io.IOException;

import java.util.*;

/**
 * Read grid(s) from a McIDAS grid file
 *
 * @author dmurray
 */
public class McIDASGridReader {

  /**
   * The file
   */
  protected RandomAccessFile rf;

  /**
   * An error message
   */
  private String errorMessage;

  /**
   * Grid index
   */
  private GridIndex gridIndex;

  /**
   * swap flag
   */
  protected boolean needToSwap = false;

  /**
   * hashMap of GridDefRecords
   */
  private HashMap<String, McGridDefRecord> gdsMap = new HashMap<>();

  /**
   * maximum number of grids in a file
   */
  // is this the right number?
  private static final int MAX_GRIDS = 999999;

  /**
   * Bean ctor
   */
  public McIDASGridReader() {
  }

  /**
   * Create a McIDASGrid Reader from the file
   *
   * @param filename filename
   * @throws IOException problem reading file
   */
  public McIDASGridReader(String filename) throws IOException {
    this(RandomAccessFile.acquire(filename));
  }

  /**
   * Create a McIDASGrid Reader from the file
   *
   * @param raf RandomAccessFile
   * @throws IOException problem reading file
   */
  public McIDASGridReader(RandomAccessFile raf) throws IOException {
    init(raf);
  }

  /**
   * Initialize the file, read in all the metadata (ala DM_OPEN)
   *
   * @param raf RandomAccessFile to read.
   * @throws IOException problem reading file
   */
  public final void init(RandomAccessFile raf) throws IOException {
    if (!init(raf, true))
      throw new IOException("Unable to open McIDAS Grid file: " + errorMessage);
  }

  /**
   * Initialize the file, read in all the metadata (ala DM_OPEN)
   *
   * @param fullCheck for a full check reading grids
   * @param raf       RandomAccessFile to read.
   * @throws IOException problem reading file
   */
  public final boolean init(RandomAccessFile raf, boolean fullCheck)
          throws IOException {
    rf = raf;
    raf.order(RandomAccessFile.BIG_ENDIAN);
    return init(fullCheck);
  }

  /**
   * Initialize this reader.  Get the Grid specific info
   *
   * @return true if successful
   * @throws IOException problem reading the data
   */
  protected boolean init() throws IOException {
    return init(true);
  }

  /**
   * Initialize this reader.  Get the Grid specific info
   *
   * @param fullCheck for a full check reading grids
   * @return true if successful, false if not a mcidas grid file
   * @throws IOException problem reading the data
   */
  protected boolean init(boolean fullCheck) throws IOException {
    if (rf == null) {
      logError("File is null");
      return false;
    }
    gridIndex = new GridIndex(rf.getLocation());

    rf.order(RandomAccessFile.BIG_ENDIAN);
    if (rf.length() < 44) return false;
    int numEntries = Math.abs(readInt(10));
    if (numEntries > 1000000) {
      needToSwap = true;
      numEntries = Math.abs(McIDASUtil.swbyt4(numEntries));
    }
    if (numEntries > MAX_GRIDS) {
      return false;
    }
    //System.out.println("need to Swap = " + needToSwap);
    //System.out.println("number entries="+numEntries);

    // go back to the beginning
    rf.seek(0);
    // read the fileheader
    String label = rf.readString(32);
    // GEMPAK too closely like McIDAS
    if (label.contains("GEMPAK DATA MANAGEMENT FILE")) {
      logError("label indicates this is a GEMPAK grid");
      return false;
    } else { // check that they are all printable ASCII chars
      for (int i = 0; i < label.length(); i++) {
        String s0 = label.substring(i, i + 1);
        if (!(0 <= s0.compareTo(" ") && s0.compareTo("~") <= 0)) {
          logError("bad label, not a McIDAS grid");
          return false;
        }
      }
    }

    //System.out.println("label = " + label);

    // int project = readInt(8);
    //System.out.println("Project = " + project);

    int date = readInt(9);
    // dates are supposed to be yyyddd, but account for ccyyddd up to year 4000
    if ((date < 10000) || (date > 400000)) {
      logError("date wrong, not a McIDAS grid");
      return false;
    }
    //System.out.println("date = " + date);

    if (rf.length() < 4 * (numEntries + 12)) return false;
    int[] entries = new int[numEntries];
    for (int i = 0; i < numEntries; i++) {
      entries[i] = readInt(i + 11);
      // sanity check that this is indeed a McIDAS Grid file
      if (entries[i] < -1) {
        logError("bad grid offset " + i + ": " + entries[i]);
        return false;
      }
    }
    if (!fullCheck) {
      return true;
    }

    // Don't swap:
    rf.order(RandomAccessFile.BIG_ENDIAN);
    for (int i = 0; i < numEntries; i++) {
      if (entries[i] == -1) {
        continue;
      }
      int[] header = new int[64];
      rf.seek(entries[i] * 4);
      rf.readInt(header, 0, 64);
      if (needToSwap) {
        swapGridHeader(header);
      }
      try {

        McIDASGridRecord gr = new McIDASGridRecord(entries[i],
                header);
        //if (gr.getGridDefRecordId().equals("CONF X:93 Y:65")) {
        //if (gr.getGridDefRecordId().equals("CONF X:54 Y:47")) {
        // figure out how to handle Mercator projections
        // if ( !(gr.getGridDefRecordId().startsWith("MERC"))) {
        gridIndex.addGridRecord(gr);
        if (gdsMap.get(gr.getGridDefRecordId()) == null) {
          McGridDefRecord mcdef = gr.getGridDefRecord();
          //System.out.println("new nav " + mcdef.toString());
          gdsMap.put(mcdef.toString(), mcdef);
          gridIndex.addHorizCoordSys(mcdef);
        }
        //}
      } catch (McIDASException me) {
        logError("problem creating grid dir");
        return false;
      }
    }
    // check to see if there are any grids that we can handle
    if (gridIndex.getGridRecords().isEmpty()) {
      logError("no grids found");
      return false;
    }
    return true;
  }

  /**
   * Swap the grid header, avoiding strings
   *
   * @param gh grid header to swap
   */
  private void swapGridHeader(int[] gh) {
    McIDASUtil.flip(gh, 0, 5);
    McIDASUtil.flip(gh, 7, 7);
    McIDASUtil.flip(gh, 9, 10);
    McIDASUtil.flip(gh, 12, 14);
    McIDASUtil.flip(gh, 32, 51);
  }

  /**
   * Read the grid
   *
   * @param gr the grid record
   * @return the data
   */
  public float[] readGrid(McIDASGridRecord gr) throws IOException {

    float[] data;
    //try {
      int te = (gr.getOffsetToHeader() + 64) * 4;
      int rows = gr.getRows();
      int cols = gr.getColumns();
      rf.seek(te);

      float scale = (float) gr.getParamScale();

      data = new float[rows * cols];
      rf.order(needToSwap ? RandomAccessFile.LITTLE_ENDIAN : RandomAccessFile.BIG_ENDIAN);
      // int n = 0;
      // store such that 0,0 is in lower left corner...
      for (int nc = 0; nc < cols; nc++) {
        for (int nr = 0; nr < rows; nr++) {
          int temp = rf.readInt();  // check for missing value
          data[(rows - nr - 1) * cols + nc] = (temp
                  == McIDASUtil.MCMISSING)
                  ? Float.NaN
                  : ((float) temp) / scale;
        }
      }
      rf.order(RandomAccessFile.BIG_ENDIAN);
    //} catch (Exception esc) {
    //  System.out.println(esc);
    //}
    return data;
  }

  /**
   * to get the grid header corresponding to the last grid read
   *
   * @return McIDASGridDirectory of the last grid read
   */
  public GridIndex getGridIndex() {
    return gridIndex;
  }

  /**
   * Read an integer
   *
   * @param word word in file (0 based) to read
   * @return int read
   * @throws IOException problem reading file
   */
  public int readInt(int word) throws IOException {
    if (rf == null) {
      throw new IOException("no file to read from");
    }
    rf.seek(word * 4);
    // set the order
    if (needToSwap) {
      rf.order(RandomAccessFile.LITTLE_ENDIAN);  // swap
    } else {
      rf.order(RandomAccessFile.BIG_ENDIAN);
    }
    int idata = rf.readInt();
    rf.order(RandomAccessFile.BIG_ENDIAN);
    return idata;
  }

  /**
   * Log an error
   *
   * @param errMsg message to log
   */
  private void logError(String errMsg) {
    errorMessage = errMsg;
  }

  /**
   * for testing purposes
   *
   * @param args file name
   * @throws IOException problem reading file
   */
  public static void main(String[] args) throws IOException {
    String file = "GRID2001";
    if (args.length > 0) {
      file = args[0];
    }
    McIDASGridReader mg = new McIDASGridReader(file);
    GridIndex gridIndex = mg.getGridIndex();
    List grids = gridIndex.getGridRecords();
    System.out.println("found " + grids.size() + " grids");
    int num = Math.min(grids.size(), 10);
    for (int i = 0; i < num; i++) {
      System.out.println(grids.get(i));
    }

  }
}


package thredds.server.wfs;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import thredds.server.config.ThreddsConfig;

/**
 * A writer for a WFS compliant Geospatial XML given a print writer.
 * Specifically writes the XML for WFS GetCapabilities requests.
 *
 * @author wchen@usgs.gov
 *
 */
public class WFSGetCapabilitiesWriter {

	private PrintWriter response;
	private String fileOutput;
	private final String server;
	private List<WFSRequestType> operationList;
	private List<WFSFeature> featureList;

	/**
	 * Writes the two service sections
	 */
	private void writeServiceInfo() {
		fileOutput += "<ows:ServiceIdentification> "
				+ "<ows:Title>WFS Server on THREDDS</ows:Title> "
				+ "<ows:Abstract>ncWFS uses the NetCDF Java Library to handle WFS requests</ows:Abstract> "
				+ "<ows:ServiceType codeSpace=\"OGC\">WFS</ows:ServiceType> "
				+ "<ows:ServiceTypeVersion>2.0.0</ows:ServiceTypeVersion> "
				+ "<ows:Fees/> "
				+ "<ows:AccessConstraints/> "
		+ "</ows:ServiceIdentification> ";

		fileOutput += "<ows:ServiceProvider> "
				+ "<ows:ProviderName>" + ThreddsConfig.get("serverInformation.hostInstitution.name", "hostInstitution") + "</ows:ProviderName> "
				+ "<ows:ProviderSite xlink:href=\"" + ThreddsConfig.get("serverInformation.hostInstitution.webSite", "") +  "\" xlink:type=\"simple\"/> "
				+ "<ows:ServiceContact/> "
				+ "</ows:ServiceProvider> ";
	}

	/**
	 * Given the parameter operation name, add the operation
	 * to the operations metadata section.
	 */
	private void writeAOperation(WFSRequestType rt) {

		fileOutput += "<ows:Operation name=\"" + rt.toString() + "\"> "
				+ "<ows:DCP> "
				+ "<ows:HTTP> "
				+ "<ows:Get xlink:href=\"" + server + "?\"/> "
				+ "<ows:Post xlink:href=\"" + server + "\"/> "
				+ "</ows:HTTP> "
				+ "</ows:DCP>";
		fileOutput += "</ows:Operation> ";
	}

	/**
	 * Writes a constraint OWS element out.
	 *
	 * @param name of the constraint
	 * @boolean isImplemented or not
	 */
	private void writeAConstraint(String name, boolean isImplemented) {

		String defValue;

		if(isImplemented) defValue = "TRUE"; else defValue = "FALSE";

		fileOutput += "<ows:Constraint name=\"" + name + "\"> "
				+ "<ows:NoValues/> "
				+ "<ows:DefaultValue>" + defValue +"</ows:DefaultValue> "
						+ "</ows:Constraint>";
	}

	/**
	 * Writes headers and service sections
	 */
	private void writeHeadersAndSS() {
		fileOutput += "<wfs:WFS_Capabilities xsi:schemaLocation="
				+ WFSXMLHelper.encQuotes("http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd ")
				+ " xmlns:xsi=" + WFSXMLHelper.encQuotes("http://www.w3.org/2001/XMLSchema-instance")
				+ " xmlns:xlink=" + WFSXMLHelper.encQuotes("http://www.w3.org/1999/xlink")
				+ " xmlns:gml=" + WFSXMLHelper.encQuotes("http://opengis.net/gml")
				+ " xmlns:fes=" + WFSXMLHelper.encQuotes("http://www.opengis.net/fes/2.0")
				+ " xmlns:ogc=" + WFSXMLHelper.encQuotes("http://www.opengis.net/ogc")
				+ " xmlns:ows=" + WFSXMLHelper.encQuotes("http://www.opengis.net/ows/1.1\" xmlns:wfs=\"http://opengis.net/wfs/2.0")
				+ " xmlns=" + WFSXMLHelper.encQuotes("http://www.opengis.net/wfs/2.0")
				+ " version=\"2.0.0\">";
		writeServiceInfo();
	}

	/**
	 * Initiate the response with an XML file with an XML header.
	 */
	public void startXML() {
		fileOutput += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
		writeHeadersAndSS();
	}

	/**
	 * Finish writing the XML file, write the end tag for WFS_Capabilities and append it all to the PrintWriter.
	 *
	 * Once a XML is finished, the WFSDataWriter is no longer usable.
	 */
	public void finishXML() {
		fileOutput += "</wfs:WFS_Capabilities>";
		this.response.append(fileOutput);
		response = null;
		fileOutput = null;
	}

	/**
	 * Given the parameter operation name, add the operation
	 * to the operations metadata section.
	 */
	public void addOperation(WFSRequestType rt) {

		this.operationList.add(rt);
	}

	/**
	 * Takes all added operations and writes an operations metadata section.
	 */
	public void writeOperations() {
		fileOutput += "<ows:OperationsMetadata> ";
		for(WFSRequestType rt : operationList) {
			writeAOperation(rt);
		}

		// Write parameters
		fileOutput += "<ows:Parameter name=\"AcceptVersions\"> "
				+ "<ows:AllowedValues> "
				+ "<ows:Value>2.0.0</ows:Value>"
				+ "</ows:AllowedValues>"
				+ "</ows:Parameter>";

		fileOutput += "<ows:Parameter name=\"AcceptFormats\">"
				+ "<ows:AllowedValues> "
				+ "<ows:Value>text/xml</ows:Value>"
				+ "</ows:AllowedValues>"
				+ "</ows:Parameter>";

		fileOutput += "<ows:Parameter name=\"Sections\"> "
				+ "<ows:AllowedValues> "
				+ "<ows:Value>ServiceIdentification</ows:Value> "
				+ "<ows:Value>ServiceProvider</ows:Value> "
				+ "<ows:Value>OperationsMetadata</ows:Value> "
				+ "<ows:Value>FeatureTypeList</ows:Value> "
				+ "</ows:AllowedValues>"
				+ "</ows:Parameter>";

		fileOutput += "<ows:Parameter name=\"version\"> "
				+ "<ows:AllowedValues> "
				+ "<ows:Value>2.0.0</ows:Value>"
				+ "</ows:AllowedValues>"
				+ "</ows:Parameter>";

		// Write constraints
		writeAConstraint("ImplementsBasicWFS", true);
		writeAConstraint("ImplementsTransactionalWFS", false);
		writeAConstraint("ImplementsLockingWFS", false);
		writeAConstraint("KVPEncoding", false);
		writeAConstraint("XMLEncoding", true);
		writeAConstraint("SOAPEncoding", false);
		writeAConstraint("ImplementsInheritance", false);
		writeAConstraint("ImplementsRemoteResolve", false);
		writeAConstraint("ImplementsResultPaging", false);
		writeAConstraint("ImplementsStandardJoins", false);
		writeAConstraint("ImplementsSpatialJoins", false);
		writeAConstraint("ImplementsTemporalJoins", false);
		writeAConstraint("ImplementsFeatureVersioning", false);
		writeAConstraint("ManageStoredQueries", false);
		writeAConstraint("PagingIsTransactionSafe", false);
		writeAConstraint("QueryExpressions", false);

		fileOutput += "</ows:OperationsMetadata>";
	}

	/**
	 *
	 */
	public void writeFeatureTypes() {
		fileOutput += "<FeatureTypeList> ";

			for(WFSFeature wf : featureList) {
				fileOutput +=
						"<FeatureType> "
					+ 	"<Name>" + wf.getName() + "</Name> "
					+ 	"<Title>" + wf.getTitle() + "</Title> "
					+ 	"<DefaultCRS>" + "urn:ogc:def:crs:EPSG::4326" + "</DefaultCRS>"
					+ 	"<OutputFormats> "
					+ 	"<Format>text/xml; subtype=gml/3.2.1</Format> "
					+ 	"</OutputFormats> "
					+ 	"<ows:WGS84BoundingBox dimensions=\"2\"> "
					+ 	"<ows:LowerCorner>-180 -90</ows:LowerCorner> <ows:UpperCorner>180 90</ows:UpperCorner>"
					+ 	"</ows:WGS84BoundingBox>"
					+	"</FeatureType> ";
			}

		fileOutput += "</FeatureTypeList> ";
	}

	/**
	 * Add a feature to the writer's feature list.
	 *
	 * @param feature to add
	 */
	public void addFeature(WFSFeature feature) {
		this.featureList.add(feature);
	}

	/**
	 * Opens a WFSDataWriter, writes to the HttpResponse given.
	 *
	 * @param response to write to
	 * @param server URI
	 */
	public WFSGetCapabilitiesWriter(PrintWriter response, String server){
		this.response = response;
		this.fileOutput = "";
		this.server = server;
		this.operationList = new ArrayList<WFSRequestType>();
		this.featureList = new ArrayList<WFSFeature>();
	}
}


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

package ucar.nc2.iosp.bufr.writer;

import ucar.nc2.iosp.bufr.*;
import ucar.unidata.io.RandomAccessFile;

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

/**
 * Read BUFR files and split them.
 * Currently only files.
 *
 * @author caron
 * @since 8/26/13
 */
public class BufrSplitter2 {
    File dirOut;
    MessageDispatchDDS dispatcher;
    Formatter out;
    int total_msgs = 0;

    public BufrSplitter2(String dirName, Formatter out) throws IOException {
      this.out = out;
      dirOut = new File(dirName);
      if (dirOut.exists() && !dirOut.isDirectory())  {
        throw new IllegalArgumentException(dirOut+" must be a directory");
      } else if (!dirOut.exists()) {
        if (!dirOut.mkdirs())
          throw new IllegalArgumentException(dirOut+" filed to create");
      }
      dispatcher = new MessageDispatchDDS(null, dirOut);
    }

    // LOOK - needs to be a directory, or maybe an MFILE collection
    public void execute(String filename) throws IOException {
      try (RandomAccessFile mraf = new RandomAccessFile(filename, "r")) {
        MessageScanner scanner = new MessageScanner(mraf);

        while (scanner.hasNext()) {
          Message m = scanner.next();
          if (m == null) continue;
          total_msgs++;
          if (m.getNumberDatasets() == 0) continue;

          // LOOK check on tables complete etc ??

          m.setRawBytes(scanner.getMessageBytes(m));

          // decide what to do with the message
          dispatcher.dispatch(m);
        }

        dispatcher.resetBufrTableMessages();
      }
    }

    public void exit() {
      dispatcher.exit(out);
    }

}


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

package thredds.logs;

import ucar.nc2.constants.CDM;
import ucar.unidata.util.StringUtil2;

import java.io.BufferedReader;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Superclass to read TDS logs
 *
 * @author caron
 * @since Apr 10, 2008
 */
public class LogReader {
  private static SimpleDateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

  public interface LogParser {
    public Log nextLog(BufferedReader reader) throws IOException;
  }

  static public class Log {
    public String getIp() {
      return ip;
    }

    public long getDateMillisec(){
      return date;
    }

    public String getDate() {
      return df.format(new Date(date));
    }

    public String getReferrer() {
      return referrer;
    }

    public String getClient() {
      return client;
    }

    public int getStatus() {
      return returnCode;
    }

    public long getMsecs() {
      return msecs;
    }

    public long getBytes() {
      return sizeBytes;
    }

    public String getPath() {
      return (path == null) ? null : StringUtil2.unescape(path);
    }

    public long date;
    String verb, referrer, client;
    int returnCode;
    long msecs, sizeBytes;
    String ip, path, http;

    public String toCSV() {
      //return ip + "," + date + ",\"" + verb + "\","+ path + "\"," + returnCode + "," + sizeBytes + ",\"" + referrer + "\",\"" + client + "\"," + msecs;
      return ip + "," + getDate() + "," + verb + ",\"" + getPath() + "\"," + returnCode + "," + sizeBytes + ",\"" + referrer + "\",\"" + client + "\"," + msecs;
    }

    public String toString() {
      return ip + " [" + getDate() + "] " + verb + " " + getPath() + " " + http + " " + returnCode + " " + sizeBytes + " " + referrer + " " + client + " " + msecs;
    }

    public void toString(Formatter f) {
      f.format("path = %s%n", path);
      int pos = path.indexOf('?');
      if (pos > 0 ) {
        f.format("  path = %s%n", path.substring(0,pos));
        f.format("  query = %s%n", path.substring(pos+1));
      }
      f.format("%n");
      f.format("date = %s%n", getDate());
      f.format("verb = %s%n", verb);
      f.format("status = %d%n", returnCode);
      f.format("took = %d msecs%n", msecs);
      f.format("size = %d bytes%n", sizeBytes);
      f.format("from address = %s%n", ip);
      f.format("client = %s%n", client);
      f.format("referrer = %s%n", referrer);
    }

  }

  public interface Closure {
    void process(Log log) throws IOException;
  }

  public static class Stats {
    public long total;
    public long passed;
  }

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

  public interface LogFilter {
    boolean pass(Log log);
  }

  public static class DateFilter implements LogFilter {
    long start, end;
    LogReader.LogFilter chain;

    public DateFilter(long start, long end, LogReader.LogFilter chain) {
      this.start = start;
      this.end = end;
      this.chain = chain;
    }

    public boolean pass(LogReader.Log log) {
      if (chain != null && !chain.pass(log))
        return false;

      if ((log.date < start) || (log.date > end))
        return false;

      return true;
    }
  }

  public static class IpFilter implements LogFilter {
    String[] match;
    LogReader.LogFilter chain;

    public IpFilter(String[] match, LogReader.LogFilter chain) {
      this.match = match;
      this.chain = chain;
    }

    public boolean pass(LogReader.Log log) {
      if (chain != null && !chain.pass(log))
        return false;

      for (String s : match)
        if (log.getIp().startsWith(s))
          return false;

      return true;
    }
  }

  public static class ErrorOnlyFilter implements LogFilter {
    LogReader.LogFilter chain;

    public ErrorOnlyFilter(LogReader.LogFilter chain) {
      this.chain = chain;
    }

    public boolean pass(LogReader.Log log) {
      if (chain != null && !chain.pass(log))
        return false;

      int status = log.getStatus();
      if ((status < 400) || (status >= 1000)) return false;

      return true;
    }
  }

  public static class FilterNoop implements LogFilter {
    public boolean pass(LogReader.Log log) {
      return true;
    }
  }
  /////////////////////////////////////////////////////////////////////

  private int maxLines = -1;
  private LogParser parser;

  public LogReader(LogParser parser)  {
    this.parser = parser;
  }

  /**
   * Read all the files in a directory and process them. Files are sorted by filename.
   * @param dir read from this directory
   * @param ff files must pass this filter (may be null)
   * @param closure send each Log to this closure
   * @param logf filter out these Logs (may be null)
   * @param stat accumulate statistics (may be null)
   * @throws IOException on read error
   */
  public void readAll(File dir, FileFilter ff, Closure closure, LogFilter logf, Stats stat) throws IOException {
    File[] files = dir.listFiles();
    if (files == null) {
      System.out.printf("Dir has no files= %s%n", dir);
      return;
    }
    List<File> list = Arrays.asList(files);
    Collections.sort(list);

    for (File f : list) {
      if ((ff != null) && !ff.accept(f)) continue;
      if (f.isDirectory())
        readAll(f, ff, closure, logf, stat);
      else
        scanLogFile(f, closure, logf, stat);
    }
  }

  /**
   * Read a log file.
   * @param file file to read
   * @param closure send each Log to this closure
   * @param logf filter out these Logs (may be null)
   * @param stat accumulate statistics (may be null)
   * @throws IOException on read error
   */
  public void scanLogFile(File file, Closure closure, LogFilter logf, Stats stat) throws IOException {
    try (InputStream ios = new FileInputStream(file)) {
      System.out.printf("-----Reading %s %n", file.getPath());

      BufferedReader dataIS = new BufferedReader(new InputStreamReader(ios,
              CDM.utf8Charset), 40 * 1000);
      int total = 0;
      int count = 0;
      while ((maxLines < 0) || (count < maxLines)) {
        Log log = parser.nextLog(dataIS);
        if (log == null) break;
        total++;

        if ((logf != null) && !logf.pass(log)) continue;

        closure.process(log);
        count++;
      }

      if (stat != null) {
        stat.total += total;
        stat.passed += count;
      }

      System.out.printf("----- %s total requests=%d passed=%d %n", file.getPath(), total, count);
    }
  }

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

  static class MyFilter implements LogFilter {

    public boolean pass(Log log) {
      return log.path.startsWith("/thredds/catalog/");
    }
  }

  static class MyFF implements FileFilter {

    public boolean accept(File f) {
      return f.getPath().endsWith(".log");
    }
  }

  public static void main(String args[]) throws IOException {
    // test
    final LogReader reader = new LogReader( new AccessLogParser());

    long startElapsed = System.nanoTime();
    Stats stats = new Stats();

    reader.readAll(new File("d:/mlode/logs/all/"), new MyFF(), new Closure() {
      long count = 0;
      public void process(Log log) throws IOException {
        if (count % 1000 == 0) System.out.printf("%s %s %s%n", log.path, log.client, log.ip);
        count++;
      }
    }, new MyFilter(), stats);

    long elapsedTime = System.nanoTime() - startElapsed;
    System.out.printf(" total= %d passed=%d%n", stats.total, stats.passed);
    System.out.printf(" elapsed=%d secs%n", elapsedTime / (1000 * 1000 * 1000));
  }
}


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

package ucar.nc2.iosp.gempak;

import ucar.nc2.grib.grib2.*;
import ucar.nc2.iosp.grid.*;

import ucar.unidata.io.RandomAccessFile;

import java.io.*;

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

/**
 * Read a GEMPAK grid file
 *
 * @author Don Murray
 */
public class GempakGridReader extends GempakFileReader {

  /**
   * logger
   */
  private static org.slf4j.Logger log =
          org.slf4j.LoggerFactory.getLogger(GempakGridReader.class);

  /**
   * Grid identifier
   */
  public static final String GRID = "GRID";

  /**
   * Grid analysis block identifier
   */
  public static final String ANLB = "ANLB";

  /**
   * Grid nav block identifier
   */
  public static final String NAVB = "NAVB";

  /**
   * Navigation Block
   */
  private NavigationBlock navBlock;

  /**
   * Navigation Block
   */
  private AnalysisBlock analBlock;

  /**
   * Grid index
   */
  private GridIndex gridIndex;

  /**
   * column headers
   */
  private static final String[] kcolnm = {
          "GDT1", "GTM1", "GDT2", "GTM2", "GLV1", "GLV2", "GVCD", "GPM1",
          "GPM2", "GPM3"
  };

  private final String filename;

  /**
   * Bean ctor.  Need to call init if you want anything to work
   */
  GempakGridReader(String filename) {
    this.filename = filename;
  }

  /**
   * Initialize the file, read in all the metadata (ala DM_OPEN)
   *
   * @param raf       RandomAccessFile to read.
   * @param fullCheck if true, check entire structure
   * @return A GempakGridReader
   * @throws IOException problem reading file
   */
  public static GempakGridReader getInstance(RandomAccessFile raf, boolean fullCheck) throws IOException {
    GempakGridReader ggr = new GempakGridReader(raf.getLocation());
    ggr.init(raf, fullCheck);
    return ggr;
  }

  /**
   * Initialize this reader.  Get the Grid specific info
   *
   * @param fullCheck check to make sure there are grids we can handle
   * @return true if successful
   * @throws IOException problem reading the data
   */
  protected boolean init(boolean fullCheck) throws IOException {

    boolean ok = super.init(fullCheck);
    if (!ok) return false;

    // Modeled after GD_OFIL
    if (dmLabel.kftype != MFGD) {
      logError("not a grid file");
      return false;
    }
    // find the part for GRID
    DMPart part = getPart("GRID");

    if (part == null) {
      logError("No part named GRID found");
      return false;
    }
    int lenhdr = part.klnhdr;
    if (lenhdr > LLGDHD) {
      logError("Grid part header too long");
      return false;
    }
    // int khdrln = lenhdr - 2;

    // check that the column names are correct
    for (int i = 0; i < keys.kkcol.size(); i++) {
      Key colkey = keys.kkcol.get(i);
      if (!colkey.name.equals(kcolnm[i])) {
        logError("Column name " + colkey + " doesn't match " + kcolnm[i]);
        return false;
      }
    }

    if (!fullCheck) {
      return true;
    }

    gridIndex = new GridIndex(filename);
    // Make the NAV and ANAL blocks
    float[] headerArray = getFileHeader(NAVB);
    if (headerArray == null) {
      return false;
    }
    navBlock = new NavigationBlock(headerArray);
    //System.out.println("nav = " + navBlock);
    gridIndex.addHorizCoordSys(navBlock);

    headerArray = getFileHeader(ANLB);
    if (headerArray == null) {
      return false;
    }
    analBlock = new AnalysisBlock(headerArray);

    // Make the grid headers
    // TODO: move this up into GempakFileReader using DM_RHDA
    // and account for the flipping there.
    List<GempakGridRecord> tmpList = new ArrayList<>();
    int[] header = new int[dmLabel.kckeys];
    if ((headers == null) || (headers.colHeaders == null)) {
      return false;
    }
    int gridNum = 0;
    for (int[] fullHeader : headers.colHeaders) {
      gridNum++;  // grid numbers are 1 based
      if ((fullHeader == null) || (fullHeader[0] == IMISSD)) {
        continue;
      }
      // TODO: have GempakGridRecord skip the first word
      System.arraycopy(fullHeader, 1, header, 0, header.length);
      GempakGridRecord gh = new GempakGridRecord(gridNum, header);
      gh.navBlock = navBlock;
      String name = gh.getParameterName();
      //if (name.equals("TMPK") ||
      //    name.equals("UREL") ||
      //    name.equals("VREL") ||
      //    name.equals("PMSL")) {
      tmpList.add(gh);
      //}
    }

    // reset the file size since we've gone through all the grids.
    fileSize = rf.length();

    // find the packing types for these grids
    // TODO: go back to using gridList
    //List gridList = gridIndex.getGridRecords();
    //if ( !gridList.isEmpty()) {
    if (!tmpList.isEmpty()) {
      for (GempakGridRecord gh : tmpList) {
        gh.packingType = getGridPackingType(gh.gridNumber);
        if ((gh.packingType == MDGGRB) || (gh.packingType == MDGRB2)
                || (gh.packingType == MDGNON)) {
          gridIndex.addGridRecord(gh);
        }
      }
    } else {
      return false;
    }

    // check to see if there are any grids that we can handle
    if (gridIndex.getGridRecords().isEmpty()) {
      return false;
    }

    return true;

  }

  /**
   * Run the program
   *
   * @param args [0] filename (required),
   *             [1] variable name (X for default),
   *             [2] X to not list grids
   * @throws IOException problem reading the file
   */
  public static void main(String[] args) throws IOException {
    if (args.length == 0) {
      System.out.println("need to supply a GEMPAK grid file name");
      System.exit(1);
    }

    try {
      GempakGridParameterTable.addParameters("resources/nj22/tables/gempak/wmogrib3.tbl");
      GempakGridParameterTable.addParameters("resources/nj22/tables/gempak/ncepgrib2.tbl");
    } catch (Exception e) {
      System.out.println("unable to init param tables");
    }
    GempakGridReader ggr = getInstance(getFile(args[0]), true);
    String var = "PMSL";
    if ((args.length > 1) && !args[1].equalsIgnoreCase("X")) {
      var = args[1];
    }
    ggr.showGridInfo(args.length != 3);
    GempakGridRecord gh = ggr.findGrid(var);
    if (gh != null) {
      System.out.println("\n" + var + ":");
      System.out.println(gh);
      for (int j = 0; j < 2; j++) {
        System.out.println("Using DP: " + ggr.useDP);

        float[] data = ggr.readGrid(gh);
        if (data != null) {
          System.out.println("# of points = " + data.length);
          int cnt = 0;
          int it = 10;
          float min = Float.POSITIVE_INFINITY;
          float max = Float.NEGATIVE_INFINITY;
          for (int i = 0; i < data.length; i++) {
            if (cnt == it) {
              cnt = 0;
            }
            cnt++;
            if ((data[i] != RMISSD) && (data[i] < min)) {
              min = data[i];
            }
            if ((data[i] != RMISSD) && (data[i] > max)) {
              max = data[i];
            }
          }
          System.out.println("max/min = " + max + "/" + min);
        } else {
          System.out.println("unable to decode grid data");
        }
        ggr.useDP = !ggr.useDP;
      }
    }
  }

  /**
   * Get the grid index
   *
   * @return the GridIndex
   */
  public GridIndex getGridIndex() {
    return gridIndex;
  }

  /**
   * Get the grid count
   *
   * @return the count
   */
  public int getGridCount() {
    return gridIndex.getGridCount();
  }

  /**
   * Get the grid packing type
   *
   * @param gridNumber grid number
   * @return packing type or error number
   * @throws IOException problem reading file
   */
  public int getGridPackingType(int gridNumber) throws IOException {
    // See DM_RDTR
    int irow = 1;  // Always 1 for grids
    if ((gridNumber < 1) || (gridNumber > dmLabel.kcol)) {
      logWarning("bad grid number " + gridNumber);
      return -9;
    }
    int iprt = getPartNumber("GRID");
    if (iprt == 0) {
      logWarning("couldn't find part: GRID");
      return -10;
    }
    // gotta subtract 1 because parts are 1 but List is 0 based
    DMPart part = parts.get(iprt - 1);
    // check for valid data type
    if (part.ktyprt != MDGRID) {
      logWarning("Not a valid type: "
              + GempakUtil.getDataType(part.ktyprt));
      return -21;
    }
    int ilenhd = part.klnhdr;
    int ipoint = dmLabel.kpdata
            + (irow - 1) * dmLabel.kcol * dmLabel.kprt
            + (gridNumber - 1) * dmLabel.kprt + (iprt - 1);
    // From DM_RPKG
    int istart = DM_RINT(ipoint);
    if (istart == 0) {
      return -15;
    }
    int length = DM_RINT(istart);
    int isword = istart + 1;
    if (length <= ilenhd) {
      logWarning("length (" + length + ") is less than header length ("
              + ilenhd + ")");
      return -15;
    } else if (Math.abs(length) > 10000000) {
      logWarning("length is huge: " + length);
      return -34;
    }
    int[] header = new int[ilenhd];
    DM_RINT(isword, header);
    // int nword = length - ilenhd;
    isword += ilenhd;
    // read the data packing type
    return DM_RINT(isword);
  }

  /**
   * Find the first grid with this name
   *
   * @param parm name of grid
   * @return the grid header or null
   */
  public GempakGridRecord findGrid(String parm) {
    List<GridRecord> gridList = gridIndex.getGridRecords();
    if (gridList == null) {
      return null;
    }
    for (GridRecord grid : gridList) {
      GempakGridRecord gh = (GempakGridRecord) grid;
      if (gh.param.trim().equals(parm)) {
        return gh;
      }
    }
    return null;
  }

  /**
   * Read the data
   *
   * @param gr grid record
   * @return the data array
   * @throws IOException problem reading file
   */
  public float[] readGrid(GridRecord gr) throws IOException {

    int gridNumber = ((GempakGridRecord) gr).getGridNumber();
    //int irow = 1;  // Always 1 for grids
    //int icol = gridNumber;
    RData data = DM_RDTR(1, gridNumber, "GRID", gr.getDecimalScale());
    float[] vals = null;
    if (data != null) {
      vals = data.data;
    }
    return vals;
  }

  /**
   * Unpack a packed grid
   *
   * @param isword       starting word (1 based)
   * @param nword        number of words to read
   * @param decimalScale decimal scale
   * @return array of unpacked data or null;
   * @throws IOException problem reading data
   */
  public float[] DM_RPKG(int isword, int nword, int decimalScale)
          throws IOException {
    // from DM_RPKG
    // read the data packing type
    float[] data;
    int ipktyp = DM_RINT(isword);
    int iiword = isword + 1;
    int lendat = nword - 1;
    if (ipktyp == MDGNON) {  // no packing
      data = new float[lendat];
      DM_RFLT(iiword, data);
      return data;
    }
    int iiw;
    int irw;
    if (ipktyp == MDGDIF) {
      iiw = 4;
      irw = 3;
    } else if (ipktyp == MDGRB2) {
      iiw = 4;
      irw = 1;
    } else {
      iiw = 3;
      irw = 2;
    }
    int[] iarray = new int[iiw];
    float[] rarray = new float[irw];
    DM_RINT(iiword, iarray);
    iiword = iiword + iiw;
    lendat = lendat - iiw;
    DM_RFLT(iiword, rarray);
    iiword = iiword + irw;
    lendat = lendat - irw;

    if (ipktyp == MDGRB2) {
      data = unpackGrib2Data(iiword, lendat, iarray, rarray);
      return data;
    }
    int nbits = iarray[0];
    int misflg = iarray[1];
    boolean miss = misflg != 0;
    int kxky = iarray[2];
    // int mword = kxky;
    int kx = 0;
    if (iiw == 4) {
      kx = iarray[3];
    }
    float ref = rarray[0];
    float scale = rarray[1];
    float difmin = 0;
    if (irw == 3) {
      difmin = rarray[2];
    }
    data = unpackData(iiword, lendat, ipktyp, kxky, nbits, ref, scale,
            miss, difmin, kx, decimalScale);
    return data;

  }

  /**
   * Read packed data
   *
   * @param iiword       Starting word  (FORTRAN 1 based)
   * @param nword        Number of words
   * @param ipktyp       Packing type
   * @param kxky         Number of grid points
   * @param nbits        Number of bits
   * @param ref          Reference minimum value of grid
   * @param scale        Scaling factor
   * @param miss         Missing data flag
   * @param difmin       Minimum value of differences
   * @param kx           Number of points in x direction
   * @param decimalScale scale of the values for the units
   * @return unpacked data
   * @throws IOException problem reading file
   */
  private synchronized float[] unpackData(int iiword, int nword,
                                          int ipktyp, int kxky, int nbits,
                                          float ref, float scale,
                                          boolean miss, float difmin,
                                          int kx, int decimalScale)
          throws IOException {
    if (ipktyp == MDGGRB) {
      if (!useDP) {
        return unpackGrib1Data(iiword, nword, kxky, nbits, ref,
                scale, miss, decimalScale);
      } else {
        if (nword * 32 < kxky * nbits) { // to account for badly written files
          nword++;
        }
        int[] ksgrid = new int[nword];
        DM_RINT(iiword, ksgrid);
        return DP_UGRB(ksgrid, kxky, nbits, ref, scale, miss,
                decimalScale);
      }
    } else if (ipktyp == MDGNMC) {
      return null;
    } else if (ipktyp == MDGDIF) {
      return null;
    }
    return null;
  }

  /**
   * flag for using DP_UGRB or not
   */
  public boolean useDP = true;  // removed static - not thread safe jcaron 12/21/14

  /**
   * Unpack grib data packed into ints
   *
   * @param idata        int array of packed data
   * @param kxky         number of output points
   * @param nbits        number of bits per point
   * @param qmin         minimum (reference) value
   * @param scale        parameter scale
   * @param misflg       missing flag
   * @param decimalScale scale of value to jive with units
   * @return the array of unpacked values
   * @throws IOException problem reading from the file
   */
  private synchronized float[] DP_UGRB(int[] idata, int kxky, int nbits,
                                       float qmin, float scale,
                                       boolean misflg, int decimalScale)
          throws IOException {
    float scaleFactor = (decimalScale == 0)
            ? 1.f
            : (float) Math.pow(10.0, -decimalScale);
    //
    //Check for valid input.
    //
    float[] grid = new float[kxky];
    if ((nbits <= 1) || (nbits > 31)) {
      return grid;
    }
    if (scale == 0.) {
      return grid;
    }

    //
    //Compute missing data value.
    //
    int imax = (int) (Math.pow(2, nbits) - 1);
    //
    //Retrieve data points from buffer.
    //
    int iword = 0;
    int ibit = 1;  // 1 based bit position
    for (int i = 0; i < kxky; i++) {
      //
      //    Get the integer from the buffer.
      //
      int jshft = nbits + ibit - 33;
      int idat = 0;
      idat = (jshft < 0)
              ? idata[iword] >>> Math.abs(jshft)
              : idata[iword] << jshft;
      idat = idat & imax;
      //
      //    Check to see if packed integer overflows into next word. LOOK fishy bit operations
      //
      if (jshft > 0) {
        jshft -= 32;
        int idat2 = 0;
        idat2 = idata[iword + 1] >>> Math.abs(jshft);
        idat = idat | idat2;
      }
      //
      //    Compute value of word.
      //
      if ((idat == imax) && misflg) {
        grid[i] = RMISSD;
      } else {
        grid[i] = (qmin + idat * scale) * scaleFactor;
      }
      //
      //    Set location for next word.
      //
      ibit += nbits;
      if (ibit > 32) {
        ibit -= 32;
        iword++;
      }
            /*
            if (i < 25) {
                System.out.println("grid["+i+"]: " + grid[i]);
            }
            */
    }
    return grid;
  }

  /**
   * Read packed Grib1 data using ucar.grib code
   *
   * @param iiword       Starting word  (FORTRAN 1 based)
   * @param nword        number of words
   * @param kxky         size of grid (kx*ky)
   * @param nbits        number of bits per word
   * @param ref          reference value
   * @param scale        scale value
   * @param miss         replace missing
   * @param decimalScale scale of the values
   * @return unpacked data
   * @throws IOException problem reading file
   */
  private float[] unpackGrib1Data(int iiword, int nword, int kxky,
                                  int nbits, float ref, float scale,
                                  boolean miss, int decimalScale)
          throws IOException {
    //System.out.println("decimal scale = " + decimalScale);
    float[] values = new float[kxky];
    bitPos = 0;
    bitBuf = 0;
    next = 0;
    ch1 = 0;
    ch2 = 0;
    ch3 = 0;
    ch4 = 0;
    rf.seek(getOffset(iiword));
    int idat;
    // save a pow call if we can
    float scaleFactor = (decimalScale == 0)
            ? 1.f
            : (float) Math.pow(10.0, -decimalScale);
    //float scaleFactor = (float) Math.pow(10.0, -decimalScale);
    for (int i = 0; i < values.length; i++) {
      idat = bits2UInt(nbits);
      if (miss && (idat == IMISSD)) {
        values[i] = IMISSD;
      } else {
        values[i] = (ref + scale * idat) * scaleFactor;
      }
            /*
            if (i < 25) {
                System.out.println("values[" + i + "] = " + values[i]);
            }
            */
    }
    return values;
  }

  /**
   * Read packed Grib2 data
   *
   * @param iiword Starting word  (FORTRAN 1 based)
   * @param lendat Number of words
   * @param iarray integer packing info
   * @param rarray float packing info
   * @return unpacked data
   * @throws IOException problem reading file
   */
  private float[] unpackGrib2Data(int iiword, int lendat, int[] iarray, float[] rarray) throws IOException {

    long start = getOffset(iiword);
    rf.seek(start);
    Grib2Record gr = makeGribRecord(rf, start);
    float[] data = gr.readData(rf);

    if (((iarray[3] >> 6) & 1) == 0) {  // -y scanning - flip
      data = gb2_ornt(iarray[1], iarray[2], iarray[3], data);
    }

    return data;
  }

  // for GempakGridReader
  private Grib2Record makeGribRecord(RandomAccessFile raf, long start) throws IOException {
    Grib2SectionIndicator is = new Grib2SectionIndicator(start, 0, 0); // apparently not in GEMPAK file (!)

    Grib2SectionIdentification ids = null;
    Grib2SectionLocalUse lus = null;
    Grib2SectionGridDefinition gds = null;
    Grib2SectionProductDefinition pds = null;
    Grib2SectionDataRepresentation drs = null;
    Grib2SectionBitMap bms = null;
    Grib2SectionData dataSection = null;

    raf.seek(start);
    raf.order(RandomAccessFile.BIG_ENDIAN);

    int secLength = raf.readInt();
    if (secLength > 0) {
      ids = new Grib2SectionIdentification(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      lus = new Grib2SectionLocalUse(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      gds = new Grib2SectionGridDefinition(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      pds = new Grib2SectionProductDefinition(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      drs = new Grib2SectionDataRepresentation(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      bms = new Grib2SectionBitMap(raf);
    }

    secLength = raf.readInt();
    if (secLength > 0) {
      dataSection = new Grib2SectionData(raf);
      if (dataSection.getMsgLength() > secLength)  // presumably corrupt
        throw new IllegalStateException("Illegal Grib2SectionData Message Length");
    }

    // LOOK - not dealing with repeated records
    return new Grib2Record(null, is, ids, lus, gds, pds, drs, bms, dataSection, false, Grib2Index.ScanModeMissing);
  }

  /**
   * Print out the navigation block so it looks something like this:
   * <pre>
   *   GRID NAVIGATION:
   *        PROJECTION:          LCC
   *        ANGLES:              25.0   -95.0    25.0
   *        GRID SIZE:           93  65
   *        LL CORNER:           12.19   -133.46
   *        UR CORNER:           57.29    -49.38
   * </pre>
   */
  public void printNavBlock() {
    StringBuilder buf = new StringBuilder("GRID NAVIGATION:");
    if (navBlock != null) {
      buf.append(navBlock.toString());
    } else {
      buf.append("\n\tUNKNOWN GRID NAVIGATION");
    }
    System.out.println(buf.toString());
  }

  /**
   * Print out the analysis block so it looks something like this:
   */
  public void printAnalBlock() {
    StringBuilder buf = new StringBuilder("GRID ANALYSIS BLOCK:");
    if (analBlock != null) {
      buf.append(analBlock.toString());
    } else {
      buf.append("\n\tUNKNOWN ANALYSIS TYPE");
    }
    System.out.println(buf.toString());
  }

  /**
   * Get list of grids
   *
   * @return list of grids
   */
  public List<GridRecord> getGridList() {
    return gridIndex.getGridRecords();
  }

  /**
   * Print out the grids.
   */
  public void printGrids() {
    List<GridRecord> gridList = gridIndex.getGridRecords();
    if (gridList == null)
      return;

    System.out.println("  NUM       TIME1              TIME2           LEVL1 LEVL2  VCORD PARM");
    for (GridRecord aGridList : gridList) {
      System.out.println(aGridList);
    }
  }

  /**
   * List out the grid information (aka GDINFO)
   *
   * @param printGrids print each grid record
   */
  public void showGridInfo(boolean printGrids) {
    List gridList = gridIndex.getGridRecords();
    System.out.println("\nGRID FILE: " + getFilename() + "\n");
    printNavBlock();
    System.out.println("");
    printAnalBlock();
    System.out.println("\nNumber of grids in file:  " + gridList.size());
    System.out.println("\nMaximum number of grids in file:  " + dmLabel.kcol);
    System.out.println("");
    if (printGrids) {
      printGrids();
    }
  }

  /**
   * gb2_ornt
   * <p/>
   * This function checks the fields scanning mode flags and re-orders
   * the grid point values so that they traverse left to right for each
   * row starting at the bottom row.
   * <p/>
   * gb2_ornt ( kx, ky, scan_mode, ingrid, fgrid, iret )
   * <p/>
   * Input parameters:
   * kx              int             Number of columns
   * ky              int             Number of rows
   * scan_mode       int             GRIB2 scanning mode flag
   * *ingrid         float           unpacked GRIB2 grid data
   * <p/>
   * Output parameters:
   * *fgrid      float   Unpacked grid data
   * *iret       int             Return code
   * -40 = scan mode not implemented
   * <p/>
   * Log:
   * S. Gilbert            1/04
   *
   * @param kx        number of points in X
   * @param ky        number of points in Y
   * @param scan_mode scan mode
   * @param ingrid    grid to flip
   * @return grid which starts at lower left
   */
  private float[] gb2_ornt(int kx, int ky, int scan_mode, float[] ingrid) {
    float[] fgrid = new float[ingrid.length];

    int ibeg, jbeg, iinc, jinc, itmp;
    int icnt, jcnt, kcnt, idxarr;

    int idrct, jdrct, consec, boustr;

    idrct = (scan_mode >> 7) & 1;
    jdrct = (scan_mode >> 6) & 1;
    consec = (scan_mode >> 5) & 1;
    boustr = (scan_mode >> 4) & 1;

    if (idrct == 0) {
      ibeg = 0;
      iinc = 1;
    } else {
      ibeg = kx - 1;
      iinc = -1;
    }
    if (jdrct == 1) {
      jbeg = 0;
      jinc = 1;
    } else {
      jbeg = ky - 1;
      jinc = -1;
    }

    kcnt = 0;
    if ((consec == 1) && (boustr == 0)) {
            /*  adjacent points in same column;  each column same direction  */
      for (jcnt = jbeg; ((0 <= jcnt) && (jcnt < ky)); jcnt += jinc) {
        for (icnt = ibeg; ((0 <= icnt) && (icnt < kx));
             icnt += iinc) {
          idxarr = ky * icnt + jcnt;
          fgrid[kcnt] = ingrid[idxarr];
          kcnt++;
        }
      }
    } else if ((consec == 0) && (boustr == 0)) {
            /*  adjacent points in same row;  each row same direction  */
      for (jcnt = jbeg; ((0 <= jcnt) && (jcnt < ky)); jcnt += jinc) {
        for (icnt = ibeg; ((0 <= icnt) && (icnt < kx));
             icnt += iinc) {
          idxarr = kx * jcnt + icnt;
          fgrid[kcnt] = ingrid[idxarr];
          kcnt++;
        }
      }
    } else if ((consec == 1) && (boustr == 1)) {
            /*  adjacent points in same column; each column alternates direction */
      for (jcnt = jbeg; ((0 <= jcnt) && (jcnt < ky)); jcnt += jinc) {
        itmp = jcnt;
        if ((idrct == 1) && (kx % 2 == 0)) {
          itmp = ky - jcnt - 1;
        }
        for (icnt = ibeg; ((0 <= icnt) && (icnt < kx));
             icnt += iinc) {
          idxarr = ky * icnt + itmp;
          fgrid[kcnt] = ingrid[idxarr];
          itmp = (itmp != jcnt)
                  ? jcnt
                  : ky - jcnt - 1;  /* toggle */
          kcnt++;
        }
      }
    } else if ((consec == 0) && (boustr == 1)) {
            /*  adjacent points in same row;  each row alternates direction  */
      if (jdrct == 0) {
        if ((idrct == 0) && (ky % 2 == 0)) {
          ibeg = kx - 1;
          iinc = -1;
        }
        if ((idrct == 1) && (ky % 2 == 0)) {
          ibeg = 0;
          iinc = 1;
        }
      }
      for (jcnt = jbeg; ((0 <= jcnt) && (jcnt < ky)); jcnt += jinc) {
        for (icnt = ibeg; ((0 <= icnt) && (icnt < kx));
             icnt += iinc) {
          idxarr = kx * jcnt + icnt;
          fgrid[kcnt] = ingrid[idxarr];
          kcnt++;
        }
        ibeg = (ibeg != 0)
                ? 0
                : kx - 1;  /* toggle */
        iinc = (iinc != 1)
                ? 1
                : -1;      /* toggle */
      }
    } //else {          logically dead code
      //fgrid = ingrid;
    //}
    return fgrid;

  }

  /**
   * bit position
   */
  private int bitPos = 0;

  /**
   * bit buffer
   */
  private int bitBuf = 0;

  /**
   * bit buffer size
   */
  private int next = 0;

  /**
   * character 1
   */
  private int ch1 = 0;

  /**
   * character 2
   */
  private int ch2 = 0;

  /**
   * character 3
   */
  private int ch3 = 0;

  /**
   * character 4
   */
  private int ch4 = 0;

  /**
   * Convert bits (nb) to Unsigned Int .
   *
   * @param nb number of bits
   * @return int of BinaryDataSection section
   * @throws IOException
   */
  private int bits2UInt(int nb) throws IOException {
    int bitsLeft = nb;
    int result = 0;

    if (bitPos == 0) {
      //bitBuf = raf.read();
      getNextByte();
      bitPos = 8;
    }

    while (true) {
      int shift = bitsLeft - bitPos;
      if (shift > 0) {
        // Consume the entire buffer
        result |= bitBuf << shift;
        bitsLeft -= bitPos;

        // Get the next byte from the RandomAccessFile
        //bitBuf = raf.read();
        getNextByte();
        bitPos = 8;
      } else {
        // Consume a portion of the buffer
        result |= bitBuf >> -shift;
        bitPos -= bitsLeft;
        bitBuf &= 0xff >> (8 - bitPos);  // mask off consumed bits

        return result;
      }
    }                                        // end while
  }                                            // end bits2Int

  /**
   * Get the next byte
   *
   * @throws IOException problem reading the byte
   */
  private void getNextByte() throws IOException {
    if (!needToSwap) {
      // Get the next byte from the RandomAccessFile
      bitBuf = rf.read();
    } else {
      if (next == 3) {
        bitBuf = ch3;
      } else if (next == 2) {
        bitBuf = ch2;
      } else if (next == 1) {
        bitBuf = ch1;
      } else {
        ch1 = rf.read();
        ch2 = rf.read();
        ch3 = rf.read();
        ch4 = rf.read();
        bitBuf = ch4;
        next = 4;
      }
      next--;
    }
  }

  /**
   * Log a warning message
   *
   * @param message the warning message
   */
  private void logWarning(String message) {
    log.warn(rf.getLocation() + ": " + message);
  }

}


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

package opendap.dap;

import java.io.*;

/**
 * This class holds a one-dimensional array of OPeNDAP data types.
 * It is the parent of both DList and DArray.
 * This class uses a PrimitiveVector to hold the data and
 * deserialize it, thus allowing more efficient storage to be used for the
 * primitive types.
 *
 * @author jehamby
 * @version $Revision: 20918 $
 * @see BaseType
 * @see DArray
 * @see PrimitiveVector
 */
abstract public class DVector extends BaseType implements ClientIO {
    /**
     * The values in this DVector, stored in a
     * PrimitiveVector.
     */
    private PrimitiveVector vals;

    /**
     * Constructs a new DVector.
     */
    public DVector() {
        super();
    }

    /**
     * Returns the OPeNDAP type name of the class instance as a String.
     *
     * @return the OPeNDAP type name of the class instance as a String.
     */
    public String getTypeName() {
        return "Vector";
    }

    /**
     * Constructs a new DVector with name n.
     *
     * @param n the name of the variable.
     */
    public DVector(String n) {
        super(n);
    }

    /**
    * The variable of which we are the parent
    */
   private BaseType containedvar;

   public BaseType getContainerVar() {return containedvar;}

   public void setContainerVar(BaseType var)
   {
   if(containedvar != null) throw new RuntimeException("DArray with multiple variables");
   containedvar = var;
   }

    /**
     * Returns the number of elements in the vector.
     *
     * @return the number of elements in the vector.
     */
    public int getLength() {
        if (vals == null)
            return 0;
        else
            return vals.getLength();
    }

    /**
     * Sets the number of elements in the vector.  Allocates a new
     * array of the desired size.  Note that if this is called multiple times,
     * the old array and its contents will be lost!
     *

     * Only called inside of deserialize method or in derived
     * classes on server.
     *
     * @param len the number of elements in the array.
     */
    public void setLength(int len) {
        vals.setLength(len);
    }

    /**
     * Adds a variable to the container.
     *
     * @param v the variable to add.
     */
    public void addVariable(BaseType v) {
        vals = v.newPrimitiveVector();
        setClearName(v.getClearName());
        v.setParent(this);
        setContainerVar(v); // save v for cloning
    }

    /**
     * Returns the PrimitiveVector for this vector.  This can be
     * cast to the appropriate type and used by a OPeNDAP client to read or set
     * individual values in the vector.
     *
     * @return the attached PrimitiveVector.
     */
    public PrimitiveVector getPrimitiveVector() {
        return vals;
    }

    /**
     * Write the variable's declaration in a C-style syntax. This
     * function is used to create textual representation of the Data
     * Descriptor Structure (DDS).  See The OPeNDAP User Manual for
     * information about this structure.
     *
     * @param os         The PrintWriter on which to print the
     *                   declaration.
     * @param space      Each line of the declaration will begin with the
     *                   characters in this string.  Usually used for leading spaces.
     * @param print_semi a boolean value indicating whether to print a
     *                   semicolon at the end of the declaration.
     * @see BaseType#printDecl(PrintWriter, String, boolean)
     */
    //Coverity[CALL_SUPER]
    public void printDecl(PrintWriter os, String space,
                          boolean print_semi, boolean constrained) {

        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
        // and all of the different signatures of printDecl() in BaseType
        // lead to one signature, we must be careful to override that
        // SAME signature here. That way all calls to printDecl() for
        // this object lead to this implementation.

        //os.println("DVector.printDecl()");
        os.print(space + getTypeName());
        vals.printDecl(os, " ", print_semi, constrained);
    }

    /**
     * Prints the value of the variable, with its declaration.  This
     * function is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *
     * @param os           the PrintWriter on which to print the value.
     * @param space        this value is passed to the printDecl method,
     *                     and controls the leading spaces of the output.
     * @param print_decl_p a boolean value controlling whether the
     *                     variable declaration is printed as well as the value.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    public void printVal(PrintWriter os, String space, boolean print_decl_p) {

        if (print_decl_p) {
            printDecl(os, space, false);
            os.print(" = ");
        }

        os.print("{ ");
        vals.printVal(os, "");

        if (print_decl_p)
            os.println("};");
        else
            os.print("}");
    }

    /**
     * Reads data from a DataInputStream. This method is only used
     * on the client side of the OPeNDAP client/server connection.
     *
     * @param source   a DataInputStream to read from.
     * @param sv       the ServerVersion returned by the server.
     * @param statusUI the StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws EOFException      if EOF is found before the variable is completely
     *                           deserialized.
     * @throws IOException       thrown on any other InputStream exception.
     * @throws DataReadException if an unexpected value was read.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException,
            EOFException,
            DataReadException {

        // Because arrays of primitive types (ie int32, float32, byte, etc) are
        // handled in the C++ core using the XDR package we must read the
        // length twice for those types. For BaseType vectors, we should read
        // it only once. This is in effect a work around for a bug in the C++
        // core as the C++ core does not consume 2 length values for the
        // BaseType vectors. Bummer...

        int length;
        length = source.readInt();

        if (!(vals instanceof BaseTypePrimitiveVector)) {
            // because both XDR and OPeNDAP write the length, we must read it twice
            int length2 = source.readInt();
            //LogStream.out.println("array1 length read: "+getName()+" "+length+ " -- "+length2);
            //LogStream.out.println("  array type = : "+vals.getClass().getName());

            // QC the second length
            if (length != length2) {
                throw new DataReadException("Inconsistent array length read: " + length + " != " + length2);
            }
        } /* else {
          LogStream.dbg.println("array2 length read: "+getName()+" "+length);
          LogStream.dbg.println("  array type = : "+vals.getClass().getName());
        } */

        if (length < 0)
            throw new DataReadException("Negative array length read.");
        if (statusUI != null)
            statusUI.incrementByteCount(8);
        vals.setLength(length);
        vals.deserialize(source, sv, statusUI);
    }

    /**
     * Writes data to a DataOutputStream. This method is used
     * primarily by GUI clients which need to download OPeNDAP data, manipulate
     * it, and then re-save it as a binary file.
     *
     * @param sink a DataOutputStream to write to.
     * @throws IOException thrown on any OutputStream
     *                     exception.
     */
    public void externalize(DataOutputStream sink) throws IOException {

        // Because arrays of primitive types (ie int32, float32, byte, etc) are
        // handled in the C++ core using the XDR package we must write the
        // length twice for those types. For BaseType vectors, we should write
        // it only once. This is in effect a work around for a bug in the C++
        // core as the C++ core does not consume 2 length values for thge
        // BaseType vectors. Bummer...
        int length = vals.getLength();
        sink.writeInt(length);

        if (!(vals instanceof BaseTypePrimitiveVector)) {
            // because both XDR and OPeNDAP write the length, we must write it twice
            sink.writeInt(length);
        }

        vals.externalize(sink);
    }

    /**
     * Returns a clone of this Vector.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this object.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        DVector v = (DVector) super.cloneDAG(map);
        v.vals = (PrimitiveVector) cloneDAG(map,vals);
        // clone the container variable
        v.containedvar = (BaseType) cloneDAG(map,containedvar);
        return v;
    }

}


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

import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.DateTimeZone;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

/**
 * A Calendar Date. Replaces java.util.Date.
 * Allows non-standard calendars. Default is Calendar.gregorian.
 * Always in UTC time zone.
 *
 * @author caron
 * @since 3/21/11
 */
@Immutable
public class CalendarDate implements Comparable<CalendarDate> {
  public static final CalendarDate UNKNOWN = CalendarDate.of(0);
  public static final double MILLISECS_IN_SECOND = 1000;
  public static final double MILLISECS_IN_MINUTE = MILLISECS_IN_SECOND * 60;
  public static final double MILLISECS_IN_HOUR = MILLISECS_IN_MINUTE * 60;
  public static final double MILLISECS_IN_DAY = MILLISECS_IN_HOUR * 24;
  public static final double MILLISECS_IN_YEAR = 3.15569259747E10;
  public static final double MILLISECS_IN_MONTH = MILLISECS_IN_YEAR / 12;

  /**
   * Get a CalendarDate representing the present moment
   * @return CalendarDate representing the present moment in UTC
   */
  static public CalendarDate present() {
     return new CalendarDate(null, new DateTime());
  }

  static public CalendarDate present(Calendar cal) {
     return new CalendarDate(cal, new DateTime());
  }

  /**
   * Get Calendar date from fields. Uses UTZ time zone
   * @param cal calendar to use, or null for default
   * @param year any integer
   * @param monthOfYear 1-12
   * @param dayOfMonth 1-31
   * @param hourOfDay  0-23
   * @param minuteOfHour 0-59
   * @param secondOfMinute 0-59
   * @return CalendarDate
   */
  public static CalendarDate of(Calendar cal, int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour, int secondOfMinute) {
    Chronology base = Calendar.getChronology(cal);
    /* if (base == null)
      base = ISOChronology.getInstanceUTC(); // already in UTC
    else
      base = ZonedChronology.getInstance( base, DateTimeZone.UTC); // otherwise wrap it to be in UTC  */

    DateTime dt = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, base);
    if (!Calendar.isDefaultChronology(cal)) dt = dt.withChronology(Calendar.getChronology(cal));
    dt = dt.withZone(DateTimeZone.UTC);
    return new CalendarDate(cal, dt);
  }

  public static CalendarDate withDoy(Calendar cal, int year, int doy, int hourOfDay, int minuteOfHour, int secondOfMinute) {
    Chronology base = Calendar.getChronology(cal);
    /* if (base == null)
      base = ISOChronology.getInstanceUTC(); // already in UTC
    else
      base = ZonedChronology.getInstance( base, DateTimeZone.UTC); // otherwise wrap it to be in UTC  */

    DateTime dt = new DateTime(year, 1, 1, hourOfDay, minuteOfHour, secondOfMinute, base);
    dt = dt.withZone(DateTimeZone.UTC);
    dt = dt.withDayOfYear(doy);
    if (!Calendar.isDefaultChronology(cal)) dt = dt.withChronology(Calendar.getChronology(cal));

    return new CalendarDate(cal, dt);
  }

  /**
   * Create CalendarDate from a java.util.Date.
   * Uses standard Calendar.
   * @param date java.util.Date
   * @return CalendarDate in UTC
   */
  public static CalendarDate of(java.util.Date date) {
    DateTime dt = new DateTime(date, DateTimeZone.UTC) ;
    return new CalendarDate(null, dt);
  }

  /**
   * Create CalendarDate from msecs since epoch
   * Uses standard Calendar.
   * @param msecs milliseconds from 1970-01-01T00:00:00Z
   * @return CalendarDate in UTC
   */
  public static CalendarDate of(long msecs) {
    // Constructs an instance set to the milliseconds from 1970-01-01T00:00:00Z using ISOChronology in the specified time zone.
    DateTime dt = new DateTime(msecs, DateTimeZone.UTC) ;
    return new CalendarDate(null, dt);
  }

  /**
   * Create CalendarDate from msecs since epoch
   * Uses the given  Calendar.
   * @param cal calendar to use, or null for default
   * @param msecs milliseconds from 1970-01-01T00:00:00Z
   * @return CalendarDate in UTC time zone.
   */
  public static CalendarDate of(Calendar cal, long msecs) {
    Chronology base = Calendar.getChronology(cal);
    DateTime dt = new DateTime(msecs, base) ;
    return new CalendarDate(cal, dt);
  }

  /**
   * Get CalendarDate from ISO date string
   * @param calendarName get Calendar from Calendar.get(calendarName). may be null
   * @param isoOrUdunits ISO or udunits date string
   * @return  CalendarDate or null if not valid
   */
  @Nullable
  public static CalendarDate parseUdunitsOrIso(String calendarName, String isoOrUdunits) {
    CalendarDate result;
    try {
      result = parseISOformat(calendarName, isoOrUdunits);
    } catch (Exception e) {
      try {
        result = parseUdunits(calendarName, isoOrUdunits);
      } catch (Exception e2) {
        return  null;
      }
    }
    return result;
  }

  /**
   * Get CalendarDate from ISO date string
   * @param calendarName get Calendar from Calendar.get(calendarName). may be null
   * @param isoDateString ISO date string
   * @return  CalendarDate
   */
  public static CalendarDate parseISOformat(String calendarName, String isoDateString) {
    Calendar cal = Calendar.get(calendarName);
    if (cal == null) cal = Calendar.getDefault();
	  return CalendarDateFormatter.isoStringToCalendarDate(cal, isoDateString);
  }

  /**
   * Get CalendarDate from udunit date string
   * @param calendarName get Calendar from Calendar.get(calendarName). may be null
   * @param udunits must be value (space) udunits string
   * @return  CalendarDate
   */
  public static CalendarDate parseUdunits(String calendarName, String udunits) {
    int pos = udunits.indexOf(' ');
    if (pos < 0) return null;
    String valString = udunits.substring(0, pos).trim();
    String unitString = udunits.substring(pos+1).trim();

    CalendarDateUnit cdu = CalendarDateUnit.of(calendarName, unitString);
    double val = Double.parseDouble(valString);
    return cdu.makeCalendarDate(val);
  }

  // internal use only
  static CalendarDate of(Calendar cal, DateTime dateTime) {
     return new CalendarDate(cal, dateTime);
  }

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

  private final DateTime dateTime;
  private final Calendar cal;

  CalendarDate(Calendar cal, DateTime dateTime) {
    this.cal = cal == null ? Calendar.getDefault() : cal;
    this.dateTime = dateTime;
  }

  public Calendar getCalendar() {
    return cal;
  }

  /**
   * Gets the milliseconds of the datetime instant from the Java epoch
   * of 1970-01-01T00:00:00Z.
   *
   * @return the number of milliseconds since 1970-01-01T00:00:00Z
   */
  public long getMillis() {
    return dateTime.getMillis();
  }

  // package private
  DateTime getDateTime() {
    return dateTime;
  }

  @Override
  public int compareTo(CalendarDate o) {
    return dateTime.compareTo(o.dateTime);
  }

  public boolean isAfter( CalendarDate o) {
    return dateTime.isAfter(o.dateTime);
  }

  public boolean isBefore( CalendarDate o) {
    return dateTime.isBefore(o.dateTime);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof CalendarDate)) return false;
    CalendarDate other = (CalendarDate) o;
    return other.cal == cal && other.dateTime.equals(dateTime);
  }

  @Override
  public int hashCode() {
    int result = 17;
    if (cal != null)
      result += 37 * result + cal.hashCode();
    result += 37 * result + dateTime.hashCode();
    return result;
  }

  /**
   * ISO formatted string
   * @return ISO8601 format (yyyy-MM-ddTHH:mm:ss.SSSZ)
   */
  @Override
  public String toString() {
    return CalendarDateFormatter.toDateTimeStringISO(this);
  }

  /**
   * udunits formatting
   * @return udunits formatted date
   */
  public String getTimeUnits(){
	  return CalendarDateFormatter.toTimeUnits(this);
  }

  /**
   * Get the hour of day (0-23) field for this chronology.
   * @return hour of day (0-23)
   */
  public int getHourOfDay() {
    return dateTime.getHourOfDay();
  }

  /*
      Millisec(PeriodType.millis()), Second(PeriodType.seconds()), Minute(PeriodType.minutes()), Hour(PeriodType.hours()),
    Day(PeriodType.days()), Month(PeriodType.months()), Year(PeriodType.years())
   */
  public int getFieldValue(CalendarPeriod.Field fld) {
    switch (fld) {
      case Day: return dateTime.get(DateTimeFieldType.dayOfMonth());
      case Hour: return dateTime.get(DateTimeFieldType.hourOfDay());
      case Millisec: return dateTime.get(DateTimeFieldType.millisOfSecond());
      case Minute: return dateTime.get(DateTimeFieldType.minuteOfHour());
      case Month: return dateTime.get(DateTimeFieldType.monthOfYear());
      case Second: return dateTime.get(DateTimeFieldType.secondOfMinute());
      case Year: return dateTime.get(DateTimeFieldType.year());
    }
    throw new IllegalArgumentException("unimplemented "+fld);
  }

  public int getDayOfMonth() {
    return dateTime.getDayOfMonth();
  }

  // old style udunits compatible.
  // fixes from https://github.com/jonescc 8/26/14

  /**
   * @deprecated use CalendarDate add(CalendarPeriod period)
   */
  public CalendarDate add(double value, CalendarPeriod.Field unit) {
    switch (unit) {
      case Millisec:
        return new CalendarDate(cal, dateTime.plus(Math.round(value)));
      case Second:
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_SECOND)));
      case Minute:
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_MINUTE)));
      case Hour:
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_HOUR)));
      case Day:
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_DAY)));
      case Month: // LOOK should we throw warning ?
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_MONTH)));
      case Year:  // LOOK should we throw warning ?
        return new CalendarDate(cal, dateTime.plus(Math.round(value * MILLISECS_IN_YEAR)));
    }
    throw new UnsupportedOperationException("period units = "+unit);
  }

  // calendar date field
  public CalendarDate add(CalendarPeriod period) {
    switch (period.getField()) {
      case Millisec:
        return new CalendarDate(cal, dateTime.plusMillis(period.getValue()));
      case Second:
        return new CalendarDate(cal, dateTime.plusSeconds(period.getValue()));
      case Minute:
        return new CalendarDate(cal, dateTime.plusMinutes(period.getValue()));
      case Hour:
        return new CalendarDate(cal, dateTime.plusHours(period.getValue()));
      case Day:
        return new CalendarDate(cal, dateTime.plusDays(period.getValue()));
      case Month:
        return new CalendarDate(cal, dateTime.plusMonths(period.getValue()));
      case Year:
        return new CalendarDate(cal, dateTime.plusYears(period.getValue()));
    }
    throw new UnsupportedOperationException("period units = "+period);
  }

  // calendar date field
  public CalendarDate subtract(CalendarPeriod period) {
    switch (period.getField()) {
      case Millisec:
        return new CalendarDate(cal, dateTime.minusMillis(period.getValue()));
      case Second:
        return new CalendarDate(cal, dateTime.minusSeconds(period.getValue()));
      case Minute:
        return new CalendarDate(cal, dateTime.minusMinutes(period.getValue()));
      case Hour:
        return new CalendarDate(cal, dateTime.minusHours(period.getValue()));
      case Day:
        return new CalendarDate(cal, dateTime.minusDays(period.getValue()));
      case Month:
        return new CalendarDate(cal, dateTime.minusMonths(period.getValue()));
      case Year:
        return new CalendarDate(cal, dateTime.minusYears(period.getValue()));
    }
    throw new UnsupportedOperationException("period units = "+period);
  }

  /**
   * truncate the CalendarDate, by zeroing all the fields that are less than the field.
   * So 2013-03-01T19:30 becomes 2013-03-01T00:00 if the field is "day"
   * @param fld set to 0 all fields less than this one
   * @return truncated result
   */
  public CalendarDate truncate(CalendarPeriod.Field fld) {
    switch (fld) {
      case Minute:
        return CalendarDate.of(cal, dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth(), dateTime.getHourOfDay(), dateTime.getMinuteOfHour(), 0);
      case Hour:
        return CalendarDate.of(cal, dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth(), dateTime.getHourOfDay(), 0, 0);
      case Day:
        return CalendarDate.of(cal, dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth(), 0, 0, 0);
      case Month:
        return CalendarDate.of(cal, dateTime.getYear(), dateTime.getMonthOfYear(), 1, 0, 0, 0);
      case Year:
        return CalendarDate.of(cal, dateTime.getYear(), 1, 1, 0, 0, 0);
    }
    return this;
  }

  /**
   * Get the equivilent java.util.Date
   * @return the equivalent Date
   */
  public java.util.Date toDate() {
    return dateTime.toDate();
  }

  /**
   * Get difference between two calendar dates in millisecs
   * @param o  other calendar date
   * @return  (this minus o) difference in millisecs
   */
  public long getDifferenceInMsecs(CalendarDate o) {
    return dateTime.getMillis() - o.dateTime.getMillis();
  }

  /**
   * Get difference between two calendar dates in given Field units
   * @param o  other calendar date
   * @return  (this minus o) difference in units of this Field
   */
  public long getDifference(CalendarDate o, CalendarPeriod.Field fld) {
    switch (fld) {
      case Millisec:
        return getDifferenceInMsecs(o);
      case Second:
        return (long) (getDifferenceInMsecs(o) / MILLISECS_IN_SECOND);
      case Minute:
        return (long) (getDifferenceInMsecs(o) / MILLISECS_IN_MINUTE);
      case Hour:
        return (long) (getDifferenceInMsecs(o) / MILLISECS_IN_HOUR);
      case Day:
        return (long) (getDifferenceInMsecs(o) / MILLISECS_IN_DAY);

      case Month:
        int tmonth = getFieldValue(CalendarPeriod.Field.Month);
        int omonth = o.getFieldValue(CalendarPeriod.Field.Month);
        int years = (int) this.getDifference(o, CalendarPeriod.Field.Year);
        return tmonth-omonth + 12 * years;

      case Year:
        int tyear = getFieldValue(CalendarPeriod.Field.Year);
        int oyear = o.getFieldValue(CalendarPeriod.Field.Year);
        return tyear - oyear;
    }
    return dateTime.getMillis() - o.dateTime.getMillis();
  }

  public static void main(String[] args) {
    CalendarDate dt;

    /* dt = CalendarDate.parseISOformat("", "2008-08-01 01:00:00Z");
    System.out.printf("%s%n", dt);
    dt = CalendarDate.parseISOformat("", "2005-05-12T00:52:56");
    System.out.printf("%s%n", dt); */

    dt = CalendarDate.parseUdunits(null, "calendar Month since 2012-01-19T18:00:00.000Z");
    System.out.printf("%s%n", dt);
  }

}