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

package dap4.dap4lib.netcdf;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import dap4.core.dmr.*;
import dap4.core.util.Convert;
import dap4.core.util.DapContext;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import ucar.nc2.jni.netcdf.Nc4prototypes;
import ucar.nc2.jni.netcdf.SizeTByReference;

import static ucar.nc2.jni.netcdf.Nc4prototypes.*;
import static dap4.dap4lib.netcdf.Nc4DSP.*;
import static dap4.dap4lib.netcdf.Nc4Notes.*;

/**
 * Compile netcdf file info into DMR
 */
public class Nc4DMRCompiler
{
    //////////////////////////////////////////////////
    // Constants

    static public final boolean DEBUG = false;

    // Define reserved attributes
    static public final String UCARTAGVLEN = Nc4DSP.UCARTAGVLEN;
    static public final String UCARTAGOPAQUE = Nc4DSP.UCARTAGOPAQUE;

    static final int NC_FALSE = 0;
    static final int NC_TRUE = 1;
    // "null" id(s)
    static public final int NC_GRPNULL = 0;
    static public final int NC_IDNULL = -1;
    static public final int NC_NOERR = 0;

    static int NC_INT_BYTES = (java.lang.Integer.SIZE / java.lang.Byte.SIZE);
    static int NC_LONG_BYTES = (Native.LONG_SIZE);
    static int NC_POINTER_BYTES = (Native.POINTER_SIZE);
    static int NC_SIZET_BYTES = (Native.SIZE_T_SIZE);

    //////////////////////////////////////////////////
    // Static methods

    /**
     * A path is file if it has no base protocol or is file:
     *
     * @param path
     * @param context Any parameters that may help to decide.
     * @return true if this path appears to be processible by this DSP
     */
    static public boolean dspMatch(String path, DapContext context)
    {
        for(String s : EXTENSIONS) {
            if(path.endsWith(s)) return true;
        }
        return false;
    }

    //////////////////////////////////////////////////
    // Instance Variables

    protected Nc4prototypes nc4 = null;

    protected boolean trace = false;
    protected boolean closed = false;

    protected int ncid = -1;        // file id
    protected int format = 0;       // from nc_inq_format
    protected int mode = 0;
    protected String path = null;

    protected String pathprefix = null;

    protected DMRFactory factory = null;
    protected Nc4DSP dsp = null;
    protected DapDataset dmr = null;

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

    public Nc4DMRCompiler(Nc4DSP dsp, int ncid, DMRFactory factory)
            throws DapException
    {
        this.dsp = dsp;
        this.nc4 = dsp.getJNI();
        this.path = dsp.getLocation();
        this.ncid = ncid;
        this.factory = factory;
    }

    //////////////////////////////////////////////////
    // Main entry point

    public DapDataset
    compile()
            throws DapException
    {
        // create and fill the root group
        buildrootgroup(this.ncid);
        if(this.dmr != null) dmr.finish();
        return this.dmr;
    }

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

    protected void
    buildrootgroup(int ncid)
            throws DapException
    {
        int ret;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        errcheck(ret = nc4.nc_inq_grpname(ncid, namep));
        String[] pieces = DapUtil.canonicalpath(this.path).split("[/]");
        DapDataset g = factory.newDataset(pieces[pieces.length - 1]);
        GroupNotes gi = (GroupNotes)Nc4Notes.factory(NoteSort.GROUP,ncid, ncid, this.dsp);
        gi.set(g);
        this.dsp.note(gi);
        this.dmr = g;
        fillgroup(ncid);
    }

    protected void
    fillgroup(int gid)
            throws DapException
    {
        int ret, mode;
        int[] dims = getDimensions(gid);
        int[] udims = getUnlimitedDimensions(gid);
        for(int dimid : dims) {
            builddim(gid, dimid, udims);
        }
        int[] typeids = getUserTypes(gid);
        for(int i=0;i<typeids.length;i++) {
            for(int j = 0; j < i; j++) {
                 if(typeids[i] == typeids[j])
                     assert false;
            }
        }

        for(int typeid : typeids) {
            buildusertype(gid, typeid);
        }
        int[] varids = getVars(gid);
        for(int varid : varids) {
            buildvar(gid, varid);
        }
        // globalattributes
        String[] gattnames = getAttributes(gid, NC_GLOBAL);
        for(String ga : gattnames) {
            buildattr(gid, NC_GLOBAL, ga);
        }
        int[] groupids = getGroups(gid);
        for(int groupid : groupids) {
            buildgroup(gid, groupid);
        }
    }

    protected void
    buildgroup(int parent, int gid)
            throws DapException
    {
        int ret;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        errcheck(ret = nc4.nc_inq_grpname(gid, namep));
        DapGroup g = factory.newGroup(Nc4DSP.makeString(namep));
        GroupNotes gi = (GroupNotes)Nc4Notes.factory(NoteSort.GROUP,parent, gid, this.dsp);
        gi.set(g);
        this.dsp.note(gi);
        GroupNotes gp = (GroupNotes) this.dsp.find(parent, NoteSort.GROUP);
        gp.get().addDecl(g);
        fillgroup(gid);
    }

    protected void
    builddim(int gid, int did, int[] udims)
            throws DapException
    {
        int ret = NC_NOERR;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        SizeTByReference lenp = new SizeTByReference();
        errcheck(ret = nc4.nc_inq_dim(gid, did, namep, lenp));
        String name = Nc4DSP.makeString(namep);
        int len = lenp.intValue();
        boolean isunlimited = contains(udims, did);
        DapDimension dim = factory.newDimension(name, lenp.longValue());
        dim.setUnlimited(isunlimited);
        DimNotes di = (DimNotes)Nc4Notes.factory(NoteSort.DIM,gid, did, this.dsp);
        di.set(dim);
        this.dsp.note(di);
        GroupNotes gp = (GroupNotes) this.dsp.find(gid, NoteSort.GROUP);
        gp.get().addDecl(dim);
        if(trace)
            System.out.printf("Nc4DSP: dimension: %s size=%d%n", name, dim.getSize());
    }

    protected void
    buildusertype(int gid, int tid)
            throws DapException
    {
        int ret = NC_NOERR;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        SizeTByReference lenp = new SizeTByReference();
        IntByReference basetypep = new IntByReference();
        IntByReference classp = new IntByReference();
        SizeTByReference nfieldsp = new SizeTByReference();
        errcheck(ret = nc4.nc_inq_user_type(gid, tid, namep, lenp, basetypep, nfieldsp, classp));
        String name = Nc4DSP.makeString(namep);
        int basetype = basetypep.getValue();
        long len = lenp.longValue();
        long nfields = nfieldsp.longValue();
        TypeNotes ti = (TypeNotes)Nc4Notes.factory(NoteSort.TYPE,gid, tid, this.dsp);
        switch (classp.getValue()) {
        case NC_OPAQUE:
            buildopaquetype(ti, name, len);
            break;
        case NC_ENUM:
            buildenumtype(ti, name, basetype);
            break;
        case NC_COMPOUND:
            buildcompoundtype(ti, name, nfields, len);
            break;
        case NC_VLEN:
            buildvlentype(ti, name, basetype, len);
            break;
        default:
            throw new DapException("Unknown class: " + classp.getValue());
        }
    }

    protected void
    buildopaquetype(TypeNotes ti, String name, long len)
            throws DapException
    {
        int ret;
        ti.setOpaque(len);
        DapType dt = DapType.lookup(TypeSort.Opaque);
        ti.set(dt);
        this.dsp.note(ti);
    }

    protected void
    buildenumtype(TypeNotes ti, String name, int basetype)
            throws DapException
    {
        int ret;
        SizeTByReference nmembersp = new SizeTByReference();
        SizeTByReference sizep = new SizeTByReference();
        byte[] namep = new byte[NC_MAX_NAME + 1];
        IntByReference basetypep = new IntByReference();
        IntByReference valuep = new IntByReference();
        TypeNotes base = (TypeNotes) this.dsp.find(basetype, NoteSort.TYPE);
        if(!isintegertype(base))
            throw new DapException("Enum base type must be integer type");
        errcheck(ret = nc4.nc_inq_enum(ti.gid, ti.id, namep, basetypep, sizep, nmembersp));
        DapEnumeration de = factory.newEnumeration(name, DapType.lookup(base.getType().getTypeSort()));
        ti.set(de);
        this.dsp.note(ti);
        ti.setEnumBaseType(basetype);
        ti.group().addDecl(de);
        // build list of enum consts
        int nconsts = nmembersp.intValue();
        for(int i = 0; i < nconsts; i++) {
            // Get info about the ith const
            errcheck(ret = nc4.nc_inq_enum_member(ti.gid, ti.id, i, namep, valuep));
            String ecname = Nc4DSP.makeString(namep);
            long ecval = (long) valuep.getValue();
            DapEnumConst dec = factory.newEnumConst(ecname, ecval);
            de.addEnumConst(dec);
        }
    }

    protected void
    buildcompoundtype(TypeNotes ti, String name, long nfields, long len)
            throws DapException
    {
        DapStructure ds = factory.newStructure(name);
        ti.set(ds);
        this.dsp.note(ti);
        ti.group().addDecl(ds);
        for(int i = 0; i < nfields; i++) {
            buildfield(ti, i, ds);
        }
        // Finally, extract the size of the structure
        int ret;
        SizeTByReference sizep = new SizeTByReference();
        SizeTByReference nfieldsp = new SizeTByReference();
        byte[] namep = new byte[NC_MAX_NAME + 1];
        errcheck(ret = nc4.nc_inq_compound(ti.gid, ti.id, namep, sizep, nfieldsp));
        ti.setSize(sizep.longValue());
        assert len == sizep.longValue();
        assert name.equals(Nc4DSP.makeString(namep));
        // Add the netcdf4 name as an xml attribute.
        ds.addXMLAttribute(UCARTAGORIGTYPE,name);
    }

    protected void
    buildfield(TypeNotes ti, int fid, DapStructure container)
            throws DapException
    {
        int ret;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        SizeTByReference offsetp = new SizeTByReference();
        IntByReference fieldtypep = new IntByReference();
        IntByReference ndimsp = new IntByReference();

        // Get everything but actual dims
        errcheck(ret = nc4.nc_inq_compound_field(ti.gid, ti.id, fid, namep,
                offsetp, fieldtypep, ndimsp, null));
        int fieldtype = fieldtypep.getValue();
        TypeNotes baset = (TypeNotes) this.dsp.find(fieldtype, NoteSort.TYPE);
        if(baset == null)
            throw new DapException("Undefined field base type: " + fieldtype);
        int[] dimsizes = getFieldDimsizes(ti.gid, ti.id, fid, ndimsp.getValue());
        VarNotes fieldnotes = makeField(ti, fid, Nc4DSP.makeString(namep), baset, offsetp.intValue(), dimsizes);
        assert baset.getSize() > 0;
    }

