/*
 * 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, O
 * 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 ucar.httpservices;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Escape
{
    public static final Charset utf8Charset = Charset.forName("UTF-8");

    // Sets of ascii characters of interest

    // This is set of legal characters that can appear unescaped in a url
    static protected final String _allowableInUrl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$&'()*+,-./:;=?@_~";

    static protected final String _allowableInUrlQuery = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&'()*+,-./:;=?@_~";

    static private final char _URIEscape = '%';

    static private final char _BACKSLASHEscape = '\\';
    static private final String _MustBackslashEscape = "?#./\\\"'";

    /**
     * Replace all characters in the String in not present in the String allowable with
     * their hexidecimal values (encoded as UTF8) and preceeded by the String esc
     *

     * The <cods>esc character may not appear on the allowable list, as if it did it would break the 1:1
     * and onto mapping between the unescaped character space and the escaped characater space.
     *
     * @param in        The string in which to replace characters.
     * @param allowable The set of allowable characters.
     * @param esc       The escape String (typically "%" for a URI or "\" for a regular expression).
     * @param spaceplus True if spaces should be replaced by '+'.
     * @return The modified identifier.
     */

    // Useful constants
    static final byte blank = ((byte) ' ');
    static final byte plus = ((byte) '+');

    private static String xescapeString(String in, String allowable, char esc, boolean spaceplus)
    {
        try {
            StringBuffer out = new StringBuffer();
            int i;
            if(in == null) return null;
            byte[] utf8 = in.getBytes(utf8Charset);
            byte[] allow8 = allowable.getBytes(utf8Charset);
            for(byte b : utf8) {
                if(b == blank && spaceplus) {
                    out.append('+');
                } else {
                    // search allow8
                    boolean found = false;
                    for(byte a : allow8) {
                        if(a == b) {
                            found = true;
                            break;
                        }
                    }
                    if(found) {
                        out.append((char) b);
                    } else {
                        String c = Integer.toHexString(b);
                        out.append(esc);
                        if(c.length() < 2) out.append('0');
                        out.append(c);
                    }
                }
            }

            return out.toString();

        } catch (Exception e) {
            return in;
        }
    }

    private static String escapeString(String in, String allowable)
    {
        return xescapeString(in, allowable, _URIEscape, false);
    }

   /*unused
   private static String escapeString(String in, String allowable, char esc)
   {return xescapeString(in,allowable,esc,false);}
   */

    /**
     * Given a string that contains WWW escape sequences, translate those escape
     * sequences back into ASCII characters. Return the modified string.
     *
     * @param in        The string to modify.
     * @param escape    The character used to signal the begining of an escape sequence.
     *                  param except If there is some escape code that should not be removed by
     *                  this call (e.g., you might not want to remove spaces, %20) use this
     *                  parameter to specify that code. The function will then transform all
     *                  escapes except that one.
     * @param spaceplus True if spaces should be replaced by '+'.
     * @return The modified string.
     */

    private static String xunescapeString(String in, char escape, boolean spaceplus)
    {
        try {
            if(in == null) return null;

            byte[] utf8 = in.getBytes(utf8Charset);
            byte escape8 = (byte) escape;
            byte[] out = new byte[utf8.length]; // Should be max we need

            int index8 = 0;
            for(int i = 0; i < utf8.length; ) {
                byte b = utf8[i++];
                if(b == plus && spaceplus) {
                    out[index8++] = blank;
                } else if(b == escape8) {
                    // check to see if there are enough characters left
                    if(i + 2 <= utf8.length) {
                        b = (byte) (fromHex(utf8[i]) << 4 | fromHex(utf8[i + 1]));
                        i += 2;
                    }
                }
                out[index8++] = b;
            }
            return new String(out, 0, index8, utf8Charset);
        } catch (Exception e) {
            return in;
        }

    }

    private static String unescapeString(String in)
    {
        return xunescapeString(in, _URIEscape, false);
    }

    /*unused
    private static String unescapeString(String in, char escape)
    {return xunescapeString(in, escape,false);}
    */

    static final byte hexa = (byte) 'a';
    static final byte hexf = (byte) 'f';
    static final byte hexA = (byte) 'A';
    static final byte hexF = (byte) 'F';
    static final byte hex0 = (byte) '0';
    static final byte hex9 = (byte) '9';
    static final byte ten = (byte) 10;

    private static byte fromHex(byte b) throws NumberFormatException
    {
        if(b >= hex0 && b <= hex9) return (byte) (b - hex0);
        if(b >= hexa && b <= hexf) return (byte) (ten + (b - hexa));
        if(b >= hexA && b <= hexF) return (byte) (ten + (b - hexA));
        throw new NumberFormatException("Illegal hex character: " + b);
    }

    /**
     * Define the DEFINITIVE URL escape function.
     * Beware, this is a rather complex operation
     *
     * @param url The url string
     * @return The escaped expression.
     */
    static private final Pattern p
            = Pattern.compile("([\\w]+)://([.\\w]+(:[\\d]+)?)([/][^?#])?([?][^#]*)?([#].*)?");

    public static String escapeURL(String surl)
    {
        String protocol = null;
        String authority = null;
        String path = null;
        String query = null;
        String fragment = null;
        if(false) {
            // We split the url ourselves to minimize character dependencies
            Matcher m = p.matcher(surl);
            boolean match = m.matches();
            if(!match) return null;
            protocol = m.group(1);
            authority = m.group(2);
            path = m.group(3);
            query = m.group(4);
            fragment = m.group(5);
        } else {// faster, but may not work quite right
            URI u = null;
            try {
                u = HTTPUtil.parseToURI(surl);
            } catch (URISyntaxException e) {
                return null;
            }
            protocol = u.getScheme();
            authority = u.getAuthority();
            path = u.getPath();
            query = u.getQuery();
            fragment = u.getFragment();
        }
        // Reassemble
        StringBuilder url = new StringBuilder();
        url.append(protocol);
        url.append("://");
        url.append(authority);
        if(path != null && path.length() > 0) {
            // Encode pieces between '/'
            String pieces[] = path.split("[/]", -1);
            for(int i = 0; i < pieces.length; i++) {
                String p = pieces[i];
                if(p == null) p = "";
                if(i > 0) url.append("/");
                url.append(urlEncode(p));
            }
        }
        if(query != null && query.length() > 0) {
            url.append("?");
            url.append(escapeURLQuery(query));
        }
        if(fragment != null && fragment.length() > 0) {
            url.append("#");
            url.append(urlEncode(fragment));
        }
        return url.toString();
    }

    static int nextpiece(String s, int index, String sep)
    {
        index = s.indexOf(sep, index);
        if(index < 0)
            index = s.length();
        return index;
    }

    /**
     * Define the DEFINITIVE URL constraint expression escape function.
     *
     * @param ce The expression to modify.
     * @return The escaped expression.
     */
    public static String escapeURLQuery(String ce)
    {
        try {
            ce = escapeString(ce, _allowableInUrlQuery);
        } catch (Exception e) {
            ce = null;
        }
        return ce;
    }

    /**
     * Define the DEFINITIVE URL constraint expression unescape function.
     *
     * @param ce The expression to unescape.
     * @return The unescaped expression.
     */
    public static String unescapeURLQuery(String ce)
    {
        try {
            ce = unescapeString(ce);
        } catch (Exception e) {
            ce = null;
        }
        return ce;
    }

    /**
     * Define the DEFINITIVE URL escape function.
     * Note that the whole string is escaped, so
     * be careful what you pass into this procedure.
     *
     * @param s The string to modify.
     * @return The escaped expression.
     */
    public static String urlEncode(String s)
    {
        //try {s = URLEncoder.encode(s,"UTF-8");} catch(Exception e) {s = null;}
        s = escapeString(s, _allowableInUrl);
        return s;
    }

    /**
     * Define the DEFINITIVE URL unescape function.
     *
     * @param s The string to unescape.
     * @return The unescaped expression.
     */
    public static String urlDecode(String s)
    {
        try {
            s = URLDecoder.decode(s, "UTF-8");
        } catch (Exception e) {
            s = null;
        }
        return s;
    }

    /**
     * Define the DEFINITIVE URL BACKSLASH unescape function.
     *
     * @param s The string to unescape.
     * @return The unescaped expression.
     */
    public static String backslashDecode(String s)
    {
        StringBuilder buf = new StringBuilder(s);
        int i = 0;
        while(i < buf.length()) {
            if(buf.charAt(i) == '\\') {
                buf.deleteCharAt(i);
            }
            i++;
        }
        return buf.toString();
    }

    /**
     * Define the DEFINITIVE URL BACKSLASH escape function.
     *
     * @param s The string to escape.
     * @return The escaped expression.
     */
    public static String backslashEncode(String s)
    {
        StringBuilder buf = new StringBuilder();
        for(int i = 0; i < s.length(); i++) {
            int c = buf.charAt(i);
            if(_MustBackslashEscape.indexOf(c) >= 0)
                buf.append(_BACKSLASHEscape);
            buf.append((char) c);
        }
        return buf.toString();
    }

}


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

package ucar.nc2.iosp.misc;

import ucar.ma2.*;

import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;

import ucar.nc2.iosp.AbstractIOServiceProvider;

/**
 * AbstractIOSP for lighting data.  Parameters for each stroke are held in
 * a structure.
 *
 * @see "http://www.unidata.ucar.edu/data/lightning.html"
 * @author dmurray
 * @since Nov 12, 2009
 */
public abstract class AbstractLightningIOSP extends AbstractIOServiceProvider {

    /** The time variable name */
    public static final String TIME = "time";

    /** The latitude variable name */
    public static final String LAT = "lat";

    /** The longitude variable name */
    public static final String LON = "lon";

    /** The stroke signal strength (amplitude) variable name */
    public static final String SIGNAL = "sgnl";

    /** The number of strokes variable name */
    public static final String MULTIPLICITY = "mult";