    protected VarNotes
    makeField(TypeNotes container, int fieldid, String name, TypeNotes baset, int offset, int[] dimsizes)
            throws DapException
    {
        DapVariable field;
        DapStructure ds = (DapStructure) container.getType();
        field = factory.newVariable(name, baset.getType());
        field.setParent(ds);
        field.setFieldIndex(fieldid);
        VarNotes notes = (VarNotes) Nc4Notes.factory(NoteSort.VAR, container.gid, container.id, this.dsp);
        notes.setOffset(offset)
                .setBaseType(baset)
                .setContainer(container);
        notes.set(field);
        this.dsp.note(notes);
        // set dimsizes
        if(dimsizes.length > 0) {
            for(int i = 0; i < dimsizes.length; i++) {
                DapDimension dim = factory.newDimension(null, dimsizes[i]);
                field.addDimension(dim);
            }
        }
        ds.addField(field);
        return notes;
    }

    protected DapVariable
    buildvar(int gid, int vid)
            throws DapException
    {
        int ret;
        byte[] namep = new byte[NC_MAX_NAME + 1];
        IntByReference ndimsp = new IntByReference();
        IntByReference xtypep = new IntByReference();
        IntByReference nattsp = new IntByReference();
        errcheck(ret = nc4.nc_inq_var(gid, vid, namep, xtypep, ndimsp, null, nattsp));
        String name = Nc4DSP.makeString(namep);
        TypeNotes xtype = (TypeNotes) this.dsp.find(xtypep.getValue(), NoteSort.TYPE);
        if(DEBUG) {
            System.err.printf("NC4: inqvar: name=%s gid=%d vid=%d xtype=%d ndims=%d natts=%d%n",
                    name, gid, vid, xtype.id, ndimsp.getValue(), nattsp.getValue());
        }
        if(xtype == null)
            throw new DapException("Unknown type id: " + xtype.id);
        DapVariable var;
        switch (((DapType) xtype.node).getTypeSort()) {
        default: /* atomic */
            var = factory.newVariable(name, xtype.getType());
            break;
        case Enum:
            var = factory.newVariable(name, xtype.getType());
            break;
        case Structure:
            DapStructure st = (DapStructure) xtype.get();
            var = factory.newVariable(name, xtype.getType());
            break;
        case Sequence:
            DapSequence seq = (DapSequence) xtype.get();
            var = factory.newVariable(name, xtype.getType());
            break;
        }
        VarNotes vi = (VarNotes)Nc4Notes.factory(NoteSort.VAR,gid, vid, this.dsp);
        vi.set(var);
        this.dsp.note(vi);
        vi.setBaseType(xtype);
        vi.group().addDecl(var);
        int[] dimids = getVardims(gid, vid, ndimsp.getValue());
        for(int i = 0; i < dimids.length; i++) {
            DimNotes di = (DimNotes) this.dsp.find(dimids[i], NoteSort.DIM);
            if(di == null)
                throw new DapException("Undefined variable dimension id: " + dimids[i]);
            var.addDimension(di.get());
        }
        // Now, if this is of type opaque, tag it with the size
        if(xtype.isOpaque()) {
            var.addXMLAttribute(UCARTAGOPAQUE,Long.toString(xtype.getSize()));
        }
        // fill in any attributes
        String[] attnames = getAttributes(gid, vid);
        for(String a : attnames) {
            buildattr(gid, vid, a);
        }
        return var;
    }

    protected void
    buildvlentype(TypeNotes ti, String vname, int basetypeid, long len)
            throws DapException
    {
        int ref;
        // We map vlen to a sequence with a single field of the
        // basetype of the vlen. Field name is same as the vlen type.
        // So we need to build two things:
        // 1. a Sequence object
        // 2. a Field
        DapSequence ds = factory.newSequence(vname);
        ti.set(ds);
        this.dsp.note(ti);
        ti.group().addDecl(ds);
        ti.markVlen();
        TypeNotes fieldtype = (TypeNotes) this.dsp.find(basetypeid, NoteSort.TYPE);
        if(fieldtype == null)
            throw new DapException("Undefined vlen basetype: " + basetypeid);
        VarNotes fieldnotes = makeField(ti, 0, vname, fieldtype, 0, new int[0]);
        // Annotate to indicate that this came from a vlen
        ds.addXMLAttribute(UCARTAGVLEN,"1");

        // Annotate to indicate that the original type name
        ds.addXMLAttribute(UCARTAGORIGTYPE,ds.getFQN());

        // Finally, extract the size of the structure, which is the same
        // as the size of the singleton field
        ti.setRecordSize(fieldtype.getSize());
        ti.setSize(Nc4prototypes.Vlen_t.VLENSIZE);
    }

    protected void
    buildattr(int gid, int vid, String name)
            throws DapException
    {
        int ret;
        boolean isglobal = (vid == NC_GLOBAL);
        IntByReference basetypep = new IntByReference();
        errcheck(ret = nc4.nc_inq_atttype(gid, vid, name, basetypep));
        int basetype = basetypep.getValue();
        TypeNotes base = (TypeNotes) this.dsp.find(basetype, NoteSort.TYPE);
        if(!islegalattrtype(base))
            throw new DapException("Non-atomic attribute types not supported: " + name);
        SizeTByReference countp = new SizeTByReference();
        errcheck(ret = nc4.nc_inq_attlen(gid, vid, name, countp));
        // Get the values of the attribute
        String[] values = getAttributeValues(gid, vid, name, base, countp.intValue());
        DapAttribute da = factory.newAttribute(name, (DapType) base.getType());
        da.setValues(values);
        if(isglobal) {
            GroupNotes gi = (GroupNotes) this.dsp.find(gid, NoteSort.GROUP);
            gi.get().addAttribute(da);
        } else {
            VarNotes vi = this.dsp.findVar(gid, vid);
            vi.get().addAttribute(da);
        }
    }

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

    int[]
    getGroups(int gid)
            throws DapException
    {
        int ret, n;
        IntByReference ip = new IntByReference();
        errcheck(ret = nc4.nc_inq_grps(gid, ip, null));
        n = ip.getValue();
        int[] grpids = null;
        if(n > 0) {
            grpids = new int[n];
            errcheck(ret = nc4.nc_inq_grps(gid, ip, grpids));
        } else
            grpids = new int[0];
        return grpids;
    }

    int[]
    getDimensions(int gid)
            throws DapException
    {
        int ret, n;
        IntByReference ip = new IntByReference();
        errcheck(ret = nc4.nc_inq_ndims(gid, ip));
        n = ip.getValue();
        int[] dimids = new int[n];
        if(n > 0)
            errcheck(ret = nc4.nc_inq_dimids(gid, ip, dimids, NC_FALSE));
        return dimids;
    }

    int[]
    getUnlimitedDimensions(int gid)
            throws DapException
    {
        int ret, n;
        IntByReference ip = new IntByReference();
        errcheck(ret = nc4.nc_inq_unlimdims(gid, ip, null));
        n = ip.getValue();
        int[] dimids = new int[n];
        if(n > 0)
            errcheck(ret = nc4.nc_inq_unlimdims(gid, ip, dimids));
        return dimids;
    }

    int[]
    getUserTypes(int gid)
            throws DapException
    {
        int ret, n;
        IntByReference ip = new IntByReference();
        errcheck(ret = nc4.nc_inq_typeids(gid, ip, null));
        n = ip.getValue();
        int[] typeids = new int[n];
        if(n > 0)
            errcheck(ret = nc4.nc_inq_typeids(gid, ip, typeids));
        return typeids;
    }

    int[]
    getVars(int gid)
            throws DapException
    {
        int ret, n;
        IntByReference ip = new IntByReference();
        errcheck(ret = nc4.nc_inq_nvars(gid, ip));
        n = ip.getValue();
        int[] ids = new int[n];
        if(n > 0)
            errcheck(ret = nc4.nc_inq_varids(gid, ip, ids));
        return ids;
    }

    int[]
    getVardims(int gid, int vid, int ndims)
            throws DapException
    {
        int ret;
        int[] dimids = new int[ndims];

        if(ndims > 0) {
            byte[] namep = new byte[NC_MAX_NAME + 1];
            IntByReference ndimsp = new IntByReference();
            errcheck(ret = nc4.nc_inq_var(gid, vid, null, null, ndimsp, dimids, null));
        }
        return dimids;
    }

    int[]
    getFieldDimsizes(int gid, int tid, int fid, int ndims)
            throws DapException
    {
        int ret;
        int[] dimsizes = new int[ndims];
        if(ndims > 0) {
            IntByReference ndimsp = new IntByReference();
            errcheck(ret = nc4.nc_inq_compound_field(gid, tid, fid, null, null, null, ndimsp, dimsizes));
        }
        return dimsizes;
    }

    String[]
    getAttributes(int gid, int vid)
            throws DapException
    {
        int ret, n;
        boolean isglobal = (vid == NC_GLOBAL);
        IntByReference nattsp = new IntByReference();
        byte[] namep = new byte[NC_MAX_NAME + 1];
        IntByReference ndimsp = new IntByReference();
        IntByReference xtypep = new IntByReference();
        if(isglobal)
            errcheck(ret = nc4.nc_inq_natts(gid, nattsp));
        else {
            errcheck(ret = nc4.nc_inq_var(gid, vid, namep, xtypep, ndimsp, null, nattsp));
        }
        n = nattsp.getValue();
        String[] names = new String[n];
        for(int i = 0; i < n; i++) {
            errcheck(ret = nc4.nc_inq_attname(gid, vid, i, namep));
            names[i] = Nc4DSP.makeString(namep);
        }
        return names;
    }

    String[]
    getAttributeValues(int gid, int vid, String name, TypeNotes tn, int count)
            throws DapException
    {
        int ret;
        // Currently certain types only are allowed.
        if(!islegalattrtype(tn))
            throw new DapException("Unsupported attribute type: " + tn.getType().getShortName());
        if(isenumtype(tn))
            tn = enumbasetype(tn);
        Object vector = getRawAttributeValues(tn, count, gid, vid, name);
        DapType basetype = tn.getType();
        // basetype == Char requires special pre-conversion
        // since the nc file data is coming back as utf-8, not utf-16.
        if(basetype.isCharType()) {
            byte[] data = (byte[]) vector;  // raw utf-8
            String sdata = new String(data,DapUtil.UTF8);
            char[] cdata = sdata.toCharArray();
            count = cdata.length;
            vector = cdata;
        }
        String[] values = (String[])Convert.convert(DapType.STRING, basetype, vector);
        return values;
    }

    Object
    getRawAttributeValues(TypeNotes base, int count, int gid, int vid, String name)
            throws DapException
    {
        int nativetypesize = base.getType().getSize();
        if(isstringtype(base))
            nativetypesize = NC_POINTER_BYTES;
        else if(nativetypesize == 0)
            throw new DapException("Illegal Type Sort:" + base.get().getShortName());
        Object values = null;
        if(count > 0) {
            int ret;
            long totalsize = nativetypesize * count;
            Nc4Pointer mem = Nc4Pointer.allocate(totalsize);
            errcheck(ret = nc4.nc_get_att(gid, vid, name, mem.p));
            switch (base.getType().getTypeSort()) {
            case Char:
                values = mem.p.getByteArray(0, count);
                break;
            case Int8:
                values = mem.p.getByteArray(0, count);
                break;
            case UInt8:
                values = mem.p.getByteArray(0, count);
                break;
            case Int16:
                values = mem.p.getShortArray(0, count);
                break;
            case UInt16:
                values = mem.p.getShortArray(0, count);
                break;
            case Int32:
                values = mem.p.getIntArray(0, count);
                break;
            case UInt32:
                values = mem.p.getIntArray(0, count);
                break;
            case Int64:
                values = mem.p.getLongArray(0, count);
                break;
            case UInt64:
                values = mem.p.getLongArray(0, count);
                break;
            case Float32:
                values = mem.p.getFloatArray(0, count);
                break;
            case Float64:
                values = mem.p.getDoubleArray(0, count);
                break;
            case String:
                values = mem.p.getStringArray(0, count);
                break;
            case Opaque:
                values = mem.p.getByteArray(0, (int) totalsize);
                break;
            case Enum:
                break;
            default:
                throw new IllegalArgumentException("Unexpected sort: " + base.getType().getShortName());
            }
        }
        return values;
    }

    /*Object[]
    convert(int count, Object src, TypeNotes basetype)
            throws DapException
    {
        boolean isenum = isenumtype(basetype);
        boolean isopaque = basetype.isOpaque();
        TypeNotes truetype = basetype;
        if(isenum)
            truetype = enumbasetype(basetype);

        Object[] dst;
        if(ischartype(basetype))
            dst = new Character[count];
        else
            dst = new Object[count];
        try {
            for(int i = 0; i < dst.length; i++) {
                switch (basetype.getType().getTypeSort()) {
                case Char:
                    if(src instanceof char[])
                        dst[i] = ((char[]) src)[i];
                    else
                        dst[i] = (char) (((byte[]) src)[i]);
                    break;
                case Int8:
                case UInt8:
                    dst[i] = ((byte[]) src)[i];
                    break;
                case Int16:
                case UInt16:
                    dst[i] = ((short[]) src)[i];
                    break;
                case Int32:
                case UInt32:
                    dst[i] = ((int[]) src)[i];
                    break;
                case Int64:
                case UInt64:
                    dst[i] = ((long[]) src)[i];
                    break;
                case Float32:
                    dst[i] = ((float[]) src)[i];
                    break;
                case Float64:
                    dst[i] = ((double[]) src)[i];
                    break;
                case String:
                    dst[i] = ((String[]) src)[i];
                    break;
                case Opaque:
                    byte[] alldata = (byte[]) src;
                    int oplen = alldata.length / count;
                    for(i = 0; i < count; i++) {
                        dst[i] = new byte[oplen];
                        System.arraycopy(alldata, oplen * i, dst[i], 0, oplen);
                    }
                    break;
                case Enum:
                    dst = convert(count, src, truetype);
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected sort: " + basetype.getType().getShortName());
                }
            }
            return dst;
        } catch (IllegalArgumentException |
                ArrayIndexOutOfBoundsException e
                ) {
            throw new DapException(e);
        }
    } */

    protected void
    errcheck(int ret)
            throws DapException
    {
        if(ret != 0) {
            String msg = String.format("TestNetcdf: errno=%d; %s", ret, nc4.nc_strerror(ret));
            if(DEBUG)
                System.err.println(msg);
            throw new DapException(msg);
        }
    }

    boolean
    contains(int[] list, int value)
    {
        for(int i = 0; i < list.length; i++) {
            if(list[i] == value) return true;
        }
        return false;
    }

    boolean
    islegalattrtype(TypeNotes nctype)
    {
        return isatomictype(nctype)
                || isenumtype(nctype)
                || nctype.isOpaque();
    }

    boolean
    isatomictype(TypeNotes t)
    {
        return (t.id <= NC_MAX_ATOMIC_TYPE);
    }

    boolean
    isstringtype(TypeNotes nctype)
    {
        return (nctype.id == NC_STRING);
    }

    boolean
    ischartype(TypeNotes t)
    {
        return (t.id == NC_CHAR);
    }

    boolean
    isintegertype(TypeNotes t)
    {
        return (t.id <= NC_UINT64 && t.id != NC_CHAR);
    }

    boolean
    isenumtype(TypeNotes nctype)
    {
        return (nctype == null ? false : nctype.getType().isEnumType());
    }

    TypeNotes
    enumbasetype(TypeNotes etype)
    {
        if(etype == null || !etype.getType().isEnumType()) return null;
        DapType dt = ((DapEnumeration) etype.getType()).getBaseType();
        return (TypeNotes) this.dsp.find(dt);
    }

    protected String
    Nc4FQN(TypeNotes t)
            throws DapException
    {
        int ret = 0;
        // get enclosing ncid fqn
        SizeTByReference lenp = new SizeTByReference();
        errcheck(ret = nc4.nc_inq_grpname_len(t.gid, lenp));
        byte[] namep = new byte[lenp.intValue() + 1];
        errcheck(ret = nc4.nc_inq_grpname_full(t.gid, lenp, namep));
        return Nc4DSP.makeString(namep);
    }
}


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

package thredds.catalog;

/**
 * A metadata element: structured XML element containing info about associated dataset or catalog.
 *
 * @author john caron
 * @see InvMetadata for public interface
 */

public class InvMetadata {
  private InvDataset dataset;
  private String title, type;
  private String xlinkHref;
  private java.net.URI xlinkUri = null;
  private String namespaceURI, prefix;
  private boolean isInherited;
  private boolean isThreddsMetadata = true;
  private MetadataConverterIF converter = null;
  private Object contentObject = null;
  private ThreddsMetadata tm = null;

  private StringBuilder log = new StringBuilder();

  /**
   * Constructor for elements with Xlinks.
   *
   * @param dataset           : dataset that contains the metadata.
   * @param xlinkHref         : URI of xlink, may be null.
   * @param title             : xlink title, may be null.
   * @param type              : metadata type
   * @param namespaceURI      : namespace URI string of the element, use null for THREDDS
   * @param prefix            : namespace prefix of the element, use null for THREDDS
   * @param inherited         : if inherited
   * @param isThreddsMetadata : is threddsMetadata
   * @param converter         : converter for creating the content object, may be null
   */
  public InvMetadata(InvDataset dataset, String xlinkHref, String title, String type,
                     String namespaceURI, String prefix, boolean inherited, boolean isThreddsMetadata,
                     MetadataConverterIF converter) {
    this.dataset = dataset;
    this.xlinkHref = xlinkHref;

    this.title = title;
    this.type = type;
    this.namespaceURI = namespaceURI;
    this.prefix = prefix;
    this.isInherited = inherited;
    this.isThreddsMetadata = isThreddsMetadata;
    this.converter = converter;
  }

  /**
   * Constructor for elements with inline content.
   *
   * @param dataset           : dataset that contains the metadata.
   * @param mtype             : metadata type
   * @param namespaceURI      : namespace URI string of the element, can use null for THREDDS
   * @param namespacePrefix   : namespace prefix of the element, use null for THREDDS
   * @param inherited         : if inherited
   * @param isThreddsMetadata : is threddsMetadata
   * @param converter         : the metadata converter.
   * @param contentObject     : content object.
   */
  public InvMetadata(InvDataset dataset, String mtype, String namespaceURI,
                     String namespacePrefix, boolean inherited, boolean isThreddsMetadata,
                     MetadataConverterIF converter, Object contentObject) {
    this.dataset = dataset;
    this.type = mtype;
    this.namespaceURI = namespaceURI;
    this.prefix = namespacePrefix;
    this.isInherited = inherited;
    this.isThreddsMetadata = isThreddsMetadata;
    this.converter = converter;
    this.contentObject = contentObject;
    if (isThreddsMetadata)
      tm = (ThreddsMetadata) contentObject;
    init = true;
  }

  /**
   * Constructor using an existing ThreddsMetadata object.
   *
   * @param dataset   : dataset that contains the metadata.
   * @param inherited : if inherited
   * @param tm        : content object.
   */
  public InvMetadata(InvDataset dataset, boolean inherited, ThreddsMetadata tm) {
    this.dataset = dataset;
    this.isInherited = inherited;
    this.isThreddsMetadata = true;
    this.contentObject = tm;
    this.tm = tm;
    init = true;
  }

  /**
   * Get the parent dataset of this InvMetadata
   * @return the parent dataset of this InvMetadata
   */
  public InvDataset getParentDataset() {
    return this.dataset;
  }

  /**
   * Get the converter; may be null.
   * @return the converter or null
   */
  public MetadataConverterIF getConverter() {
    return converter;
  }

  /**
   * Get the metadata type.
   * @return the metadata type.
   */
  public String getMetadataType() {
    return type;
  }

  /**
   * Get the namespace.
   * @return the namespace.
   */
  public String getNamespaceURI() {
    return namespaceURI;
  }

  /**
   * Get the prefix mapped to the namespace.
   * @return the prefix mapped to the namespace.
   */
  public String getNamespacePrefix() {
    return prefix;
  }

  /**
   * if it has an XLink
   * @return true if it has an XLink
   */
  public boolean hasXlink() {
    return xlinkHref != null;
  }

  /**
   * if its a XLink, get the xlink:href String
   * @return the xlink:href or null
   */
  public String getXlinkHref() {
    return xlinkHref;
  }