    /** The error ellipse major axis variable name */
    public static final String MAJOR_AXIS = "majorAxis";

    /** The error ellipse minor axis variable name */
    public static final String MINOR_AXIS = "minorAxis";

    /** The error ellipse major axis orientation variable name */
    public static final String ELLIPSE_ANGLE = "ellipseAngle";

    /** The error ellipse major axis orientation variable name */
    public static final String ECCENTRICITY = "eccent";

    /** The record dimension name */
    public static final String RECORD = "record";

    /** time unit */
    public final static String secondsSince1970 =
         "seconds since 1970-01-01 00:00:00";

    /**
     * Make lightning variables
     * @param  ncfile      the netCDF file
     * @param  group       the group (may be null)
     * @param  seq         the sequence to add to
     * @param  name        variable name
     * @param  dataType    the data type
     * @param  dims        dimenstion names
     * @param  longName    the long_name attribute value (a description)
     * @param  cfName      the CF standard_name attribute value (may be null)
     * @param  units       the units attribute value (if null, not added)
     * @param  type        coordinate axis type units (if null, not added)
     *
     * @return the variable
     */
    protected Variable makeLightningVariable(NetcdfFile ncfile, Group group,
                                             Structure seq, String name,
                                             DataType dataType, String dims,
                                             String longName, String cfName,
                                             String units, AxisType type) {
        Variable v = new Variable(ncfile, group, seq, name);
        v.setDataType(dataType);
        v.setDimensions(dims);
        v.addAttribute(new Attribute(CDM.LONG_NAME, longName));
        if (cfName != null) {
            v.addAttribute(new Attribute(CF.STANDARD_NAME, cfName));
        }
        if (units != null) {
            v.addAttribute(new Attribute(CDM.UNITS, units));
        }
        if (type != null) {
            v.addAttribute(new Attribute(_Coordinate.AxisType,
                                         type.toString()));
        }
        return v;
    }

    /**
     * Add the global attributes.  Specific implementations should call super
     * and then add their own.
     * @param ncfile  the file to add to
     */
    protected void addLightningGlobalAttributes(NetcdfFile ncfile) {
        ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, CF.FeatureType.point.toString()));
        ncfile.addAttribute( null, new Attribute(CDM.HISTORY, "Read directly by Netcdf Java IOSP"));
    }

    /**
     * Make the sequence that holds the data.
     * @param the netcdf file to add to.
     * @return the Sequence
     */
    //protected abstract Sequence makeSequence(NetcdfFile ncfile);
}


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

package thredds.util;

import java.io.File;

/**
 * Server side constants
 *
 * @author caron
 * @since 6/6/14
 */
public class Constants {
  public static final String Content_Disposition = "Content-Disposition";

  public static final String Content_Length = "Content-Length";

  //       res.setHeader("Content-Disposition", "attachment; filename=" + path + ".nc");
  public static String setContentDispositionValue(String filename) {
    return "attachment; filename=" + filename;
  }

  /**
   *
   * Get the string value of the file size in bytes
   *
   * @param file file object of the file to be returned
   * @return size of file in bytes
   */
  public static String getContentLengthValue(File file) {
    return Long.toString(file.length());
  }

    //       res.setHeader("Content-Disposition", "attachment; filename=" + path + ".nc");
  public static String setContentDispositionValue(String filename, String suffix) {
    int pos = filename.lastIndexOf('/');
    String outname = (pos > 0) ? filename.substring(pos+1) : filename;
    int pos2 = outname.lastIndexOf('.');
    outname = (pos > 0) ? outname.substring(0,pos2) : outname;
    outname = outname + suffix;
    return setContentDispositionValue(outname);
  }
}


/////////////////////////////////////////////////////////////////////////////
// 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.servers;

import java.util.Vector;
import java.util.Enumeration;
import java.io.*;

import opendap.dap.*;
import opendap.dap.parsers.DDSXMLParser;

/**
 * Holds a OPeNDAP Server Grid value.
 *
 * @author ndp
 * @version $Revision: 19676 $
 * @see BaseType
 */
public abstract class SDGrid extends DGrid implements ServerArrayMethods, RelOps {
    private boolean Synthesized;
    private boolean ReadMe;
    Vector AP;

    /**
     * Constructs a new SDGrid.
     */
    public SDGrid() {
        super();
        Synthesized = false;
        ReadMe = false;
    }

    /**
     * Constructs a new SDGrid with name n.
     *
     * @param n the name of the variable.
     */
    public SDGrid(String n) {
        super(n);
        Synthesized = false;
        ReadMe = false;
    }

    /**
     * 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)
     */
    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.

        boolean isSingle = false;
        boolean isStructure = false;
        boolean isGrid = false;
        boolean psemi = true;

        //os.println("The grid contains "+projectedComponents(true)+" projected components");

        if (constrained && projectedComponents(true) == 0)
            return;

        // If we are printing the declaration of a constrained Grid then check for
        // the case where the projection removes all but one component; the
        // resulting object is a simple array.
        // 2013-2-26: Heimbigner : this is incorrect, even single
        // projected components should be in a structure.

        /* Wrong
            if (constrained && projectedComponents(true) == 1) {
            //os.println("It's a single Array.");
            isSingle = true;
            psemi = print_semi;
        } */

        // If there are M (< N) components (Array and Maps combined) in a N
        // component Grid, send the M components as elements of a Structure.
        // This will preserve the grouping without violating the rules for a
        // Grid.
        else if (constrained && !projectionYieldsGrid(true)) {
            //os.println("It's a Structure.");
            isStructure = true;
        } else {
            // The number of elements in the (projected) Grid must be such that
            // we have a valid Grid object; send it as such.
            //os.println("It's a Grid.");
            isGrid = true;
        }

        if (isGrid) os.println(space + getTypeName() + " {");
        if (isGrid) os.println(space + " ARRAY:");

        if (isStructure) os.println(space + "Structure {");

        ((SDArray) arrayVar).printDecl(os, space + "    ", psemi, constrained);

        if (isGrid) os.println(space + " MAPS:");
        for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
            SDArray sda = (SDArray) e.nextElement();
            sda.printDecl(os, space + "    ", psemi, constrained);
        }

        if (isStructure || isGrid) {
            os.print(space + "} " + getEncodedName());
            if (print_semi)
                os.println(";");
        }

        return;
    }

    /**
     * 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.
     *

     *
 Important Note

     * This method overrides the BaseType method of the same name and
     * type signature and it significantly changes the behavior for all versions
     * of printVal() for this type:
     *  All the various versions of printVal() will only
     * print a value, or a value with declaration, if the variable is
     * in the projection.
     *

     *
In other words, if a call to
     * isProject() for a particular variable returns
     * true then printVal() will print a value
     * (or a declaration and a value).
     *

     *
If isProject() for a particular variable returns
     * false then printVal() is basically a No-Op.
     *

     *

     *
     * @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)
     * @see ServerMethods#isProject()
     */

    public void printVal(PrintWriter os, String space, boolean print_decl_p) {

        if (!isProject())
            return;

        //System.out.println("\nSome Part of this object is projected...");

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

        boolean isStillGrid = projectionYieldsGrid(true);

        os.print("{ ");
        if (isStillGrid) os.print("ARRAY: ");

        if (((SDArray) arrayVar).isProject())
            arrayVar.printVal(os, "", false);

        if (isStillGrid) os.print(" MAPS: ");

        boolean firstPass = true;
        Enumeration e = mapVars.elements();
        while (e.hasMoreElements()) {

            SDArray sda = (SDArray) e.nextElement();

            if (((SDArray) sda).isProject()) {
                if (!firstPass) os.print(", ");
                sda.printVal(os, "", false);
                firstPass = false;
            }

        }
        os.print(" }");

        if (print_decl_p)
            os.println(";");

    }

    /**
     * Adds a variable to the container. This overrides the same method in
     * the parent class DGrid in order to add array projection
     * functionality.
     *
     * @param v    the variable to add.
     * @param part the part of the DGrid to be modified.  Allowed
     *             values are ARRAY or MAPS.
     * @throws IllegalArgumentException if an invalid part was given.
     */
    public void addVariable(BaseType v, int part) {
        super.addVariable(v, part);
    }

    // --------------- Projection Interface

    /**
     * Set the state of this variable's projection. true means
     * that this variable is part of the current projection as defined by
     * the current constraint expression, otherwise the current projection
     * for this variable should be false.
     *
     * @param state true if the variable is part of the current
     *              projection, false otherwise.
     * @param all   If true, set the Project property of all the
     *              members (and their children, and so on).
     * @see CEEvaluator
     */
    @Override
    public void setProject(boolean state, boolean all) {
        setProjected(state);
        if (all) {
            // System.out.println("SDGrid:setProject: Blindly setting Project");
            ((SDArray) arrayVar).setProject(state);

            for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
                ServerMethods sm = (ServerMethods) e.nextElement();
                sm.setProject(state);
            }
        }
    }

// --------------- RelOps Interface

    /**
     * The RelOps interface defines how each type responds to relational
     * operators. Most (all?) types will not have sensible responses to all of
     * the relational operators (e.g. DGrid won't know how to match a regular
     * expression but DString will). For those operators that are nonsensical a
     * class should throw InvalidOperatorException.
     */

    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDGrid!");
    }

    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDGrid!");
    }

    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDGrid!");
    }

    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDGrid!");
    }

    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDGrid!");
    }

    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDGrid!");
    }

    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        throw new InvalidOperatorException("Regular Expression's don't work with the type SDGrid!");
    }