  /**
   * if its a XLink, get the xlink:href as an absolute URI; may be null
   * @return the xlink:href as an absolute URI, or null
   */
  public java.net.URI getXlinkURI() {
    return xlinkUri;
  }

  /**
   * if its a XLink, get the xlink:title attribute
   * @return xlink:title or null
   */
  public String getXlinkTitle() {
    return title;
  }

  /**
   * if it is inherited
   * @return true if it is inherited
   */
  public boolean isInherited() {
    return isInherited;
  }

  /**
   * if this element contains ThreddsMetadata
   * @return true if this element contains ThreddsMetadata
   */
  public boolean isThreddsMetadata() {
    return isThreddsMetadata;
  }

  /**
   * set if this element contains ThreddsMetadata
   * @param isThreddsMetadata true if this element contains ThreddsMetadata
   */
  public void setThreddsMetadata(boolean isThreddsMetadata) {
    this.isThreddsMetadata = isThreddsMetadata;
  }

  /**
   * set the namespace URI
   * @param namespaceURI set the namespace URI
   */
  public void setNamespaceURI(String namespaceURI) {
    this.namespaceURI = namespaceURI;
    hashCode = 0;
  }

  /**
   * get the content object, may be null
   * @return the content object, or null
   */
  public Object getContentObject() {
    finish();
    return contentObject;
  }

  /**
   * set the internal ThreddsMetadata; this holds elements from THREDDS namespace
   * @param tmd the internal ThreddsMetadata object
   */
  public void setThreddsMetadata(ThreddsMetadata tmd) {
    tm = tmd;
  }

  /**
   * get the internal ThreddsMetadata, if isThreddsMetadata() is true
   * @return  the internal ThreddsMetadata, or null
   */
  public ThreddsMetadata getThreddsMetadata() {
    return tm;
  }

  /**
   * Finish getting the metadata if necessary.
   * If this is an XLink, this will trigger a read of the href the first time called.
   */
  public void finish() {
    if (init) return;
    init = true;
    if (xlinkHref == null) return;

    xlinkHref = xlinkHref.trim();
    try {
      this.xlinkUri = dataset.getParentCatalog().resolveUri(xlinkHref);
    } catch (java.net.URISyntaxException e) {
      log.append(" ** Error: Bad URL in metadata href = ").append(xlinkHref).append("\n");
      return;
    }

    // open and read the referenced catalog XML
    try {
      if (converter == null) {
        log.append("  **InvMetadata on = (").append(this).append("): has no converter\n");
        return;
      }

      contentObject = converter.readMetadataContentFromURL(dataset, xlinkUri);
      if (isThreddsMetadata)
        tm = (ThreddsMetadata) contentObject;

    } catch (java.io.IOException e) {
      log.append("  **InvMetadata on = (").append(xlinkUri).append("): Exception (").append(e.getMessage()).append(")\n");
      // e.printStackTrace();
    }
  }

  private boolean init = false;

  boolean check(StringBuilder out) {
    boolean isValid = true;

    if (log.length() > 0) {
      isValid = false;
      out.append(log);
    }

    if ((contentObject != null) && (converter != null))
      isValid &= converter.validateMetadataContent(contentObject, out);

    return isValid;
  }

  /**
   * InvMetadata elements with same values are equal.
   */
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof InvMetadata)) return false;
    return o.hashCode() == this.hashCode();
  }

  /**
   * Override Object.hashCode() to implement equals.
   */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      if (null != getNamespaceURI())
        result = 37 * result + getNamespaceURI().hashCode();
      if (null != getXlinkHref())
        result = 37 * result + getXlinkHref().hashCode();
      if (null != getXlinkTitle())
        result = 37 * result + getXlinkTitle().hashCode();
      if (null != getMetadataType())
        result = 37 * result + getMetadataType().hashCode();
      result = 37 * result + (isInherited() ? 1 : 0);
      // if (isThreddsMetadata)
      //  result = 37*result + getThreddsMetadata().hashCode();
      hashCode = result;
    }
    return hashCode;
  }

  private volatile int hashCode = 0; // Bloch, item 8

  /** String representation
   public String toString() {
   return " xlink = "+getXlinkHref()+" mtype= "+type+" namespaceURI="+namespaceURI
   +" isThredds= "+isThreddsMetadata  +" inherit= "+isInherited();
   } */

}


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

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Vector;

/**
 * SortedTable looks exactly like Hashtable but preserves the insertion order
 * of elements.  While this results in slower performance, it ensures that
 * the DAS will always be printed in the same order in which it was read.
 */
public final class SortedTable extends Dictionary implements java.io.Serializable {

    static final long serialVersionUID = 1;

    private Vector keys, elements;

    public SortedTable() {
        keys = new Vector();
        elements = new Vector();
    }

    /**
     * Returns the number of keys in this table.
     */
    public int size() {
        return keys.size();
    }

    /**
     * Tests if this table is empty.
     */
    public boolean isEmpty() {
        return keys.isEmpty();
    }

    /**
     * Returns an enumeration of the keys in this table.
     */
    public Enumeration keys() {
        return keys.elements();
    }

    /**
     * Returns an enumeration of the values in this table.
     */
    public synchronized Enumeration elements() {
        return elements.elements();
    }

    /**
     * Returns the value to which the key is mapped in this table.
     *
     * @param key a key in this table.
     * @return the value to which the key is mapped, or null if the key is not
     *         mapped to any value in the table.
     */
    public synchronized Object get(Object key) {
        int index = keys.indexOf(key);
        if (index != -1)
            return elements.elementAt(index);
        else
            return null;
    }

    /**
     * Returns the key at the specified index.
     *
     * @param index the index to return
     * @return the key at the specified index.
     */
    public synchronized Object getKey(int index) {
        return keys.elementAt(index);
    }

    /**
     * Returns the element at the specified index.
     *
     * @param index the index to return
     * @return the element at the specified index.
     */
    public synchronized Object elementAt(int index) {
        return elements.elementAt(index);
    }

    /**
     * Maps the specified key to the specified value in this table.
     *
     * @param key   the key
     * @param value the value
     * @return the previous value to which the key is mapped, or null if the
     *         key did not have a previous mapping.
     * @throws NullPointerException if the key or value is null.
     */
    public synchronized Object put(Object key, Object value) throws NullPointerException {
        if (key == null || value == null)
            throw new NullPointerException();

        int index = keys.indexOf(key);
        if (index != -1) {
            Object prev = elements.elementAt(index);
            elements.setElementAt(value, index);
            return prev;
        } else {
            keys.addElement(key);
            elements.addElement(value);
            return null;
        }
    }

    /**
     * Removes the key (and its corresponding value) from this table.  If the
     * key is not in the table, do nothing.
     *
     * @param key the key to remove.
     * @return the value to which the key had been mapped, or null if the key
     *         did not have a mapping.
     */
    public synchronized Object remove(Object key) {
        int index = keys.indexOf(key);
        if (index != -1) {
            Object prev = elements.elementAt(index);
            keys.removeElementAt(index);
            elements.removeElementAt(index);
            return prev;
        } else {
            return null;
        }
    }

    /** Returns a Vector containing the elements in the SortedTable.  This is
     used for more efficient implementation of opendap.dap.Util.uniqueNames() by
     opendap.dap.DDS.checkSemantics()
     @return A Vector containing the elements in this SortedTable.
     */
    public synchronized Vector getElementVector() {
        return elements;
  }
}


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

package ucar.nc2.iosp.grads;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * A class to hold a GrADS time structure.  The full time spec is:
 *
 *        HH:mm'Z'ddMMMyyyy (e.g. 12:04Z05Mar2011)
 *
 * @author   Don Murray CU-CIRES
 */
public class GradsTimeStruct {

    /** months */
    public static final String[] months = {
        "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct",
        "nov", "dec"
    };

    /** year field */
    int year = 0;

    /** month field (1 based) */
    int month = 0;

    /** day field */
    int day = 0;

    /** hour field */
    int hour = 0;

    /** minute field */
    int minute = 0;

    /** julian day field */
    int jday = 0;

    /**
     * Create a new time structure
     */
    public GradsTimeStruct() {}

    /**
     * Get a String representation of this object
     *
     * @return the GrADS time specification
     */
    public String toString() {
        return String.format("%02d:%02dZ%02d%s%d", hour, minute, day,
                             months[month - 1], year);
    }

    /**
     * Return this as a java Date object
     *
     * @return the corresponding Date
     */
    public Date getDate() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1);  // MONTH is zero based
        calendar.set(Calendar.DAY_OF_MONTH, day);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }
}


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

package dap4.dap4lib;

import dap4.core.ce.CEConstraint;
import dap4.core.dmr.*;
import dap4.core.util.*;

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

/**
 * DMR Printer.
 * Given a constraint and a Dataset,
 * print the constrained subset of the dataset.
 *

 * WARNING: the printDMR code in some cases duplicates code in dap4.dap4.Dap4Print;
 * so changes here should be propagated.
 */

public class DMRPrinter
{

    //////////////////////////////////////////////////
    // Constants

    // Could use enumset, but it is so ugly,
    // so use good old OR'able flags
    static protected final int NILFLAGS = 0;
    static protected final int PERLINE = 1; // print xml attributes 1 per line
    static protected final int NONAME = 2; // do not print name xml attribute
    static protected final int NONNIL = 4; // print empty xml attributes
    static protected final int XMLESCAPED = 8; // String is already xml escaped

    static protected final String[] GROUPSPECIAL = {
            "_NCProperties",
            "_DAP4_Little_Endian"
    };

    static protected final String[] VARSPECIAL = {
    };

    static protected final String[] RESERVEDTAGS = {
            "_edu.ucar"
    };

    static final public boolean ALLOWFIELDMAPS = false;

    //////////////////////////////////////////////////
    // Instance Variables

    protected PrintWriter writer = null;
    protected IndentWriter printer = null;
    protected DapDataset dmr = null;
    protected CEConstraint ce = null;
    protected ResponseFormat format = null;
    protected boolean testing = false;

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

    protected DMRPrinter()
    {
    }

    public DMRPrinter(DapDataset dmr, PrintWriter writer)
    {
        this(dmr, null, writer, null);
    }

    public DMRPrinter(DapDataset dmr, CEConstraint ce, PrintWriter writer, ResponseFormat format)
    {
        this();
        this.dmr = dmr;
        this.ce = ce;
        this.writer = writer;
        this.printer = new IndentWriter(writer);
        this.format = (format == null ? ResponseFormat.XML : format);
    }

    //////////////////////////////////////////////////
    // API

    public void flush()
    {
        this.printer.flush();
    }

    public void close()
    {
        this.flush();
    }

    //////////////////////////////////////////////////
    // External API

    /**
     * Print a DapDataset:
     * - as DMR
     * - optionally constrained
     *
     * @throws IOException
     */

    public void
    print()
            throws IOException
    {
        if(this.ce == null)
            this.ce = CEConstraint.getUniversal(dmr);
        assert (this.ce != null);
        this.printer.setIndent(0);
        printNode(dmr); // start printing at the root
        printer.eol();
    }

    /**
     * Same as print() except certain items of
     * information are suppressed.
     *
     * @throws IOException
     */

    public void
    testprint()
            throws IOException
    {
        this.testing = true;
        print();
    }

    //////////////////////////////////////////////////
    // Helper functions

    /**
     * Print an arbitrary DapNode and its subnodes
     * as if it is being sent to a client with
     * optional constraint; inclusions are determined
     * by the view.
     *

     * Handling newlines is a bit tricky because they may be
     * embedded for e.g. groups, enums,
     * etc.  So the rule is that the
     * last newline is elided and left
     * for the caller to print.
     * Exceptions: printMetadata
     * printDimrefs, printMaps
     *
     * @param node - the node to print
     * @throws IOException Note that the PrintWriter is global.
     */

    public void
    printNode(DapNode node)
            throws IOException
    {
        if(node == null)
            return;

        DapSort sort = node.getSort();
        String dmrname = sort.getName();

        switch (sort) {
        case DATASET:// treat like group
        case GROUP:
            if(!this.ce.references(node)) break;
            DapGroup group = (DapGroup) node;
            printer.marginPrint("<" + dmrname);
            int flags = (sort == DapSort.DATASET ? PERLINE : NILFLAGS);
            printXMLAttributes(node, ce, flags);
            printer.println(">");
            printer.indent();
            // Make the output order conform to the spec
            if(group.getDimensions().size() > 0) {
                for(DapNode subnode : group.getDimensions()) {
                    if(!this.ce.references(subnode)) continue;
                    printNode(subnode);
                    printer.eol();
                }
            }
            if(group.getEnums().size() > 0) {
                for(DapNode subnode : group.getEnums()) {
                    if(!this.ce.references(subnode)) continue;
                    printNode(subnode);
                    printer.eol();
                }
            }
            if(group.getVariables().size() > 0)
                for(DapNode subnode : group.getVariables()) {
                    if(!this.ce.references(subnode)) continue;
                    printNode(subnode);
                    printer.eol();
                }
            printMetadata(node);
            if(group.getGroups().size() > 0)
                for(DapNode subnode : group.getGroups()) {
                    if(!this.ce.references(subnode)) continue;
                    printNode(subnode);
                    printer.eol();
                }
            printer.outdent();
            printer.marginPrint("</" + dmrname + ">");
            break;

        case DIMENSION:
            if(!this.ce.references(node)) break;
            DapDimension dim = (DapDimension) node;
            if(!dim.isShared()) break; // ignore, here, anonymous dimensions
            printer.marginPrint("<" + dmrname);
            printXMLAttributes(node, ce, NILFLAGS);
            if(dim.isUnlimited())
                printXMLAttribute(AbstractDSP.UCARTAGUNLIMITED, "1", NILFLAGS);
            if(hasMetadata(node)) {
                printer.println(">");
                printMetadata(node);
                printer.marginPrint("</" + dmrname + ">");
            } else {
                printer.print("/>");
            }
            break;

        case ENUMERATION:
            if(!this.ce.references(node)) break;
            DapEnumeration en = (DapEnumeration) node;
            printer.marginPrint("<" + dmrname);
            printXMLAttributes(en, ce, NILFLAGS);
            printer.println(">");
            printer.indent();
            List<String> econstnames = en.getNames();
            for(String econst : econstnames) {
                DapEnumConst value = en.lookup(econst);
                assert (value != null);
                printer.marginPrintln(
                        String.format("<EnumConst name=\"%s\" value=\"%d\"/>",
                                Escape.entityEscape(econst, null), value.getValue()));
            }
            printMetadata(node);
            printer.outdent();
            printer.marginPrint("</" + dmrname + ">");
            break;

        case VARIABLE:
            if(!this.ce.references(node)) break;
            DapVariable var = (DapVariable) node;
            DapType type = var.getBaseType();
            printer.marginPrint("<" + type.getTypeSort().name());
            printXMLAttributes(node, ce, NILFLAGS);
            if(type.isAtomic()) {
                if((hasMetadata(node) || hasDimensions(var) || hasMaps(var))) {
                    printer.println(">");
                    printer.indent();
                    if(hasDimensions(var))
                        printDimrefs(var);
                    if(hasMetadata(var))
                        printMetadata(var);
                    if(hasMaps(var))
                        printMaps(var);
                    printer.outdent();
                    printer.marginPrint("</" + type.getTypeSort().name() + ">");
                } else
                    printer.print("/>");
            } else if(type.getTypeSort().isCompound()) {
                DapStructure struct = (DapStructure) type;
                printer.println(">");
                printer.indent();
                for(DapVariable field : struct.getFields()) {
                    if(!this.ce.references(field)) continue;
                    printNode(field);
                    printer.eol();
                }
                printDimrefs(var);
                printMetadata(var);
                printMaps(var);
                printer.outdent();
                printer.marginPrint("</" + type.getTypeSort().name() + ">");
            } else
                assert false : "Illegal variable base type";
            break;

        default:
            assert (false) : "Unexpected sort: " + sort.name();
            break;
        }

    }

    /**
     * Print info from the node that needs to be in the form of xml attributes
     */
    void
    printXMLAttributes(DapNode node, CEConstraint ce, int flags)
            throws IOException
    {
        if((flags & PERLINE) != 0)
            printer.indent(2);

        // Print name first, if non-null and !NONAME
        // Note that the short name needs to use
        // entity escaping (which is done by printXMLattribute),
        // but backslash escaping is not required.
        String name = node.getShortName();
        if(name != null && (flags & NONAME) == 0) {
            name = node.getShortName();
            printXMLAttribute("name", name, flags);
        }

        switch (node.getSort()) {
        case DATASET:
            DapDataset dataset = (DapDataset) node;
            printXMLAttribute("dapVersion", dataset.getDapVersion(), flags);
            printXMLAttribute("dmrVersion", dataset.getDMRVersion(), flags);
            // boilerplate
            printXMLAttribute("xmlns", "http://xml.opendap.org/ns/DAP/4.0#", flags);
            printXMLAttribute("xmlns:dap", "http://xml.opendap.org/ns/DAP/4.0#", flags);
            break;

        case DIMENSION:
            DapDimension orig = (DapDimension) node;
            if(orig.isShared()) {//not Anonymous
                // name will have already been printed
                // Now, we need to get the size as defined by the constraint
                DapDimension actual = this.ce.getRedefDim(orig);
                if(actual == null)
                    actual = orig;
                long size = actual.getSize();
                printXMLAttribute("size", Long.toString(size), flags);
            }
            break;

        case ENUMERATION:
            printXMLAttribute("basetype", ((DapEnumeration) node).getBaseType().getTypeName(), flags);
            break;

        case VARIABLE:
            DapVariable var = (DapVariable) node;
            DapType basetype = var.getBaseType();
            if(basetype.isEnumType()) {
                printXMLAttribute("enum", basetype.getTypeName(), flags);
            }
            break;

        case ATTRIBUTE:
            DapAttribute attr = (DapAttribute) node;
            basetype = attr.getBaseType();
            printXMLAttribute("type", basetype.getTypeName(), flags);
            if(attr.getBaseType().isEnumType()) {
                printXMLAttribute("enum", basetype.getTypeName(), flags);
            }
            break;

        default:
            break; // node either has no attributes or name only
        } //switch
        if(!this.testing)
            printReserved(node);
        if((flags & PERLINE) != 0) {
            printer.outdent(2);
        }
    }

    /**
     * PrintXMLAttributes helper function
     *
     * @param name
     * @param value
     * @param flags
     * @throws DapException
     */
    protected void
    printXMLAttribute(String name, String value, int flags)
            throws DapException
    {
        if(name == null) return;
        if((flags & NONNIL) == 0
                && (value == null || value.length() == 0))
            return;
        if((flags & PERLINE) != 0) {
            printer.eol();
            printer.margin();
        }
        printer.print(" " + name + "=");
        printer.print("\"");

        if(value != null) {
            // add xml entity escaping
            if((flags & XMLESCAPED) == 0)
                value = Escape.entityEscape(value, "\"");
            printer.print(value);
        }
        printer.print("\"");
    }

    protected void
    printReserved(DapNode node)
            throws DapException
    {
        Map<String, String> xattrs = node.getXMLAttributes();
        if(xattrs == null) return; // ignore
        for(Map.Entry<String, String> entry : xattrs.entrySet()) {
            if(isReserved(entry.getKey()))
                printXMLAttribute(entry.getKey(), entry.getValue(), NILFLAGS);
        }
    }

    protected void
    printMetadata(DapNode node)
            throws IOException
    {

        Map<String, DapAttribute> attributes = node.getAttributes();
        if(attributes.size() == 0) {
            return;
        }
        for(Map.Entry<String, DapAttribute> entry : attributes.entrySet()) {
            DapAttribute attr = entry.getValue();
            assert (attr != null);
            switch (attr.getSort()) {
            case ATTRIBUTE:
                printAttribute(attr);
                break;
            case ATTRIBUTESET:
                printContainerAttribute(attr);
                break;
            case OTHERXML:
                printOtherXML(attr);
                break;
            }
        }
    }

    protected void
    printContainerAttribute(DapAttribute attr)
    {
    }

    protected void
    printOtherXML(DapAttribute attr)
    {
    }

    static boolean isSuppressed(String name)
    {
        return isReserved(name);
    }

    static boolean isReserved(String key)
    {
        for(String s : RESERVEDTAGS) {
            if(key.startsWith(s)) return true;
        }
        return false;
    }

    /**
     * Special here is not the same as reserved
     *
     * @param attr
     * @return
     */
    static boolean isSpecial(DapAttribute attr)
    {
        if(attr.getParent().getSort() == DapSort.DATASET) {
            for(String s : GROUPSPECIAL) {
                if(s.equals(attr.getShortName()))
                    return true;
            }
        } else if(attr.getParent().getSort() == DapSort.VARIABLE) {
            for(String s : VARSPECIAL) {
                if(s.equals(attr.getShortName()))
                    return true;
            }
        }
        return false;
    }