// --------------- FileIO Interface

    /**
     * Set the Synthesized property.
     *
     * @param state If true then the variable is considered a
     *              synthetic variable and no part of OPeNDAP will ever try to read it from a
     *              file, otherwise if false the variable is considered a
     *              normal variable whose value should be read using the
     *              read() method. By default this property is false.
     * @see #isSynthesized()
     * @see #read(String, Object)
     */
    public void setSynthesized(boolean state) {
        Synthesized = state;
    }

    /**
     * Get the value of the Synthesized property.
     *
     * @return true if this is a synthetic variable,
     *         false otherwise.
     */
    public boolean isSynthesized() {
        return (Synthesized);
    }

    /**
     * Set the Read property. A normal variable is read using the
     * read() method. Once read the Read property is
     * true. Use this function to manually set the property
     * value. By default this property is false.
     *
     * @param state true if the variable has been read,
     *              false otherwise.
     * @see #isRead()
     * @see #read(String, Object)
     */
    public void setRead(boolean state) {
        ReadMe = state;
    }

    /**
     * Get the value of the Read property.
     *
     * @return true if the variable has been read,
     *         false otherwise.
     * @see #read(String, Object)
     * @see #setRead(boolean)
     */
    public boolean isRead() {
        return (ReadMe);
    }

    /**
     * Read a value from the named dataset for this variable.
     *
     * @param datasetName String identifying the file or other data store
     *                    from which to read a vaue for this variable.
     * @param specialO    This Object is a goody that is used by Server implementations
     *                    to deliver important, and as yet unknown, stuff to the read method. If you
     *                    don't need it, make it a null.
     * @return true if more data remains to be read, otherwise
     *         false. This is an abtsract method that must be implemented
     *         as part of the installation/localization of a OPeNDAP server.
     * @throws IOException
     * @throws EOFException
     */
    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;

    /**
     * Server-side serialization for OPeNDAP variables (sub-classes of
     * BaseType).
     * This does not send the entire class as the Java Serializable
     * interface does, rather it sends only the binary data values. Other software
     * is responsible for sending variable type information (see DDS).
     *

     * Writes data to a DataOutputStream. This method is used
     * on the server side of the OPeNDAP client/server connection, and possibly
     * 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.
     * @see BaseType
     * @see DDS
     * @see ServerDDS
     */
    public void serialize(String dataset, DataOutputStream sink, CEEvaluator ce, Object specialO)
            throws NoSuchVariableException, DAP2ServerSideException, IOException {

        if (!isRead())
            read(dataset, specialO);

        if (ce.evalClauses(specialO)) {
            if (((ServerMethods) arrayVar).isProject())
                ((ServerMethods) arrayVar).serialize(dataset, sink, ce, specialO);

            for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
                ServerMethods sm = (ServerMethods) e.nextElement();
                if (sm.isProject())
                    sm.serialize(dataset, sink, ce, specialO);
            }
        }
    }

//---------------------------------------------------------------------------------
//------------------------- Array Projection Interface ----------------------------
//..................................................................................

    /**
     * Set the projection information for this dimension. The internal DArray is
     * retrieved and then the DArrayDimension
     * associated with the dimension specified is retrieved and the start
     * stride and stop parameters are passed to its
     * setProjection() method.
     *
     * @param dimension The dimension that is to be modified.
     * @param start     The starting point for the projection of this DArrayDimension.
     * @param stride    The size of the stride for the projection of this DArrayDimension.
     * @param stop      The stopping point for the projection of this DArrayDimension.
     * @see DArray
     * @see DArrayDimension
     */
    public void setProjection(int dimension, int start, int stride, int stop)
            throws InvalidDimensionException, SBHException {
        try {
            DArray a = (DArray) getVar(0);
            DArrayDimension d = a.getDimension(dimension);
            d.setProjection(start, stride, stop);

            DArray map = (DArray) getVar(dimension + 1);
            DArrayDimension mapD = map.getDimension(0);
            mapD.setProjection(start, stride, stop);
        }
        catch (NoSuchVariableException e) {
            throw new InvalidDimensionException("SDGrid.setProjection(): Bad Value for dimension!: "
                    + e.getMessage());
        }
    }

    /**
     * Gets the start value for the projection of the
     * dimension indicated. The parameter
     * dimension is checked against the instance of the
     * SDArray for bounds violation.
     *
     * @param dimension The dimension from whose projection to retrieve the
     *                  start value.
     */
    public int getStart(int dimension) throws InvalidDimensionException {
        try {
            DArray a = (DArray) getVar(0);
            DArrayDimension d = a.getDimension(dimension);
            return (d.getStart());
        }
        catch (NoSuchVariableException e) {
            throw new InvalidDimensionException("SDGrid.getStart(): Bad Value for dimension!: "
                    + e.getMessage());
        }
    }

    /**
     * Gets the stride value for the projection of the
     * dimension indicated. The parameter
     * dimension is checked against the instance of the
     * SDArray for bounds violation.
     *
     * @param dimension The dimension from whose projection to retrieve the
     *                  stride value.
     */
    public int getStride(int dimension) throws InvalidDimensionException {
        try {
            DArray a = (DArray) getVar(0);
            DArrayDimension d = a.getDimension(dimension);
            return (d.getStride());
        }
        catch (NoSuchVariableException e) {
            throw new InvalidDimensionException("SDGrid.getStride(): Bad Value for dimension!: "
                    + e.getMessage());
        }
    }

    /**
     * Gets the stop value for the projection of the
     * dimension indicated. The parameter
     * dimension is checked against the instance of the
     * SDArray for bounds violation.
     *
     * @param dimension The dimension from whose projection to retrieve the
     *                  stop value.
     */
    public int getStop(int dimension) throws InvalidDimensionException {
        try {
            DArray a = (DArray) getVar(0);
            DArrayDimension d = a.getDimension(dimension);
            return (d.getStop());
        }
        catch (NoSuchVariableException e) {
            throw new InvalidDimensionException("SDGrid.getStop(): Bad Value for dimension!: "
                    + e.getMessage());
        }
    }

    /**
     * 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.
     *
     * @see BaseType#printDecl(PrintWriter, String, boolean)
     * @opendap.ddx.experimental
     */
    public void printXML(PrintWriter pw, String pad, 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.

        boolean isSingle = false;
        boolean isStructure = false;
        boolean isGrid = false;
        boolean psemi = true;

        //os.println("The gird contains "+projectedComponents(true)+" projected components");

        if (constrained && projectedComponents(true) == 0)
            return;

        // If we are printing the declaration of a constrained Grid then check for
        // the case where the projection removes all but one component; the
        // resulting object is a simple array.

        if (constrained && projectedComponents(true) == 1) {
            //os.println("It's a single Array.");
            isSingle = true;
        }
        // If there are M (< N) componets (Array and Maps combined) in a N
        // component Grid, send the M components as elements of a Struture.
        // This will preserve the grouping without violating the rules for a
        // Grid.
        else if (constrained && !projectionYieldsGrid(true)) {
            //os.println("It's a Structure.");
            isStructure = true;
        } else {
            // The number of elements in the (projected) Grid must be such that
            // we have a valid Grid object; send it as such.
            //os.println("It's a Grid.");
            isGrid = true;
        }

        if (isGrid) {
            pw.print(pad + "<Grid ");
            if (getEncodedName() != null) {
                pw.print(" name=\"" +
                        DDSXMLParser.normalizeToXML(getEncodedName()) + "\"");
            }
            pw.println(">");
        }

        if (isStructure) {

            pw.print(pad + "<Structure");
            if (getEncodedName() != null) {
                pw.print(" name=\"" +
                        DDSXMLParser.normalizeToXML(getEncodedName()) + "\"");
            }
            pw.println(">");

        }

        Enumeration e = getAttributeNames();

        while (e.hasMoreElements()) {
            String aName = (String) e.nextElement();
            Attribute a = getAttribute(aName);
            if(a!=null)
                a.printXML(pw, pad + "\t", constrained);
        }

        ((SDArray) arrayVar).printXML(pw, pad + (isSingle ? "" : "\t"), constrained);

        if (isGrid) {

            e = mapVars.elements();
            while (e.hasMoreElements()) {
                SDArray map = (SDArray) e.nextElement();
                //Coverity[DEADCODE]
                map.printAsMapXML(pw, pad + (isSingle ? "" : "\t"), constrained);
            }

        } else {

            e = mapVars.elements();
            while (e.hasMoreElements()) {
                SDArray sda = (SDArray) e.nextElement();
                sda.printXML(pw, pad + (isSingle ? "" : "\t"), constrained);
            }

        }

        if (isStructure) {
            pw.println(pad + "</Structure>");
        } else if (isGrid) {
            pw.println(pad + "</Grid>");
        }

        return;
    }

/*

	if(isGrid){

            os.println(space + getTypeName() + " {" );
            os.println(space + " ARRAY:");

	    ((SDArray)arrayVar).printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);

            os.println(space + " MAPS:");

            for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
                SDArray sda = (SDArray)e.nextElement();
                sda.printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);
            }

            os.print(space + "} " + getName());
            os.println(";");

 	}

        if(isStructure){

            os.println(space + "Structure {");
            ((SDArray)arrayVar).printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);

            for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
                SDArray sda = (SDArray)e.nextElement();
                sda.printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);
            }

            os.print(space + "} " + getName());
            os.println(";");

	}

	if(isSingle){

            ((SDArray)arrayVar).printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);

            for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
                SDArray sda = (SDArray)e.nextElement();
                sda.printDecl(os, space , psemi, constrained);
            }

	}

*/

}


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

package ucar.unidata.io;

/**
 * The Swap class provides static methods for swapping the bytes of chars,
 * shorts, ints, longs, floats, and doubles.
 *
 * @author  Kirk Waters
 * @author  Russ Rew, 1998, added documentation
 */

public class Swap {

    /**
     * Returns the short resulting from swapping 2 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the short represented by the bytes
     *                 b[offset+1], b[offset]
     */
    static public short swapShort(byte[] b, int offset) {
        // 2 bytes
        int low  = b[offset] & 0xff;
        int high = b[offset + 1] & 0xff;
        return (short) (high << 8 | low);
    }