    void
    printAttribute(DapAttribute attr)
            throws IOException
    {
        if(this.testing && isSpecial(attr))
            return;
        printer.marginPrint("<Attribute");
        printXMLAttribute("name", attr.getShortName(), NILFLAGS);
        DapType type = attr.getBaseType();
        printXMLAttribute("type", type.getTypeName(), NILFLAGS);
        printer.println(">");
        Object values = attr.getValues();
        if(values == null)
            throw new DapException("Attribute with no values:" + attr.getFQN());
        printer.indent();
        // The values for a DapAttribute are always strings,
        // but for enums might be either the econst name or the associated integer.
        String[] svec = (String[]) values;
        // Special case for char
        if(type == DapType.CHAR) {
            // Print the value as a string of all the characters
            StringBuilder buf = new StringBuilder();
            for(int i = 0; i < svec.length; i++) {
                buf.append(svec[i]);
            }
            String cs = String.format("<Value value=\"%s\"/>", buf.toString());
            printer.marginPrintln(cs);
        } else if(type.isEnumType()) {
            String[] names = (String[])((DapEnumeration)type).convert(svec);
            for(int i = 0; i < svec.length; i++) {
                String s = Escape.entityEscape(names[i], null);
                String cs = String.format("<Value value=\"%s\"/>", s);
                printer.marginPrintln(cs);
            }
        } else {
            for(int i = 0; i < svec.length; i++) {
                String s = Escape.entityEscape(svec[i], null);
                String cs = String.format("<Value value=\"%s\"/>", s);
                printer.marginPrintln(cs);
            }
        }
        printer.outdent();
        printer.marginPrintln("</Attribute>");
    }

    /**
     * Print the dimrefs for a variable's dimensions.
     * If the variable has a non-whole projection, then use size
     * else use the dimension name.
     *
     * @param var whole dimensions are to be printed
     * @throws DapException
     */
    void
    printDimrefs(DapVariable var)
            throws DapException
    {
        if(var.getRank() == 0) return;
        List<DapDimension> dimset = this.ce.getConstrainedDimensions(var);
        if(dimset == null)
            throw new DapException("Unknown variable: " + var);
        assert var.getRank() == dimset.size();
        for(int i = 0; i < var.getRank(); i++) {
            DapDimension dim = dimset.get(i);
            printer.marginPrint("<Dim");
            if(dim.isShared()) {
                String fqn = dim.getFQN();
                assert (fqn != null) : "Illegal Dimension reference";
                fqn = fqnXMLEscape(fqn);
                printXMLAttribute("name", fqn, XMLESCAPED);
            } else {
                long size = dim.getSize();// the size for printing purposes
                printXMLAttribute("size", Long.toString(size), NILFLAGS);
            }
            printer.println("/>");
        }
    }

    void
    printMaps(DapVariable parent)
            throws IOException
    {
        if(this.testing)
            return;
        List<DapMap> maps = parent.getMaps();
        if(maps.size() == 0) return;
        for(DapMap map : maps) {
            // Optionally suppress field maps
            if(!ALLOWFIELDMAPS) {
                DapVariable mapvar = map.getVariable();
                if(mapvar.getParent() != null
                    && !mapvar.getParent().getSort().isGroup())
                    continue; // Supress maps with non-top-level vars
            }
            // Separate out name attribute so we can use FQN.
            String name = map.getFQN();
            assert (name != null) : "Illegal <Map> reference";
            printer.marginPrint("<Map");
            name = fqnXMLEscape(name);
            printXMLAttribute("name", name, XMLESCAPED);
            printXMLAttributes(map, ce, NONAME);
            if(hasMetadata(map)) {
                printer.println(">");
                printer.indent();
                printMetadata(map);
                printer.outdent();
                printer.marginPrintln("</Map>");
            } else
                printer.println("/>");
        }
    }

    //////////////////////////////////////////////////
    // Misc. Static Utilities

    /**
     * XML escape a dap fqn
     * and converting '"' to "
     * Assumes backslash escapes are in effect for '/' and '.'
     *
     * @param fqn the backslash escaped fqn
     * @return escaped string
     */
    static public String
    fqnXMLEscape(String fqn)
    {
        // Split the fqn into pieces
        StringBuilder xml = new StringBuilder();
        String segment = null;
        List<String> segments = Escape.backslashsplit(fqn, '/');
        for(int i = 1; i < segments.size() - 1; i++) {  // skip leading /
            segment = segments.get(i);
            segment = Escape.backslashUnescape(segment); // get to raw name
            segment = Escape.entityEscape(segment, "\"");// '"' -> "
            segment = Escape.backslashEscape(segment, "/."); // re-escape
            xml.append("/");
            xml.append(segment);
        }
        // Last segment might be structure path, so similar processing,
        // but worry about '.'
        segment = segments.get(segments.size() - 1);
        segments = Escape.backslashsplit(segment, '.');
        xml.append("/");
        for(int i = 0; i < segments.size(); i++) {
            segment = segments.get(i);
            segment = Escape.backslashUnescape(segment); // get to raw name
            segment = Escape.entityEscape(segment, "\"");// '"' -> "
            segment = Escape.backslashEscape(segment, "/."); // re-escape
            if(i > 0) xml.append(".");
            xml.append(segment);
        }
        return xml.toString();
    }

    static protected String
    getPrintValue(Object value)
    {
        if(value instanceof String) {
            String sclean = Escape.cleanString((String) value);
            return Escape.entityEscape((String) value, null);
        } else if(value instanceof Character) {
            return Escape.entityEscape(((Character) value).toString(), null);
        } else
            return value.toString();
    }

    protected boolean hasMetadata(DapNode node)
    {
        return node.getAttributes().size() > 0;
    }

    static protected boolean hasMaps(DapVariable var)
    {
        return var.getMaps().size() > 0;
    }

    static protected boolean hasDimensions(DapVariable var)
    {
        return var.getDimensions().size() > 0;
    }

} // class DapPrint


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

package ucar.nc2.iosp.mcidas;

import com.google.common.base.MoreObjects;
import edu.wisc.ssec.mcidas.AREAnav;
import edu.wisc.ssec.mcidas.AreaDirectory;
import edu.wisc.ssec.mcidas.AreaFile;
import edu.wisc.ssec.mcidas.AreaFileException;
import edu.wisc.ssec.mcidas.Calibrator;
import edu.wisc.ssec.mcidas.CalibratorException;
import edu.wisc.ssec.mcidas.CalibratorFactory;
import edu.wisc.ssec.mcidas.McIDASException;
import edu.wisc.ssec.mcidas.McIDASUtil;

import ucar.ma2.Array;
import ucar.ma2.ArrayChar;
import ucar.ma2.ArrayInt;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.ma2.Section;

import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.units.DateFormatter;

import ucar.unidata.geoloc.ProjectionImpl;

import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.Parameter;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Date;
import java.util.List;

/**
 * Class to read an AREA file and create a netCDF data structure
 * from it.
 *
 * @author Don Murray
 */
public class AreaReader {

  /**
   * The AREA file
   */
  AreaFile af;

  /**
   * The AREA navigation
   */
  private AREAnav nav;

  /**
   * The raw AREA directory
   */
  private int[] dirBlock;

  /**
   * The raw nav block
   */
  private int[] navBlock;

  /**
   * The AREA directory
   */
  private AreaDirectory ad;

  /**
   * The calibrator
   */
  Calibrator calibrator = null;

  /**
   * list  of bands
   */
  int[] bandMap = null;

  /**
   * calibration scale
   */
  private float calScale = 1.f;

  /**
   * calibration scale
   */
  private String calUnit;

  /**
   * Default ctor
   */
  public AreaReader() {
  }

  /**
   * initialize; note that the file is reopened here
   *
   * @param location    the AREA file to open
   * @param ncfile the netCDF file to fill out
   * @return true if successful
   * @throws AreaFileException problem opening the area file
   */
  public boolean init(String location, NetcdfFile ncfile) throws AreaFileException {

    af = new AreaFile(location);

    //read metadata
    dirBlock = af.getDir();
    ad = af.getAreaDirectory();
    int numElements = ad.getElements();
    int numLines = ad.getLines();
    int numBands = ad.getNumberOfBands();
    bandMap = ad.getBands();
    navBlock = af.getNav();
    Date nomTime = ad.getNominalTime();
    DateFormatter df = new DateFormatter();
    try {
      nav = AREAnav.makeAreaNav(navBlock, af.getAux());
    } catch (McIDASException me) {
      throw new AreaFileException(me.getMessage());
    }
    int sensor = dirBlock[AreaFile.AD_SENSORID];
    String calName = McIDASUtil.intBitsToString(dirBlock[AreaFile.AD_CALTYPE]);
    int calType = getCalType(calName);

    // TODO:  Need to support calibrated data.
    if ((af.getCal() != null)
            && CalibratorFactory.hasCalibrator(sensor)) {
      //System.out.println("can calibrate");
      try {
        calibrator = CalibratorFactory.getCalibrator(sensor, calType, af.getCal());
      } catch (CalibratorException ce) {
        // System.out.println("can't make calibrator");
        calibrator = null;
      }
      //System.out.println("calibrator = " + calibrator);

    }
    calUnit = ad.getCalibrationUnitName();
    calScale = (1.0f / ad.getCalibrationScaleFactor());

    // make the dimensions
    Dimension elements = new Dimension("elements", numElements, true);
    Dimension lines = new Dimension("lines", numLines, true);
    Dimension bands = new Dimension("bands", numBands, true);
    Dimension time = new Dimension("time", 1, true);
    Dimension dirDim = new Dimension("dirSize", AreaFile.AD_DIRSIZE,
            true);
    Dimension navDim = new Dimension("navSize", navBlock.length, true);
    List<Dimension> image = new ArrayList<>();
    image.add(time);
    image.add(bands);
    image.add(lines);
    image.add(elements);
    ncfile.addDimension(null, elements);
    ncfile.addDimension(null, lines);
    ncfile.addDimension(null, bands);
    ncfile.addDimension(null, time);
    ncfile.addDimension(null, dirDim);
    ncfile.addDimension(null, navDim);

    Array varArray;

    // make the variables

    // time
    Variable timeVar = new Variable(ncfile, null, null, "time");
    timeVar.setDataType(DataType.INT);
    timeVar.setDimensions("time");
    timeVar.addAttribute(new Attribute(CDM.UNITS,
            "seconds since "
                    + df.toDateTimeString(nomTime)));
    timeVar.addAttribute(new Attribute("long_name", "time"));
    varArray = new ArrayInt.D1(1, false);
    ((ArrayInt.D1) varArray).set(0, 0);
    timeVar.setCachedData(varArray, false);
    ncfile.addVariable(null, timeVar);

    // lines and elements
    Variable lineVar = new Variable(ncfile, null, null, "lines");
    lineVar.setDataType(DataType.INT);
    lineVar.setDimensions("lines");
    //lineVar.addAttribute(new Attribute(CDM.UNITS, "km"));
    lineVar.addAttribute(new Attribute("standard_name",
            "projection_y_coordinate"));
    varArray = new ArrayInt.D1(numLines, false);
    for (int i = 0; i < numLines; i++) {
      int pos = nav.isFlippedLineCoordinates()
              ? i
              : numLines - i - 1;
      ((ArrayInt.D1) varArray).set(i, pos);
    }
    lineVar.setCachedData(varArray, false);
    ncfile.addVariable(null, lineVar);

    Variable elementVar = new Variable(ncfile, null, null, "elements");
    elementVar.setDataType(DataType.INT);
    elementVar.setDimensions("elements");
    //elementVar.addAttribute(new Attribute(CDM.UNITS, "km"));
    elementVar.addAttribute(new Attribute("standard_name",
            "projection_x_coordinate"));
    varArray = new ArrayInt.D1(numElements, false);
    for (int i = 0; i < numElements; i++) {
      ((ArrayInt.D1) varArray).set(i, i);
    }
    elementVar.setCachedData(varArray, false);
    ncfile.addVariable(null, elementVar);

    // TODO: handle bands and calibrations
    Variable bandVar = new Variable(ncfile, null, null, "bands");
    bandVar.setDataType(DataType.INT);
    bandVar.setDimensions("bands");
    bandVar.addAttribute(new Attribute("long_name",
            "spectral band number"));
    bandVar.addAttribute(new Attribute("axis", "Z"));
    Array bandArray = new ArrayInt.D1(numBands, false);
    for (int i = 0; i < numBands; i++) {
      ((ArrayInt.D1) bandArray).set(i, bandMap[i]);
    }
    bandVar.setCachedData(bandArray, false);
    ncfile.addVariable(null, bandVar);

    // the image
    Variable imageVar = new Variable(ncfile, null, null, "image");
    imageVar.setDataType(DataType.INT);
    imageVar.setDimensions(image);
    setCalTypeAttributes(imageVar, getCalType(calName));
    imageVar.addAttribute(new Attribute(getADDescription(AreaFile.AD_CALTYPE),
            calName));
    imageVar.addAttribute(new Attribute("bands", bandArray));
    imageVar.addAttribute(new Attribute("grid_mapping", "AREAnav"));
    ncfile.addVariable(null, imageVar);

    Variable dirVar = new Variable(ncfile, null, null, "areaDirectory");
    dirVar.setDataType(DataType.INT);
    dirVar.setDimensions("dirSize");
    setAreaDirectoryAttributes(dirVar);
    ArrayInt.D1 dirArray = new ArrayInt.D1(AreaFile.AD_DIRSIZE, false);
    for (int i = 0; i < AreaFile.AD_DIRSIZE; i++) {
      dirArray.set(i, dirBlock[i]);
    }
    dirVar.setCachedData(dirArray, false);
    ncfile.addVariable(null, dirVar);

    Variable navVar = new Variable(ncfile, null, null, "navBlock");
    navVar.setDataType(DataType.INT);
    navVar.setDimensions("navSize");
    setNavBlockAttributes(navVar);
    ArrayInt.D1 navArray = new ArrayInt.D1(navBlock.length, false);
    for (int i = 0; i < navBlock.length; i++) {
      navArray.set(i, navBlock[i]);
    }
    navVar.setCachedData(navArray, false);
    ncfile.addVariable(null, navVar);

    // projection variable
    ProjectionImpl projection = new McIDASAreaProjection(af);
    Variable proj = new Variable(ncfile, null, null, "AREAnav");
    proj.setDataType(DataType.CHAR);
    proj.setDimensions("");

    for (Parameter p : projection.getProjectionParameters()) {
      proj.addAttribute(new Attribute(p));
    }

    // For now, we have to overwrite the parameter versions of thes
    proj.addAttribute(new Attribute("grid_mapping_name", McIDASAreaProjection.GRID_MAPPING_NAME));
        /*
        proj.addAttribute(new Attribute(McIDASAreaProjection.ATTR_AREADIR,
                                        dirArray));
        proj.addAttribute(new Attribute(McIDASAreaProjection.ATTR_NAVBLOCK,
                                        navArray));
        */
    varArray = new ArrayChar.D0();
    ((ArrayChar.D0) varArray).set(' ');
    proj.setCachedData(varArray, false);

    ncfile.addVariable(null, proj);

    // add global attributes
    ncfile.addAttribute(null, new Attribute("Conventions", "CF-1.0"));
    ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, FeatureType.GRID.toString()));
    ncfile.addAttribute(null, new Attribute("nominal_image_time", df.toDateTimeString(nomTime)));

    String encStr = "netCDF encoded on " + df.toDateTimeString(new Date());
    ncfile.addAttribute(null, new Attribute("history", encStr));

    //Lastly, finish the file
    ncfile.finish();
    return true;
  }

  /**
   * Check to see if this is a valid AREA file.
   *
   * @param raf the file in question
   * @return true if it is an AREA file.
   */
  public static boolean isValidFile(RandomAccessFile raf) {
    String fileName = raf.getLocation();
    AreaFile af = null;
    try {
      af = new AreaFile(fileName);  // LOOK opening again not ok for isValidFile
      return true;
    } catch (AreaFileException e) {
      return false;                 // barfola
    } finally {
      if (af != null) af.close();  // LOOK need to look at this code
    }
  }

  /**
   * Read the values for a variable
   *
   * @param v2      the variable
   * @param section the section info (time,x,y range);
   * @return the data
   * @throws IOException           problem reading file
   * @throws InvalidRangeException range doesn't match data
   */
  public Array readVariable(Variable v2, Section section) throws IOException, InvalidRangeException {
    // not sure why timeRange isn't used...will comment out
    // for now
    // TODO: use timeRange in readVariable
    //Range timeRange = null;
    Range bandRange = null;
    Range geoXRange = null;
    Range geoYRange = null;
    Array dataArray;

    if (section == null) {
      dataArray = Array.factory(v2.getDataType(), v2.getShape());

    } else if (section.getRank() > 0) {
      if (section.getRank() > 3) {
        //timeRange = (Range) section.getRange(0);
        bandRange = section.getRange(1);
        geoYRange = section.getRange(2);
        geoXRange = section.getRange(3);
      } else if (section.getRank() > 2) {
        //timeRange = (Range) section.getRange(0);
        geoYRange = section.getRange(1);
        geoXRange = section.getRange(2);
      } else if (section.getRank() > 1) {
        geoYRange = section.getRange(0);
        geoXRange = section.getRange(1);
      }

      dataArray = Array.factory(v2.getDataType(), section.getShape());

    } else {
      String strRank = Integer.toString(section.getRank());
      String msg = "Invalid Rank: " + strRank + ". Must be > 0.";
      throw new IndexOutOfBoundsException(msg);
    }
    String varname = v2.getFullName();

    Index dataIndex = dataArray.getIndex();

    if (varname.equals("latitude") || varname.equals("longitude")) {
      double[][] pixel = new double[2][1];
      double[][] latLon;

      assert geoXRange != null;
      assert geoYRange != null;

      // Use Range object, which calculates requested i, j
      // values and incorporates stride
      for (int i = 0; i < geoXRange.length(); i++) {
        for (int j = 0; j < geoYRange.length(); j++) {
          pixel[0][0] = (double) geoXRange.element(i);
          pixel[1][0] = (double) geoYRange.element(j);
          latLon = nav.toLatLon(pixel);

          if (varname.equals("lat")) {
            dataArray.setFloat(dataIndex.set(j, i), (float) (latLon[0][0]));
          } else {
            dataArray.setFloat(dataIndex.set(j, i), (float) (latLon[1][0]));
          }
        }
      }
    }

    if (varname.equals("image")) {
      try {
        int[][] pixelData;
        if (bandRange != null) {
          for (int k = 0; k < bandRange.length(); k++) {
            int bandIndex = bandRange.element(k) + 1;  // band numbers in McIDAS are 1 based
            for (int j = 0; j < geoYRange.length(); j++) {
              for (int i = 0; i < geoXRange.length(); i++) {
                pixelData = af.getData(geoYRange.element(j),
                        geoXRange.element(i), 1, 1,
                        bandIndex);
                dataArray.setInt(dataIndex.set(0, k, j, i),
                        (pixelData[0][0]));
              }
            }
          }

        } else {
          assert geoXRange != null;
          assert geoYRange != null;

          for (int j = 0; j < geoYRange.length(); j++) {
            for (int i = 0; i < geoXRange.length(); i++) {
              pixelData = af.getData(geoYRange.element(j),
                      geoXRange.element(i), 1, 1);
              dataArray.setInt(dataIndex.set(0, j, i),
                      (pixelData[0][0]));
            }
          }
        }
      } catch (AreaFileException afe) {
        throw new IOException(afe.toString());
      }

    }

    return dataArray;
  }

  /**
   * Set the area directory attributes on the variable
   *
   * @param v the variable to set them on
   */
  private void setAreaDirectoryAttributes(Variable v) {
    if ((dirBlock == null) || (ad == null)) {
      return;
    }
    for (int i = 1; i < 14; i++) {
      if (i == 7) {
        continue;
      }
      v.addAttribute(new Attribute(getADDescription(i), dirBlock[i]));
    }
  }

  /**
   * Set the navigation block attributes on the variable
   *
   * @param v the variable to set them on
   */
  private void setNavBlockAttributes(Variable v) {
    if ((navBlock == null) || (ad == null)) {
      return;
    }
    v.addAttribute(
            new Attribute(
                    "navigation_type", McIDASUtil.intBitsToString(navBlock[0])));
  }

  // TODO: Move to use edu.wisc.ssec.mcidas.AreaDirectory.getDescription
  // once it's released

  /**
   * Get a description for a particular Area Directory entry
   *
   * @param index the index
   * @return a description
   */
  private String getADDescription(int index) {

    String desc = "dir(" + index + ")";
    switch (index) {

      case AreaFile.AD_STATUS:
        desc = "relative position of the image object in the ADDE dataset";
        break;

      case AreaFile.AD_VERSION:
        desc = "AREA version";
        break;

      case AreaFile.AD_SENSORID:
        desc = "SSEC sensor source number";
        break;

      case AreaFile.AD_IMGDATE:
        desc = "nominal year and Julian day of the image (yyyddd)";
        break;

      case AreaFile.AD_IMGTIME:
        desc = "nominal time of the image (hhmmss)";
        break;

      case AreaFile.AD_STLINE:
        desc = "upper-left image line coordinate";
        break;

      case AreaFile.AD_STELEM:
        desc = "upper-left image element coordinate";
        break;

      case AreaFile.AD_NUMLINES:
        desc = "number of lines in the image";
        break;

      case AreaFile.AD_NUMELEMS:
        desc = "number of data points per line";
        break;

      case AreaFile.AD_DATAWIDTH:
        desc = "number of bytes per data point";
        break;

      case AreaFile.AD_LINERES:
        desc = "line resolution";
        break;

      case AreaFile.AD_ELEMRES:
        desc = "element resolution";
        break;

      case AreaFile.AD_NUMBANDS:
        desc = "number of spectral bands";
        break;

      case AreaFile.AD_PFXSIZE:
        desc = "length of the line prefix";
        break;

      case AreaFile.AD_PROJNUM:
        desc = "SSEC project number used when creating the file";
        break;

      case AreaFile.AD_CRDATE:
        desc = "year and Julian day the image file was created (yyyddd)";
        break;

      case AreaFile.AD_CRTIME:
        desc = "image file creation time (hhmmss)";
        break;

      case AreaFile.AD_BANDMAP:
        desc = "spectral band map: bands 1-32";
        break;

      case AreaFile.AD_DATAOFFSET:
        desc = "byte offset to the start of the data block";
        break;

      case AreaFile.AD_NAVOFFSET:
        desc = "byte offset to the start of the navigation block";
        break;

      case AreaFile.AD_VALCODE:
        desc = "validity code";
        break;

      case AreaFile.AD_STARTDATE:
        desc = "actual image start year and Julian day (yyyddd)";
        break;

      case AreaFile.AD_STARTTIME:
        desc = "actual image start time (hhmmss) in milliseconds for POES data";
        break;

      case AreaFile.AD_STARTSCAN:
        desc = "actual image start scan";
        break;

      case AreaFile.AD_DOCLENGTH:
        desc = "length of the prefix documentation";
        break;

      case AreaFile.AD_CALLENGTH:
        desc = "length of the prefix calibration";
        break;

      case AreaFile.AD_LEVLENGTH:
        desc = "length of the prefix band list";
        break;

      case AreaFile.AD_SRCTYPE:
        desc = "source type";
        break;

      case AreaFile.AD_CALTYPE:
        desc = "calibration type";
        break;

      case AreaFile.AD_SRCTYPEORIG:
        desc = "original source type";
        break;

      case AreaFile.AD_CALTYPEUNIT:
        desc = "calibration unit";
        break;

      case AreaFile.AD_CALTYPESCALE:
        desc = "calibration scaling";
        break;

      case AreaFile.AD_AUXOFFSET:
        desc = "byte offset to the supplemental block";
        break;

      case AreaFile.AD_CALOFFSET:
        desc = "byte offset to the calibration block";
        break;

      case AreaFile.AD_NUMCOMMENTS:
        desc = "number of comment cards";
        break;

    }
    desc = desc.replaceAll("\\s", "_");
    return desc;

  }

  /**
   * Get the calibration type from the name
   *
   * @param calName calibration name
   * @return the Calibrator class type
   */
  private int getCalType(String calName) {
    int calTypeOut = Calibrator.CAL_NONE;
    if (calName.trim().equals("ALB")) {
      calTypeOut = Calibrator.CAL_ALB;
    } else if (calName.trim().equals("BRIT")) {
      calTypeOut = Calibrator.CAL_BRIT;
    } else if (calName.trim().equals("RAD")) {
      calTypeOut = Calibrator.CAL_RAD;
    } else if (calName.trim().equals("RAW")) {
      calTypeOut = Calibrator.CAL_RAW;
    } else if (calName.trim().equals("TEMP")) {
      calTypeOut = Calibrator.CAL_TEMP;
    }
    return calTypeOut;
  }

  /**
   * Set the long name and units for the calibration type
   *
   * @param image   image variable
   * @param calType calibration type
   */
  private void setCalTypeAttributes(Variable image, int calType) {
    String longName = "image values";
    //String unit     = "";
    switch (calType) {

      case Calibrator.CAL_ALB:
        longName = "albedo";
        //unit     = "%";
        break;

      case Calibrator.CAL_BRIT:
        longName = "brightness values";
        break;

      case Calibrator.CAL_TEMP:
        longName = "temperature";
        //unit     = "K";
        break;

      case Calibrator.CAL_RAD:
        longName = "pixel radiance values";
        //unit     = "mW/m2/sr/cm-1";
        break;

      case Calibrator.CAL_RAW:
        longName = "raw image values";
        break;

      default:
        break;
    }
    image.addAttribute(new Attribute("long_name", longName));
    if (calUnit != null) {
      image.addAttribute(new Attribute(CDM.UNITS, calUnit));
    }
    if (calScale != 1.f) {
      image.addAttribute(new Attribute("scale_factor", calScale));
    }

  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
            .add("af", af)
            .add("nav", nav)
            .add("ad", ad)
            .add("calibrator", calibrator)
            .add("calScale", calScale)
            .add("calUnit", calUnit)
            .toString();
  }
}


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