    /**
     * Returns the int resulting from reversing 4 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the int represented by the bytes
     *                 b[offset+3], b[offset+2], ..., b[offset]
     */
    static public int swapInt(byte[] b, int offset) {
        // 4 bytes
        int accum = 0;
        for (int shiftBy = 0, i = offset; shiftBy < 32; shiftBy += 8, i++) {
            accum |= (b[i] & 0xff) << shiftBy;
        }
        return accum;
    }

    /**
     * Returns the long resulting from reversing 8 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the long represented by the bytes
     *                 b[offset+7], b[offset+6], ..., b[offset]
     */
    static public long swapLong(byte[] b, int offset) {
        // 8 bytes
        long accum = 0;
        long shiftedval;
        for (int shiftBy = 0, i = offset; shiftBy < 64; shiftBy += 8, i++) {
            shiftedval = ((long) (b[i] & 0xff)) << shiftBy;
            accum      |= shiftedval;
        }
        return accum;
    }

    /**
     * Returns the float resulting from reversing 4 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the float represented by the bytes
     *                 b[offset+3], b[offset+2], ..., b[offset]
     */
    static public float swapFloat(byte[] b, int offset) {
        int accum = 0;
        for (int shiftBy = 0, i = offset; shiftBy < 32; shiftBy += 8, i++) {
            accum |= (b[i] & 0xff) << shiftBy;
        }
        return Float.intBitsToFloat(accum);
    }

    /**
     * Returns the double resulting from reversing 8 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the double represented by the bytes
     *                 b[offset+7], b[offset+6], ..., b[offset]
     */
    static public double swapDouble(byte[] b, int offset) {
        long accum = 0;
        long shiftedval;
        for (int shiftBy = 0, i = offset; shiftBy < 64; shiftBy += 8, i++) {
            shiftedval = ((long) (b[i] & 0xff)) << shiftBy;
            accum      |= shiftedval;
        }
        return Double.longBitsToDouble(accum);
    }

    /**
     * Returns the char resulting from swapping 2 bytes at a specified
     * offset in a byte array.
     *
     * @param  b       the byte array
     * @param  offset  the offset of the first byte
     * @return         the char represented by the bytes
     *                 b[offset+1], b[offset]
     */
    static public char swapChar(byte[] b, int offset) {
        // 2 bytes
        int low  = b[offset] & 0xff;
        int high = b[offset + 1] & 0xff;
        return (char) (high << 8 | low);
    }

    /**
     * Returns the short resulting from swapping 2 bytes of a specified
     * short.
     *
     * @param  s       input value for which byte reversal is desired
     * @return         the value represented by the bytes of s
     *                 reversed
     */
    static public short swapShort(short s) {
        return (swapShort(shortToBytes(s), 0));
    }

    /**
     * Returns the int resulting from reversing 4 bytes of a specified
     * int.
     *
     * @param  v       input value for which byte reversal is desired
     * @return         the value represented by the bytes of v
     *                 reversed
     */
    static public int swapInt(int v) {
        return (swapInt(intToBytes(v), 0));
    }

    /**
     * Returns the long resulting from reversing 8 bytes of a specified
     * long.
     *
     * @param  l       input value for which byte reversal is desired
     * @return         the value represented by the bytes of l
     *                 reversed
     */
    static public long swapLong(long l) {
        return (swapLong(longToBytes(l), 0));
    }

    /**
     * Returns the float resulting from reversing 4 bytes of a specified
     * float.
     *
     * @param  v       input value for which byte reversal is desired
     * @return         the value represented by the bytes of v
     *                 reversed
     */
    static public float swapFloat(float v) {
        int l = swapInt(Float.floatToIntBits(v));
        return (Float.intBitsToFloat(l));
    }

    /**
     * Returns the double resulting from reversing 8 bytes of a specified
     * double.
     *
     * @param  v       input value for which byte reversal is desired
     * @return         the value represented by the bytes of v
     *                 reversed
     */
    static public double swapDouble(double v) {
        long l = swapLong(Double.doubleToLongBits(v));
        return (Double.longBitsToDouble(l));
    }