package ucar.nc2.ft.point.standard;

import ucar.nc2.Attribute;
import ucar.nc2.constants.CF;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.CoordinateSystem;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.constants.AxisType;
import ucar.nc2.Dimension;

import java.util.List;

/**
 * CoordinateSystem Evaluation utilities.
 *
 * @author caron
 * @since Dec 16, 2008
 */
public class CoordSysEvaluator {

  /**
   * search for Axis by Type, assign to TableConfig if found.
   * search for Lat, Lon, Time, Height.
   * @param nt set coordinates short names in this table.
   * @param ds search in this dataset's "Best" coordinate system. If no CoordSystem, try list of coordinate axes
   */
  static public void findCoords(TableConfig nt, NetcdfDataset ds, Predicate p) {
    nt.lat =  findCoordShortNameByType(ds, AxisType.Lat, p);
    nt.lon =  findCoordShortNameByType(ds, AxisType.Lon, p);
    nt.time =  findCoordShortNameByType(ds, AxisType.Time, p);
    nt.elev =  findCoordShortNameByType(ds, AxisType.Height, p);
    if (nt.elev == null)
      nt.elev =  findCoordShortNameByType(ds, AxisType.Pressure, p);
  }

   /**
   * search for Axis by Type.
   * @param ds search in this dataset's "Best" coordinate system.
   * @param atype search for this type of CoordinateAxis. takes the first one it finds.
    * @return the found CoordinateAxis name, or null if none
   */
   static public String findCoordNameByType(NetcdfDataset ds, AxisType atype) {
     CoordinateAxis coordAxis = findCoordByType(ds, atype);
     return coordAxis == null ? null : coordAxis.getFullName();
   }

  static public String findCoordShortNameByType(NetcdfDataset ds, AxisType atype) {
    CoordinateAxis coordAxis = findCoordByType(ds, atype);
    return coordAxis == null ? null : coordAxis.getShortName();
  }

  static public String findCoordShortNameByType(NetcdfDataset ds, AxisType atype, Predicate p) {
    CoordinateAxis coordAxis = findCoordByType(ds, atype, p);
    return coordAxis == null ? null : coordAxis.getShortName();
  }

  /**
   * Search for Axis by Type.
   * @param ds search in this dataset's "Best" coordinate system.
   * @param atype search for this type of CoordinateAxis. takes the first one it finds.
   * @return the found CoordinateAxis, or null if none
   */
  static public CoordinateAxis findCoordByType(NetcdfDataset ds, AxisType atype) {
    return findCoordByType(ds, atype, null);
  }

  /**
   * search for Axis by Type and test against a predicate
   * @param ds search in this dataset's "Best" coordinate system.
   * @param atype search for this type of CoordinateAxis.
   * @param p match this predicate; may be null
   * @return the found CoordinateAxis, or null if none
   */
  static public CoordinateAxis findCoordByType(NetcdfDataset ds, AxisType atype, Predicate p) {
    // try the "best" coordinate system
    CoordinateSystem use = findBestCoordinateSystem(ds);
    if (use == null) return null;
    CoordinateAxis result = findCoordByType(use.getCoordinateAxes(), atype, p);
    if (result != null) return result;

    // try all the axes
    return findCoordByType(ds.getCoordinateAxes(), atype, p);
  }

  static public CoordinateAxis findCoordByType(List<CoordinateAxis> axes, AxisType atype, Predicate p) {
    // first search for matching AxisType and "CF axis" attribute
    for (CoordinateAxis axis : axes) {
      if (axis.getAxisType() == atype) {
        Attribute att = axis.findAttribute(CF.AXIS);
        if (att != null && att.getStringValue().equals(atype.getCFAxisName()) && (p == null || p.match(axis)))
          return axis;
      }
    }

    // now match on just the AxisType
    for (CoordinateAxis axis : axes) {
      if (axis.getAxisType() == atype && (p == null || p.match(axis)))
        return axis;
    }

    return null;
  }

  public interface Predicate {
    boolean match(CoordinateAxis axis);
  }

  /**
   * search for Dimension used by axis of given by Type.
   * @param ds search in this dataset's "Best" coordinate system.
   * @param atype search for this type of CoordinateAxis. takes the first one it finds.
   * @return the found CoordinateAxis' first Dimension, or null if none or scalar
   */
  static public Dimension findDimensionByType(NetcdfDataset ds, AxisType atype) {
    CoordinateAxis axis = findCoordByType(ds, atype);
    if (axis == null) return null;
    if (axis.isScalar()) return null;
    return axis.getDimension(0);
  }

  /**
   * Find the CoordinateSystem with the most number of CoordinateAxes
   * @param ds search in this dataset
   * @return CoordinateSystem or null if none
   */
  static private CoordinateSystem findBestCoordinateSystem(NetcdfDataset ds) {
        // find coordinate system with highest rank (largest number of axes)
    CoordinateSystem use = null;
    for (CoordinateSystem cs : ds.getCoordinateSystems()) {
      if (use == null) use = cs;
      else if (cs.getCoordinateAxes().size() > use.getCoordinateAxes().size())
        use = cs;
    }
    return use;
  }

}