    /**
     * Convert a short to an array of 2 bytes.
     *
     * @param  v       input value
     * @return         the corresponding array of bytes
     */
    static public byte[] shortToBytes(short v) {
        byte[] b       = new byte[2];
        int    allbits = 255;
        for (int i = 0; i < 2; i++) {
            b[1 - i] = (byte) ((v & (allbits <> i * 8);
        }
        return b;
    }

    /**
     * Convert an int to an array of 4 bytes.
     *
     * @param  v       input value
     * @return         the corresponding array of bytes
     */
    static public byte[] intToBytes(int v) {
        byte[] b       = new byte[4];
        int    allbits = 255;
        for (int i = 0; i < 4; i++) {
            b[3 - i] = (byte) ((v & (allbits <> i * 8);
        }
        return b;
    }

    /**
     * Convert a long to an array of 8 bytes.
     *
     * @param  v       input value
     * @return         the corresponding array of bytes
     */
    static public byte[] longToBytes(long v) {
        byte[] b       = new byte[8];
        long   allbits = 255;
        for (int i = 0; i < 8; i++) {
            b[7 - i] = (byte) ((v & (allbits <> i * 8);
        }
        return b;
    }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.vertical;

import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;

import ucar.nc2.*;
import ucar.unidata.util.Parameter;

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

/**
 * A transformation to a vertical reference coordinate system,
 * such as height or pressure.
 *
 * @author  Unidata Development Team
 */

public abstract class VerticalTransformImpl implements VerticalTransform {

    /** unit string */
    protected String units;

    /** time dimension */
    private Dimension timeDim;

    /**
     * Construct a VerticalCoordinate
     *
     * @param timeDim  time dimension
     */
    public VerticalTransformImpl(Dimension timeDim) {
        this.timeDim = timeDim;
    }

    /**
     * Get the 3D vertical coordinate array for this time step.
     *
     * @param timeIndex the time index. Ignored if !isTimeDependent().
     * @return  vertical coordinate array
     * @throws InvalidRangeException _more_
     * @throws java.io.IOException problem reading the data
     */
    public abstract ucar.ma2.ArrayDouble.D3 getCoordinateArray(int timeIndex)
     throws java.io.IOException, InvalidRangeException;

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

    /**
     * Get the unit string for the vertical coordinate.
     * @return unit string
     */
    public String getUnitString() {
        return units;
    }

    /**
     * Get whether this coordinate is time dependent.
     * @return true if time dependent
     */
    public boolean isTimeDependent() {
        return (timeDim != null);
    }

    /**
     * Get the time Dimension
     *
     * @return time Dimension
     */
    protected Dimension getTimeDimension() {
        return timeDim;
    }

    /**
     * Read the data {@link ucar.ma2.Array} from the variable, at the specified
     * time index if applicable.  If the variable does not have a time
     * dimension, the data array will have the same rank as the Variable.
     * If the variable has a time dimension, the data array will have rank-1.
     *
     * @param v             variable to read
     * @param timeIndex     time index, ignored if !isTimeDependent()
     * @return Array from   the variable at that time index
     *
     * @throws IOException problem reading data
     * @throws InvalidRangeException _more_
     */
    protected Array readArray(Variable v, int timeIndex) throws IOException, InvalidRangeException {
        int[] shape  = v.getShape();
        int[] origin = new int[v.getRank()];

        if (getTimeDimension() != null) {
            int dimIndex = v.findDimensionIndex(getTimeDimension().getShortName());
            if (dimIndex >= 0) {
                shape[dimIndex]  = 1;
                origin[dimIndex] = timeIndex;
                return v.read(origin, shape).reduce(dimIndex);
            }
        }

        return v.read(origin, shape);
    }

    /**
     * Create a subset of this VerticalTransform.
     *
     * @param t_range subset the time dimension, or null if you want all of it
     * @param z_range subset the vertical dimension, or null if you want all of it
     * @param y_range subset the y dimension, or null if you want all of it
     * @param x_range subset the x dimension, or null if you want all of it
     *
     * @return the subsetted VerticalTransform
     *
     * @throws ucar.ma2.InvalidRangeException if any of the range parameters are illegal
     */
    public VerticalTransform subset(Range t_range, Range z_range,
                                    Range y_range, Range x_range)
            throws ucar.ma2.InvalidRangeException {
        return new VerticalTransformSubset(this, t_range, z_range, y_range, x_range);
    }

  protected String getParameterStringValue(List<Parameter> params, String name) {
    for (Parameter a : params) {
      if (name.equalsIgnoreCase(a.getName()))
        return a.getStringValue();
    }
    return null;
  }

  protected boolean getParameterBooleanValue(List<Parameter> params, String name) {
    for (Parameter p : params) {
      if (name.equalsIgnoreCase(p.getName()))
        return Boolean.valueOf(p.getStringValue());
    }
    return false;
  }

}


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

package ucar.nc2.ft.fmrc;

import thredds.inventory.CollectionManagerAbstract;
import thredds.inventory.MCollection;
import ucar.nc2.constants.CDM;
import ucar.nc2.dataset.CoordinateAxis1DTime;
import ucar.nc2.dataset.DatasetUrl;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.GridCoordSystem;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.NetcdfFile;
import ucar.nc2.ncml.NcMLReader;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.units.DateUnit;
import ucar.nc2.constants._Coordinate;

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

import org.jdom2.output.XMLOutputter;
import org.jdom2.output.Format;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import thredds.inventory.MFile;

/**
 * The data inventory of one GridDataset.
 * Track grids, time, vert, ens coordinates.
 * Grids are grouped by the time coordinated that they use.
 * Provides serialization to/from XML.
 * Uses dense time, vert coordinates - just the ones that are in the file.
 *
 * This replaces the older ucar.nc2.dt.fmrc.ForecastModelRunInventory, gets rid of the definition files.
 *
 * Not sure if the vert coords will ever be different across the time coords.
 *
 * Should be immutable, once the file is finished writing.
 *
 * Maybe will go away
 *
 * @author caron
 * @since Jan 11, 2010
 */
public class GridDatasetInv {
  static private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GridDatasetInv.class);
  static private final int REQ_VERSION = 2; // minimum required version, else regenerate XML
  static private final int CURR_VERSION = 2;  // current version

  static private boolean debug = false;  // current version

  public static GridDatasetInv open(MCollection cm, MFile mfile, Element ncml) throws IOException {
    // do we already have it ?
    byte[] xmlBytes = ((CollectionManagerAbstract)cm).getMetadata(mfile, "fmrInv.xml");  // LOOK should we keep this functionality ??
    if (xmlBytes != null) {
      if (logger.isDebugEnabled()) logger.debug(" got xmlFile in cache ="+ mfile.getPath()+ " size = "+xmlBytes.length);
      if (xmlBytes.length < 300) {
        logger.warn(" xmlFile in cache only has nbytes ="+ xmlBytes.length+"; will reread");
        // drop through and regenerate
      } else {
        GridDatasetInv inv = readXML(xmlBytes);

        // check if version required regen
        if (inv.version >= REQ_VERSION) {
          // check if file has changed
          long fileModifiedSecs = mfile.getLastModified() / 1000; // ignore msecs
          long xmlModifiedSecs = inv.getLastModified() / 1000; // ignore msecs
          if (xmlModifiedSecs >= fileModifiedSecs) { // LOOK if fileDate is -1, will always succeed
            if (logger.isDebugEnabled()) logger.debug(" cache ok "+new Date(inv.getLastModified())+" >= "+new Date(mfile.getLastModified())+" for " + mfile.getName());
            return inv; // ok, use it
          } else {
            if (logger.isInfoEnabled()) logger.info(" cache out of date "+new Date(inv.getLastModified())+" < "+new Date(mfile.getLastModified())+" for " + mfile.getName());
          }
        } else {
          if (logger.isInfoEnabled()) logger.info(" version needs upgrade "+inv.version+" < "+REQ_VERSION +" for " + mfile.getName());
        }
      }
    }

    // generate it and save it
    GridDataset gds = null;
    try {
      if (ncml == null) {
        gds = GridDataset.open( mfile.getPath());

      } else {
        NetcdfFile nc = NetcdfDataset.acquireFile(new DatasetUrl(null, mfile.getPath()), null);
        NetcdfDataset ncd = NcMLReader.mergeNcML(nc, ncml); // create new dataset
        ncd.enhance(); // now that the ncml is added, enhance "in place", ie modify the NetcdfDataset
        gds = new GridDataset(ncd);
      }

      // System.out.println("gds dataset= "+ gds.getNetcdfDataset());

      GridDatasetInv inv = new GridDatasetInv(gds, cm.extractDate(mfile));
      String xmlString = inv.writeXML( new Date(mfile.getLastModified()));
      ((CollectionManagerAbstract)cm).putMetadata(mfile, "fmrInv.xml", xmlString.getBytes(CDM.utf8Charset));
      if (logger.isDebugEnabled()) logger.debug(" added xmlFile "+ mfile.getPath()+".fmrInv.xml to cache");
      if (debug) System.out.printf(" added xmlFile %s.fmrInv.xml to cache%n", mfile.getPath());
      // System.out.println("new xmlBytes= "+ xmlString);
      return inv;
    } finally {
      if (gds != null) gds.close();
    }
  }

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

  private String location;
  private int version;
  private final List<TimeCoord> times = new ArrayList<>(); // list of TimeCoord
  private final List<VertCoord> vaxes = new ArrayList<>(); // list of VertCoord
  private final List<EnsCoord> eaxes = new ArrayList<>(); // list of EnsCoord
  private CalendarDate runDate; // date of the run
  private String runTimeString; // string representation of the date of the run
  private Date lastModified;

  private GridDatasetInv() {
  }

  public GridDatasetInv(ucar.nc2.dt.grid.GridDataset gds, CalendarDate runDate) {
    this.location = gds.getLocation();
    this.runDate = runDate;

    NetcdfFile ncfile = gds.getNetcdfFile();
    if (ncfile != null && this.runDate == null) {
      runTimeString = ncfile.findAttValueIgnoreCase(null, _Coordinate.ModelBaseDate, null);
      if (runTimeString == null)
        runTimeString = ncfile.findAttValueIgnoreCase(null, _Coordinate.ModelRunDate, null);

      if (runTimeString != null) {
        this.runDate = DateUnit.parseCalendarDate(runTimeString);
         if (this.runDate == null) {
           logger.warn("GridDatasetInv rundate not ISO date string ({}) file={}", runTimeString, location);
           //throw new IllegalArgumentException(_Coordinate.ModelRunDate + " must be ISO date string " + runTime);
         }
      }

      if (this.runDate == null) {
        this.runDate = gds.getCalendarDateStart(); // LOOK not really right
        logger.warn("GridDatasetInv using gds.getStartDate() for run date = {}", runTimeString, location);
        //log.error("GridDatasetInv missing rundate in file=" + location);
        //throw new IllegalArgumentException("File must have " + _Coordinate.ModelBaseDate + " or " + _Coordinate.ModelRunDate + " attribute ");
      }
    }

    if (this.runDate == null) {
      throw new IllegalStateException("No run date");
    }

    this.runTimeString = this.runDate.toString();

    // add each variable, collect unique time and vertical axes
    for (GridDatatype gg : gds.getGrids()) {
      GridCoordSystem gcs = gg.getCoordinateSystem();
      Grid grid = new Grid(gg.getFullName());

      // LOOK: Note this assumes a dense coordinate system
      CoordinateAxis1DTime axis = gcs.getTimeAxis1D();
      if (axis != null) {
        TimeCoord tc = getTimeCoordinate(axis);
        tc.addGridInventory(grid);
        grid.tc = tc;
      }

     CoordinateAxis1D vaxis = gcs.getVerticalAxis();
      if (vaxis != null) {
        grid.vc = getVertCoordinate(vaxis);
      }

     /* not yet
     CoordinateAxis1D eaxis = gcs.getEnsembleAxis();
     if (eaxis != null) {
       grid.ec = getEnsCoordinate(eaxis);
     } */

    }

    // assign sequence number
    int seqno = 0;
    for (TimeCoord tc : times) {
      tc.setId(seqno++);
    }

  }

  public String toString() {
    return location;
  }

  public String getLocation() {
    return location;
  }

  public long getLastModified() {
    return lastModified.getTime();
  }

  /**
   * Get the date of the ForecastModelRun
   *
   * @return the date of the ForecastModelRun
   */
  public CalendarDate getRunDate() {
    return runDate;
  }

  /**
   * Get string representation of the date of the ForecastModelRun
   *
   * @return string representation of the date of the ForecastModelRun
   */
  public String getRunDateString() {
    return runTimeString;
  }

  /**
   * Get a list of unique TimeCoords, which contain the list of variables that all use that TimeCoord.
   *
   * @return list of TimeCoord
   */
  public List<TimeCoord> getTimeCoords() {
    return times;
  }

  /**
   * Get a list of unique VertCoords.
   *
   * @return list of VertCoord
   */
  public List<VertCoord> getVertCoords() {
    return vaxes;
  }

  public Grid findGrid(String name) {
    for (TimeCoord tc : times) {
      List<Grid> grids = tc.getGridInventory();
      for (Grid g : grids) {
        if (g.name.equals(name))
          return g;
      }
    }
    return null;
  }

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

  private TimeCoord getTimeCoordinate(CoordinateAxis1DTime axis) {
    // check for same axis
    for (TimeCoord tc : times) {
      if (tc.getAxisName().equals(axis.getFullName()))
        return tc;
    }

    // check for same offsets
    TimeCoord want = new TimeCoord(runDate, axis);
    for (TimeCoord tc : times) {
      if ((tc.equalsData(want)))
        return tc;
    }

    // its a new one
    times.add(want);
    return want;
  }

  Grid makeGrid(String gridName) {
    return new Grid(gridName);
  }

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

  /**
   * A Grid variable has a name, timeCoord and optionally a Vertical and Ensemble Coordinate
   */
  public class Grid implements Comparable {
    final String name;
    TimeCoord tc = null; // time coordinates reletive to getRunDate()
    EnsCoord ec = null; // optional
    VertCoord vc = null; // optional

    private Grid(String name) {
      this.name = name;
    }

    public String getName() { return name; }

    public String getLocation() { return location; }

    public String getTimeCoordName() {
      return  (tc == null) ? "" : tc.getName();
    }

    public String getVertCoordName() {
      return (vc == null) ? "" : vc.getName();
    }

    public int compareTo(Object o) {
      Grid other = (Grid) o;
      return name.compareTo(other.name);
    }

    public int countTotal() {
      int ntimes = tc.getNCoords();
      return ntimes * getVertCoordLength();
    }

    public String toString() { return name; }

   /*  public String showCount() {
      int ntimes = tc.getOffsetHours().length;
      int nverts = getVertCoordLength();
      return countTotal()+" ("+ntimes+" x "+nverts+") " + tc;
    }

    public boolean hasOffset(double want) {
      for (double got : tc.getOffsetHours() ) {
        if (Misc.nearlyEquals(want, got)) return true;
      }
      return false;
    } */

    public int getVertCoordLength() {
      return (vc == null) ? 1 : vc.getValues1().length;
    }

    public TimeCoord getTimeCoord() {
      return tc;
    }

    public GridDatasetInv getFile() { return GridDatasetInv.this; }

   /*  public int countInventory(double hourOffset) {
      int timeIndex = tc.findIndex(hourOffset);
      if (timeIndex < 0)
        return 0;

      return getVertCoordLength();
    } */

    /*
     * Get inventory as an array of vert coords, at a particular time coord = hourOffset
     *
     * @param hourOffset : may or may not be in the list of time coords
     * @return array of vert coords. NaN = missing; -0.0 = surface.
     *
    public double[] getVertCoords(double hourOffset) {

      int timeIndex = tc.findIndex(hourOffset);
      if (timeIndex < 0)
        return new double[0]; // if not in list of time coordinates, then entire inventory is missing

      if (vc == null) {
        double[] result = new double[1]; // if 2D return -0.0
        result[0] = -0.0;
        return result;
      }

      return vc.getValues1().clone();
    } */
  }

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

  private VertCoord getVertCoordinate(int wantId) {
    if (wantId < 0) return null;
    for (VertCoord vc : vaxes) {
      if (vc.getId() == wantId)
        return vc;
    }
    return null;
  }

  private VertCoord getVertCoordinate(CoordinateAxis1D axis) {
    for (VertCoord vc : vaxes) {
      if (vc.getName().equals(axis.getFullName())) return vc;
    }

    VertCoord want = new VertCoord(axis);
    for (VertCoord vc : vaxes) {
      if ((vc.equalsData(want))) return vc;
    }

    // its a new one
    vaxes.add(want);
    return want;
  }

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

  private EnsCoord getEnsCoordinate(int ens_id) {
    if (ens_id < 0) return null;
    for (EnsCoord ec : eaxes) {
      if ((ec.getId() == ens_id))
        return ec;
    }
    return null;
  }

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

  /**
   * Write the XML representation to a local file.
   *
   * @param filename wite to this local file
   * @throws IOException on io error
   *
  public void writeXML(String filename) throws IOException {
    OutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
    XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
    fmt.output(writeDocument(), out);
    out.close();
  }

  /**
   * Write the XML representaion to an OutputStream.
   *
   * @param out write to this OutputStream
   * @throws IOException on io error
   *
  public void writeXML(OutputStream out) throws IOException {
    XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
    fmt.output(writeDocument(), out);
  }

  public void writeXML(File f) throws IOException {
    FileOutputStream out = new FileOutputStream(f);
    writeXML(out);
    out.close();
  }  */

  /**
   * Write the XML representation to a String.
   *
   * @return the XML representation to a String.
   */
  public String writeXML(Date lastModified) {
    XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
    return fmt.outputString(writeDocument(lastModified));
  }

  /**
   * Create the XML representation of the GridDatasetInv
   *
   * @return the XML representation as a Document
   */
  Document writeDocument(Date lastModified) {
    Element rootElem = new Element("gridInventory");
    Document doc = new Document(rootElem);
    rootElem.setAttribute("location", location);
    rootElem.setAttribute("runTime", runTimeString);
    if (lastModified != null) {
      rootElem.setAttribute("lastModified", CalendarDateFormatter.toDateTimeString(lastModified));
    }
    rootElem.setAttribute("version", Integer.toString(CURR_VERSION));

    // list all the vertical coords
    Collections.sort(vaxes);
    int count = 0;
    for (VertCoord vc : vaxes) {
      vc.setId(count++);
      Element vcElem = new Element("vertCoord");
      rootElem.addContent(vcElem);
      vcElem.setAttribute("id", Integer.toString(vc.getId()));
      vcElem.setAttribute("name", vc.getName());
      if (vc.getUnits() != null)
        vcElem.setAttribute(CDM.UNITS, vc.getUnits());

      StringBuilder sbuff = new StringBuilder();
      double[] values1 = vc.getValues1();
      double[] values2 = vc.getValues2();
      for (int j = 0; j < values1.length; j++) {
        if (j > 0) sbuff.append(" ");
        sbuff.append(Double.toString(values1[j]));
        if (values2 != null) {
          sbuff.append(",");
          sbuff.append(Double.toString(values2[j]));
        }
      }
      vcElem.addContent(sbuff.toString());
    }

    // list all the time coords
    count = 0;
    for (TimeCoord tc : times) {
      tc.setId(count++);
      Element timeElement = new Element("timeCoord");
      rootElem.addContent(timeElement);
      timeElement.setAttribute("id", Integer.toString(tc.getId()));
      timeElement.setAttribute("name", tc.getName());
      timeElement.setAttribute("isInterval", tc.isInterval() ? "true" : "false");

      Formatter sbuff = new Formatter();
      if (tc.isInterval()) {
        double[] bound1 = tc.getBound1();
        double[] bound2 = tc.getBound2();
        for (int j = 0; j < bound1.length; j++)
          sbuff.format((Locale) null, "%f %f,", bound1[j], bound2[j]);

      } else {
        for (double offset : tc.getOffsetTimes())
          sbuff.format((Locale) null, "%f,", offset);
      }
      timeElement.addContent(sbuff.toString());

      List<GridDatasetInv.Grid> vars = tc.getGridInventory();
      Collections.sort(vars);
      for (Grid grid : vars) {
        Element varElem = new Element("grid");
        timeElement.addContent(varElem);
        varElem.setAttribute("name", grid.name);
        if (grid.ec != null)
          varElem.setAttribute("ens_id", Integer.toString(grid.ec.getId()));
        if (grid.vc != null)
          varElem.setAttribute("vert_id", Integer.toString(grid.vc.getId()));
      }
    }

    return doc;
  }

  /**
   * Construct a GridDatasetInv from its XML representation
   *
   * @param xmlString the xml string
   * @return ForecastModelRun
   * @throws IOException on io error
   */
  private static GridDatasetInv readXML(byte[] xmlString) throws IOException {
    InputStream is = new BufferedInputStream(new ByteArrayInputStream(xmlString));
    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(is);
    } catch (JDOMException e) {
      throw new IOException(e.getMessage() + " reading from XML ");
    }

    Element rootElem = doc.getRootElement();
    GridDatasetInv fmr = new GridDatasetInv();
    fmr.runTimeString = rootElem.getAttributeValue("runTime");
    fmr.location = rootElem.getAttributeValue("location");
    if (fmr.location == null)
      fmr.location = rootElem.getAttributeValue("name"); // old way
    String lastModifiedS = rootElem.getAttributeValue("lastModified");
    if (lastModifiedS != null)
      fmr.lastModified = CalendarDateFormatter.isoStringToDate(lastModifiedS);
    String version = rootElem.getAttributeValue("version");
    fmr.version = (version == null) ? 0 : Integer.parseInt(version);
    if (fmr.version < REQ_VERSION) return fmr;

    fmr.runDate = DateUnit.parseCalendarDate(fmr.runTimeString);

    java.util.List<Element> vList = rootElem.getChildren("vertCoord");
    for (Element vertElem : vList) {
      VertCoord vc = new VertCoord();
      fmr.vaxes.add(vc);
      vc.setId( Integer.parseInt(vertElem.getAttributeValue("id")));
      vc.setName(vertElem.getAttributeValue("name"));
      vc.setUnits(vertElem.getAttributeValue(CDM.UNITS));

      // parse the values
      String values = vertElem.getTextNormalize();
      StringTokenizer stoke = new StringTokenizer(values);
      int n = stoke.countTokens();
      double[] values1 = new double[n];
      double[] values2 = null;
      int count = 0;
      while (stoke.hasMoreTokens()) {
        String toke = stoke.nextToken();
        int pos = toke.indexOf(',');
        if (pos < 0)
          values1[count] = Double.parseDouble(toke);
        else {
          if (values2 == null)
            values2 = new double[n];
          String val1 = toke.substring(0, pos);
          String val2 = toke.substring(pos + 1);
          values1[count] = Double.parseDouble(val1);
          values2[count] = Double.parseDouble(val2);
        }
        count++;
      }
      vc.setValues1(values1);
      vc.setValues2(values2);
    }

    java.util.List<Element> tList = rootElem.getChildren("timeCoord");
    for (Element timeElem : tList) {
      TimeCoord tc = new TimeCoord(fmr.runDate);
      fmr.times.add(tc);
      tc.setId(Integer.parseInt(timeElem.getAttributeValue("id")));
      String s = timeElem.getAttributeValue("isInterval");
      boolean isInterval = (s != null) && (s.equals("true"));

      if (isInterval) {
        String boundsAll = timeElem.getTextNormalize();
        String[] bounds = boundsAll.split(",");
        int n = bounds.length;
        double[] bound1 = new double[n];
        double[] bound2 = new double[n];
        int count = 0;
        for (String b : bounds) {
          String[] value = b.split(" ");
          bound1[count] = Double.parseDouble(value[0]);
          bound2[count] = Double.parseDouble(value[1]);
          count++;
        }
        tc.setBounds(bound1, bound2);

      } else {
        String values = timeElem.getTextNormalize();
        String[] value = values.split(",");
        int n = value.length;
        double[] offsets = new double[n];
        int count = 0;
        for (String v : value)
          offsets[count++] = Double.parseDouble(v);
        tc.setOffsetTimes(offsets);
      }

      //get the variable names
      List<Element> varList = timeElem.getChildren("grid");
      for (Element vElem : varList) {
        Grid grid = fmr.makeGrid(vElem.getAttributeValue("name"));
        if (vElem.getAttributeValue("ens_id") != null)
          grid.ec = fmr.getEnsCoordinate( Integer.parseInt(vElem.getAttributeValue("ens_id")));
        if (vElem.getAttributeValue("vert_id") != null)
          grid.vc = fmr.getVertCoordinate( Integer.parseInt(vElem.getAttributeValue("vert_id")));
        tc.addGridInventory(grid);
        grid.tc = tc;
      }
    }

    return fmr;
  }

  public static void main(String[] args) {
    String values = "1,2,3,4";
    String[] value = values.split("[,]");
    for (String s : value)
      System.out.printf("%s%n", s);
  }

}


/*
 * Copyright (c) 1998 - 2010. University Corporation for Atmospheric Research/Unidata
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "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 UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*****************************************/
/* Note: Much of the naming is here to keep the oc parser
   parallel to this one
*/

package opendap.dap.parsers;

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

import java.io.*;

class Dap2Lex implements Dap2Parser.Lexer
{

static final boolean DAP2STRING = true;

static final int CONTEXTLEN = 20; // yyerror shows last CONTEXTLEN characters of input

// Define some charsets
static final String alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static final String numeric = "0123456789";

static final String dapSpecLegal = "_!~*'-\""; // legal id chars as defined in dap2 spec
                                               // In practice, people abuse this abominably

// First character in SCAN_WORD => larger character set than dapSpecLegal.
static final String wordchars1 = alpha + numeric + dapSpecLegal
                                 + "-+."   // possible numeric characters
                                 + "_"   // less strict allows _ in identifiers
                                 + "\\"   // why?  maybe because some old codes use \\ escaping
                                 + "%"    // for escaped characters
                                 + "/" ;  // Add '/' to support group ids.

// First and non-first legal characters for dds identifier
static final String ddswordchars1 = wordchars1 + "#"; // why sharp?
static final String ddswordcharsn = ddswordchars1;

// Non-first legal characters for das identifiers
static final String daswordcharsn = ddswordcharsn
                                    + ":()"; // Experimentally observed

static final String worddelims = "{}[]:;=,";

/**
 * **********************************************
 */
/* Hex digits */
static final String hexdigits = "0123456789abcdefABCDEF";

static final String[] keywords = new String[]{
        "alias",
        "array",
        "attributes",
        "byte",
        "code",
        "dataset",
        "error",
        "float32",
        "float64",
        "grid",
        "int16",
        "int32",
        "maps",
        "message",
        "program",
        "program_type",
        "sequence",
        "string",
        "structure",
        "uint16",
        "uint32",
        "url",
        null
};

static final int[] keytokens = new int[]{
        SCAN_ALIAS,
        SCAN_ARRAY,
        SCAN_ATTR,
        SCAN_BYTE,
        SCAN_CODE,
        SCAN_DATASET,
        SCAN_ERROR,
        SCAN_FLOAT32,
        SCAN_FLOAT64,
        SCAN_GRID,
        SCAN_INT16,
        SCAN_INT32,
        SCAN_MAPS,
        SCAN_MESSAGE,
        SCAN_PROG,
        SCAN_PTYPE,
        SCAN_SEQUENCE,
        SCAN_STRING,
        SCAN_STRUCTURE,
        SCAN_UINT16,
        SCAN_UINT32,
        SCAN_URL
};

//////////////////////////////////////////////////
/**
 * Equivalent of StringReader that allows for better
 * access to position info
 */

static class TextStream
{

// Don't bother with getters
String text = null; // source of text to lex
int mark = 0;
int next = 0; // next unread character
int len = 0;

public TextStream()
{
}
public String around()
{
   String prefix = text.substring(mark-10,mark);
   String suffix = text.substring(mark,mark+10);
   return prefix + "|"+suffix;
}

public void setText(String text)
{
    this.text = text;
    this.len = text.length();
}

public String toString() {return text;}

boolean isEof()
{
    return next >= len;
}

int
peek()
{
    if(next >= len) return 0;
    int c = text.charAt(next);
    return c;
}

void
backup()
{
    if(next <= 0) next = 0;
    else
        next--;
}

int
read()
{
    if(next >= len) return 0;
    int c = text.charAt(next);
    next++;
    return c;
}

void
mark()
{
    this.mark = this.next;
}

}

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

/**
 * **********************************************
 * Per-lexer state
 */

Dap2Parse parsestate = null; // our parent parser

Object lval = null;

TextStream text = null;
int lineno = 0;
int charno = 0;
StringBuilder yytext = null;

// Per-instance set of chars (after first)
// Differs between DAS and DDS.
String wordcharsn = ddswordcharsn;

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

/* Constructor(s) */
public Dap2Lex(Dap2Parse state)
{
    reset(state);
}

/* Reset the lexer */

public void reset(Dap2Parse state)
{
    this.parsestate = state;
    this.text = new TextStream();
    yytext = new StringBuilder();
    lval = null;
}

/* Get/Set */

void setText(String text) {this.text.setText(text);}

public String getInput()
{
    return text.text;
}

void
dassetup()
        throws ParseException
{
    wordcharsn = daswordcharsn;
}

/**
 * Entry point for the scanner.      Returns the token identifier corresponding
 * to the next token and prepares to return the semantic value
 * of the token.
 * @return the token identifier corresponding to the next token.
 * This is part of the Lexer interface
 */

public int
yylex()
        throws ParseException
{
    int token;
    int c;
    token = 0;
    yytext.setLength(0);
    text.mark();

    try {

        token = -1;
        while (token < 0 && (c = text.read()) > 0) {
            if (c == '\n') {
                lineno++;
            } else if (c <= ' ' || c == '\177') {
                /* whitespace: ignore */
            } else if (c == '#') {
                /* single line comment */
                for (; ;) {
                    c = text.read();
                    if (c == '\n' || c == '\0') break;
                }
            } else if (worddelims.indexOf(c) >= 0) {
                token = c;
            } else if (c == '"') {
                boolean more = true;
                /* We have a string token; will be reported as SCAN_WORD */
                while (more && (c = text.read()) > 0) {
                    if (DAP2STRING) {/* Implement DAP2 standard */
                        switch  (c) {
                        case '"':
                            more = false;
                            break;
                        case '\\':
                            c = text.read();
                            if (c < 0) more = false;
                            break;
                        case '(':
                        default: break;
                        }
                    } else {// not used : Implement a more java/c like alternative for string encoding
                        switch (c) {
                        case '"':
                            more = false;
                            break;
                        case '\\':
                            c = text.read();
                            switch (c) {
                            case 'r':
                                c = '\r';
                                break;
                            case 'n':
                                c = '\n';
                                break;
                            case 'f':
                                c = '\f';
                                break;
                            case 't':
                                c = '\t';
                                break;
                            case 'x': {
                                int d1, d2;
                                c = text.read();
                                d1 = tohex(c);
                                if (d1 < 0) {
                                    throw new ParseException("Illegal \\xDD in TOKEN_STRING");
                                } else {
                                    c = text.read();
                                    d2 = tohex(c);
                                    if (d2 < 0) {
                                        throw new ParseException("Illegal \\xDD in TOKEN_STRING");
                                    } else {
                                        c = ((d1) << 4) | d2;
                                    }
                                }
                            }
                            break;
                            default:
                                break;
                            }
                            break;
                        default:
                            break;
                        }
                    }
                    if (more) yytext.append((char) c);
                }
                token = WORD_STRING;

            } else if (wordchars1.indexOf(c) >= 0) {
                yytext.append((char) c);
                /* we have a SCAN_WORD (== identifier | number) */
                while ((c = text.read()) > 0) {
                    if (wordcharsn.indexOf(c) < 0) {
                        text.backup();
                        break;
                    }
                    yytext.append((char) c);
                }
                token = WORD_WORD; /* assume */
                /* check for keyword */
                String tmp = yytext.toString();
                for (int i = 0; ; i++) {
                    if (keywords[i] == null) break;
                    if (keywords[i].equalsIgnoreCase(tmp)) {
                        token = keytokens[i];
                        break;
                    }
                }
            } else { /* illegal */
                String msg = String.format("Illegal Character: '%c'", c);
                yytext.append((char)c);
                lexerror(msg);
                throw new ParseException(msg);
            }
        }

        // do eof check
        if (token <= 0) {
            token = 0;
            lval = null;
        } else {
            lval = (yytext.length() == 0 ? (String) null : yytext.toString());
        }
        if (parsestate.getDebugLevel() > 0)
            dumptoken(token, (String) lval);

        return token;       /* Return the type of the token.  */

    } catch (IOException ioe) {
        throw new ParseException(ioe);
    }

}

void
dumptoken(int token, String lval)
        throws ParseException
{
    String stoken;
    if (token == WORD_WORD)
        stoken = lval;
    else if (token < '\177')
        stoken = "" + (char) token;
    else switch (token) {
        case SCAN_ALIAS:
            stoken = "alias";
            break;
        case SCAN_ARRAY:
            stoken = "array";
            break;
        case SCAN_ATTR:
            stoken = "attributes";
            break;
        case SCAN_BYTE:
            stoken = "byte";
            break;
        case SCAN_DATASET:
            stoken = "dataset";
            break;
        case SCAN_FLOAT32:
            stoken = "float32";
            break;
        case SCAN_FLOAT64:
            stoken = "float64";
            break;
        case SCAN_GRID:
            stoken = "grid";
            break;
        case SCAN_INT16:
            stoken = "int16";
            break;
        case SCAN_INT32:
            stoken = "int32";
            break;
        case SCAN_MAPS:
            stoken = "maps";
            break;
        case SCAN_SEQUENCE:
            stoken = "sequence";
            break;
        case SCAN_STRING:
            stoken = "string";
            break;
        case SCAN_STRUCTURE:
            stoken = "structure";
            break;
        case SCAN_UINT16:
            stoken = "uint16";
            break;
        case SCAN_UINT32:
            stoken = "uint32";
            break;
        case SCAN_URL:
            stoken = "url";
            break;
        default:
            stoken = "X" + Integer.toString(token);
        }
    System.err.println("TOKEN = |" + stoken + "|");
    if(stoken != null && stoken.length() == 1) System.err.println("TOKEN = " + ((int) stoken.charAt(0)));

}

static int
tohex(int c)
        throws ParseException
{
    if (c >= 'a' && c <= 'f') return (c - 'a') + 0xa;
    if (c >= 'A' && c <= 'F') return (c - 'A') + 0xa;
    if (c >= '0' && c <= '9') return (c - '0');
    return -1;
}

/**************************************************/
/* Lexer Interface */

/**
 * Method to retrieve the semantic value of the last scanned token.
 *
 * @return the semantic value of the last scanned token.
 */
public Object getLVal()
{
    return this.lval;
}

/**
 * Entry point for error reporting.  Emits an error
 * in a user-defined way.
 *
 * @param s The string for the error message.
 */
public void yyerror(String s)
{
    String kind = "?";
    switch (parsestate.parseClass) {
    case Dap2Parse.DapDAS: kind = "DAS"; break;
    case Dap2Parse.DapDDS: kind = "DDS"; break;
    case Dap2Parse.DapERR: kind = "Error"; break;
    default: kind = "?"; break;
    }
    System.err.println("yyerror: "+s+"; "+kind+" parse failed at line: " + lineno+" char: "+charno+"; near: ");
    String context = parsestate.flatten(getInput());
    int show = (context.length() < CONTEXTLEN ? context.length() : CONTEXTLEN);
    System.err.println(context.substring(context.length() - show)+"^");
        if(parsestate.getURL() != null)
            System.err.println("\turl="+parsestate.getURL());
    new Exception().printStackTrace(System.err);
}

public void lexerror(String msg)
{
    StringBuilder nextline = new StringBuilder();
    int c = 0;
    for(int i=0;i<1024;i++) { // limit amount read
        if((c = text.read()) == 0) break;
        if (c == '\n') break;
        nextline.append((char) c);
    }
    if(c != 0) nextline.append("...");
    System.out.printf("Lex error: %s; line: %d: %s^%s%n", msg, lineno, yytext, nextline);
}

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.vertical;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.unidata.util.Parameter;

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

/**
 * Create a 3D height(z,y,x) array using the CF formula for
 * "ocean s vertical coordinate".
 *

 * Modified April 20, 2009 by sachin
 *
 * @author caron
 * @author skbhate@gmail.com
 * @see "https://www.myroms.org/wiki/index.php/Vertical_S-coordinate#Metadata_Considerations"
 */

public class OceanS extends VerticalTransformImpl {

  /**
   * The eta variable name identifier
   */
  public static final String ETA = "Eta_variableName";

  /**
   * The "s" variable name identifier
   */
  public static final String S = "S_variableName";

  /**
   * The "depth" variable name identifier
   */
  public static final String DEPTH = "Depth_variableName";

  /**
   * The "depth c" variable name identifier
   */
  public static final String DEPTH_C = "Depth_c_variableName";

  /**
   * The "a" variable name
   */
  public static final String A = "A_variableName";

  /**
   * The "b" variable name
   */
  public static final String B = "B_variableName";

  /**
   * the values of depth_c, a, and b
   */
  private double depth_c;

  /**
   * The eta, s and depth variables
   */
  private Variable etaVar, sVar, depthVar, aVar, bVar, depthCVar;

  /**
   * the c array
   */
  private Array c = null;

  /**
   * Create a new vertical transform for Ocean S coordinates
   *
   * @param ds      dataset
   * @param timeDim time dimension
   * @param params  list of transformation Parameters
   */
  public OceanS(NetcdfFile ds, Dimension timeDim, List<Parameter> params) {

    super(timeDim);
    String etaName = getParameterStringValue(params, ETA);
    String sName = getParameterStringValue(params, S);
    String depthName = getParameterStringValue(params, DEPTH);
    String aName = getParameterStringValue(params, A);
    String bName = getParameterStringValue(params, B);
    String depthCName = getParameterStringValue(params, DEPTH_C);

    etaVar = ds.findVariable(etaName);
    sVar = ds.findVariable(sName);
    depthVar = ds.findVariable(depthName);
    aVar = ds.findVariable(aName);
    bVar = ds.findVariable(bName);
    depthCVar = ds.findVariable(depthCName);

    units = ds.findAttValueIgnoreCase(depthVar, CDM.UNITS, "none");
  }

  /**
   * Get the 3D vertical coordinate array for this time step.
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @return vertical coordinate array
   * @throws IOException           problem reading data
   * @throws InvalidRangeException _more_
   */
  public ArrayDouble.D3 getCoordinateArray(int timeIndex)  throws IOException, InvalidRangeException {
    Array etaArray = readArray(etaVar, timeIndex);
    Array sArray = readArray(sVar, timeIndex);
    Array depthArray = readArray(depthVar, timeIndex);

    if (null == c) {
      double a = aVar.readScalarDouble();
      double b = bVar.readScalarDouble();
      depth_c = depthCVar.readScalarDouble();
      c = makeC(sArray, a, b);
    }

    return makeHeight(etaArray, sArray, depthArray, c, depth_c);
  }

/** Add new method for retrieving 1D Z values for specified indices for lat/lon. */
  /* -Sachin */
  /**
   * Get the 1D vertical coordinate array for this time step and
   * the specified X,Y index for Lat-Lon point.
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @param xIndex    the x index
   * @param yIndex    the y index
   * @return vertical coordinate array
   * @throws IOException           problem reading data
   * @throws InvalidRangeException _more_
   */
  public ArrayDouble.D1 getCoordinateArray1D(int timeIndex, int xIndex, int yIndex)
      throws IOException, InvalidRangeException {
    Array etaArray = readArray(etaVar, timeIndex);
    Array sArray = readArray(sVar, timeIndex);
    Array depthArray = readArray(depthVar, timeIndex);

    if (null == c) {
      double a = aVar.readScalarDouble();
      double b = bVar.readScalarDouble();
      depth_c = depthCVar.readScalarDouble();
      c = makeC(sArray, a, b);
    }

    return makeHeight1D(etaArray, sArray, depthArray, c, depth_c, xIndex, yIndex);
  }

  // C(z) = (1-b)*sinh(a*s(z))/sinh(a) + b*(tanh(a*(s(z)+0.5))/(2*tanh(0.5*a))-0.5)

  /**
   * Make the C array
   *
   * @param s s Array
   * @param a "a" value
   * @param b "b" value
   * @return the C array
   */
  private Array makeC(Array s, double a, double b) {
    int nz = (int) s.getSize();
    Index sIndex = s.getIndex();
    if (a == 0) return s;  // per R. Signell, USGS

    ArrayDouble.D1 c = new ArrayDouble.D1(nz);

    double fac1 = 1.0 - b;
    double denom1 = 1.0 / Math.sinh(a);
    double denom2 = 1.0 / (2.0 * Math.tanh(0.5 * a));

    for (int i = 0; i < nz; i++) {
      double sz = s.getDouble(sIndex.set(i));
      double term1 = fac1 * Math.sinh(a * sz) * denom1;
      double term2 = b * (Math.tanh(a * (sz + 0.5))
          * denom2 - 0.5);
      c.set(i, term1 + term2);
    }

    return c;
  }

  /**
   * Make height from the given data.

   * old equationn:  height(x,y,z) = eta(x,y)*(1+s(z)) + depth_c*s(z) + (depth(x,y)-depth_c)*C(z)
   *

   *

   * /* -sachin 03/23/09
   * The new corrected equation according to Hernan Arango (Rutgers)
   * height(x,y,z) =  S(x,y,z) + eta(x,y) * (1 + S(x,y,z) / depth(x,y) )
   *

   * where,
   * S(x,y,z) = depth_c*s(z) + (depth(x,y)-depth_c)*C(z)
   * /
   *
   * @param eta     eta Array
   * @param s       s Array
   * @param depth   depth Array
   * @param c       c Array
   * @param depth_c value of depth_c
   * @return hieght data
   */
  private ArrayDouble.D3 makeHeight(Array eta, Array s, Array depth, Array c, double depth_c) {
    int nz = (int) s.getSize();
    Index sIndex = s.getIndex();
    Index cIndex = c.getIndex();

    int[] shape2D = eta.getShape();
    int ny = shape2D[0];
    int nx = shape2D[1];
    Index etaIndex = eta.getIndex();
    Index depthIndex = depth.getIndex();

    ArrayDouble.D3 height = new ArrayDouble.D3(nz, ny, nx);

    for (int z = 0; z < nz; z++) {
      double sz = s.getDouble(sIndex.set(z));
      double cz = c.getDouble(cIndex.set(z));

      double term1 = depth_c * sz;

      for (int y = 0; y < ny; y++) {
        for (int x = 0; x < nx; x++) {
          //-sachin 03/23/09  modifications according to corrected equation.
          double fac1 = depth.getDouble(depthIndex.set(y, x));
          double term2 = (fac1 - depth_c) * cz;

          double Sterm = term1 + term2;

          double term3 = eta.getDouble(etaIndex.set(y, x));
          double term4 = 1 + Sterm / fac1;
          double hterm = Sterm + term3 * term4;

          height.set(z, y, x, hterm);

        }
      }
    }

    return height;
  }

  // Modify method 'makeHeight' as  new method for getting vertical coordinate array for single point.
  //- sachin
  private ArrayDouble.D1 makeHeight1D(Array eta, Array s, Array depth,
                                      Array c, double depth_c, int x_index, int y_index) {
    int nz = (int) s.getSize();
    Index sIndex = s.getIndex();
    Index cIndex = c.getIndex();

    Index etaIndex = eta.getIndex();
    Index depthIndex = depth.getIndex();

    ArrayDouble.D1 height = new ArrayDouble.D1(nz);

    for (int z = 0; z < nz; z++) {
      double sz = s.getDouble(sIndex.set(z));
      double cz = c.getDouble(cIndex.set(z));

      double term1 = depth_c * sz;
      //-sachin 03/06/09  modifications according to corrected equation.

      double fac1 = depth.getDouble(depthIndex.set(y_index, x_index));
      double term2 = (fac1 - depth_c) * cz;

      double Sterm = term1 + term2;

      double term3 = eta.getDouble(etaIndex.set(y_index, x_index));
      double term4 = 1 + Sterm / fac1;
      double hterm = Sterm + term3 * term4;

      height.set(z, hterm);
    }

    return height;
  }
}