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

package dap4.core.ce;

import dap4.core.ce.parser.CEParserImpl;
import dap4.core.data.Constraint;
import dap4.core.data.DataCursor;
import dap4.core.dmr.*;
import dap4.core.dmr.parser.ParseException;
import dap4.core.util.*;

import java.util.*;

/**
 * A Constraint is a structure
 * containing a parsed representation
 * of a constraint expression.
 * Its purpose is define a subset of interest of a dataset.
 *

 * The constraint object is defined with respect to some underlying DMR.
 * It defines a subset view over that DMR in that it specifies
 * a set of declarations (variables, enums, dimensions, groups)
 * to be included.
 * For each such variable, the constraint specifies
 * any overriding of the dimensions of the variables.
 * Additionally, each variable (if appropriate) may have a filter expr.
 *

 * Thus, there are three 'sub' constraints within a full constraint.
 * 1. Referencing - is a variable from the underlying dataset
 * included in the constraint, directly (by
 * or indirectly: e.g. fields of a structure when only
 * the structure is referenced
 * 2. Projection - the actual values of a variable to be included
 * in the constraint; this is specified by a triple [start:stride:stop]
 * for each dimension of a variable.
 * 3. Selection (aka filters) - A predicate over the scalar fields
 * of a row of a Sequence; if it evaluates to true, then that
 * row matches the constraint.
 *

 * There are multiple ways to effect a constraint.
 * 1. Generate and test mode: the constraint is asked if a given
 * element matches the constraint. E.g.
 * a. For referencing, one might ask the constraint
 * if a given variable or field is in the constraint
 * b. For a projection filter, one might ask the constraint
 * if a given set of dimension indices match the projection.
 * c. For a selection filter, one might ask the constraint
 * if a given sequence row matches the filter predicate
 * 2. Iteration mode: the constraint provides an iterator
 * that returns the elements matching the constraint. E.g.
 * a. For referencing, the iterator would return all the
 * variables and fields referenced in the constraint.
 * b. For a projection filter, the iterator would return
 * the successive sets of indices of the projection,
 * or it could return the actual matching value.
 * c. For a selection filter, the iterator would return
 * either the row indices or the actual rows of a sequence
 * that matched the filter predicate
 *

 * 3. Read mode: Sometimes, it may be more efficient to let the
 * DataVariable object handle the constraint more directly. E.g.
 * a. For example, if the data variable was backed by a netcdf
 * file, then passing in the complete projection might be more
 * efficient than pulling values 1 by 1.
 * b. Similarly, if the sequence object had an associated btree,
 * then it would be more efficient to allow the sequence object
 * to evaluate the filter using the btree. Note that this
 * requires analysis of the filter expression to see if the
 * btree is usable.
 *

 * Ideally, we would allow all three modes, but for now, only
 * generate-and-test is implemented, and only a subset of that.
 * Generate-and-test is provided for referencing and selection. It is not
 * provided for projection (for now) because it essentially requires the
 * inverse of iteration and that is fairly tricky.
 */

public class CEConstraint implements Constraint
{
    //////////////////////////////////////////////////
    // Constants

    static protected final boolean PARSEDEBUG = false;
    static protected final boolean DEBUG = false;

    // Mnemonics

    static final String LBRACE = "{";
    static final String RBRACE = "}";

    //////////////////////////////////////////////////
    // Type Decls

    static protected enum Expand
    {
        NONE, EXPANDED, CONTRACTED
    }

    ;

    static protected class Segment
    {
        DapVariable var;
        List<Slice> slices; // projection slices for this variable
        List<DapDimension> dimset; // dimensions for the variable; including
        // redefs and anonymous derived from slices
        CEAST filter;

        Segment(DapVariable var)
        {
            this.var = var;
            this.slices = null; // added later
            this.filter = null; // added later
            this.dimset = null; // added later
        }

        void setDimset(List<DapDimension> dimset)
        {
            this.dimset = dimset;
        }

        void setSlices(List<Slice> slices)
                throws DapException
        {
            this.slices = slices;
            // Make sure they are finished
            for(Slice sl : slices) {
                sl.finish();
            }
        }

        void setFilter(CEAST filter)
        {
            this.filter = filter;
        }

        public String toString()
        {
            StringBuilder buf = new StringBuilder();
            buf.append(var.getFQN());
            if(slices != null)
                buf.append(slices.toString());
            if(this.filter != null) {
                buf.append("|");
                buf.append(filter.toString());
            }
            return buf.toString();
        }
    }

    /*
    static protected class ReferenceIterator implements Iterator<DapNode>
    {

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

        List<DapNode> list = new ArrayList<>();
        Iterator<DapNode> listiter = null;

        **
         * @param ce the constraint over which to iterate
         * @throws DapException
         *
        public ReferenceIterator(CEConstraint ce)
            throws DapException
        {
            list.addAll(ce.dimrefs);
            list.addAll(ce.enums);
            list.addAll(ce.variables);
            listiter = list.iterator();
        }

        //////////////////////////////////////////////////
        // Iterator Interface

        public boolean hasNext()
        {
            return listiter.hasNext();
        }

        public DapNode next()
        {
            return listiter.next();
        }

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

    }

    static protected class FilterIterator implements Iterator<DataCursor>
    {
        protected DapSequence seq;
        protected DataCursor data;
        protected long nrecords;
        protected CEAST filter;

        protected int recno;
        protected DataCursor current;
        CEConstraint ce;

        public FilterIterator(CEConstraint ce, DapSequence seq, DataCursor data, CEAST filter)
        {
            this.ce = ce;
            this.filter = filter;
            this.seq = seq;
            this.data = data;
            this.nrecords = data.getRecordCount();
            this.recno = 0; // actually recno of next record to read
            this.current = null;
        }

        // Iterator interface
        public boolean hasNext()
        {
            if(recno < nrecords)
                return false;
            try {
                // look for next matching record starting at recno
                if(filter == null) {
                    this.current = data.getRecord(this.recno);
                    this.recno++;
                    return true;
                } else for(;recno < nrecords;recno++) {
                    this.current = data.getRecord(this.recno);
                    if(ce.matches(this.seq, this.current, filter))
                        return true;
                }
            } catch (DapException de) {
                return false;
            }
            this.current = null;
            return false;
        }

        public DataCursor next()
        {
            if(this.recno >= nrecords || this.current == null)
                throw new NoSuchElementException();
            return this.current;
        }

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

    //////////////////////////////////////////////////
    // class variables and methods

    static protected Map<DapDataset, CEConstraint> universals = new HashMap<>();

    static public CEConstraint
    getUniversal(DapDataset dmr)
    {
        CEConstraint u = universals.get(dmr);
        if(u == null) {
            try {
                u = new Universal(dmr);
                universals.put(dmr, u);
            } catch (DapException de) {
                throw new IllegalArgumentException("DapDataSet has no universal");
            }
        }
        return u;
    }

    static public void release(DapDataset dmr)
    {
        universals.remove(dmr);
    }

    static protected DataCursor
    fieldValue(DapVariable sqvar, DapSequence seq, DataCursor record, String field)
            throws DapException
    {
        DapVariable dapv = seq.findByName(field);
        if(dapv == null)
            throw new DapException("Unknown variable in filter: " + field);
        if(!dapv.isAtomic())
            throw new DapException("Non-atomic variable in filter: " + field);
        if(dapv.getRank() > 0)
            throw new DapException("Non-scalar variable in filter: " + field);
        int fieldindex = seq.indexByName(field);
        DataCursor da = (DataCursor) (record.readField(fieldindex));
        if(da == null)
            throw new DapException("No such field: " + field);
        return da;
    }

    static protected int
    compare(Object lvalue, Object rvalue)
            throws DapException
    {
        if(lvalue instanceof String && rvalue instanceof String)
            return ((String) lvalue).compareTo((String) rvalue);
        if(lvalue instanceof Boolean && rvalue instanceof Boolean)
            return compare((Boolean) lvalue ? 1 : 0, (Boolean) rvalue ? 1 : 0);
        if(lvalue instanceof Double || lvalue instanceof Float
                || rvalue instanceof Double || rvalue instanceof Float) {
            double d1 = ((Number) lvalue).doubleValue();
            double d2 = ((Number) lvalue).doubleValue();
            return Double.compare(d1, d2);
        } else {
            long l1 = ((Number) lvalue).longValue();
            long l2 = ((Number) rvalue).longValue();
            return Long.compare(l1, l2);
        }
    }

    /**
     * Evaluate a filter with respect to a Sequence record.
     * Assumes the filter has been canonicalized so that
     * the lhs is a variable.
     *
     * @param seq    the template
     * @param record the record to evaluate
     * @param expr   the filter
     * @throws DapException
     * @returns the value of the expression (usually a Boolean)
     */
    protected Object
    eval(DapVariable var, DapSequence seq, DataCursor record, CEAST expr)
            throws DapException
    {
        switch (expr.sort) {

        case CONSTANT:
            return expr.value;

        case SEGMENT:
            return fieldValue(var, seq, record, expr.name);

        case EXPR:
            Object lhs = eval(var, seq, record, expr.lhs);
            Object rhs = (expr.rhs == null ? null : eval(var, seq, record, expr.rhs));
            if(rhs != null)
                switch (expr.op) {
                case LT:
                    return compare(lhs, rhs) < 0;
                case LE:
                    return compare(lhs, rhs) <= 0;
                case GT:
                    return compare(lhs, rhs) > 0;
                case GE:
                    return compare(lhs, rhs) >= 0;
                case EQ:
                    return lhs.equals(rhs);
                case NEQ:
                    return !lhs.equals(rhs);
                case REQ:
                    return lhs.toString().matches(rhs.toString());
                case AND:
                    return ((Boolean) lhs) && ((Boolean) rhs);
                }
            else switch (expr.op) {
            case NOT:
                return !((Boolean) lhs);
            }
        }
        throw new DapException("Malformed Filter");
    }

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

    // Information given to us by the compiler
    protected DapDataset dmr = null; // Underlying DMR

    /**
     * "Map" of variables (at all levels) to be included
     * Maps variables -> associated slices
     * and is modified by computdimensions().
     * Note that we keep the original insertion order
     */

    protected List<Segment> segments = new ArrayList<>();

    /**
     * Also keep a raw list of variables
     */
    protected List<DapVariable> variables = new ArrayList<>();

    // Track redefs
    protected Map<DapDimension, Slice> redefslice = new HashMap<DapDimension, Slice>();

    // Hold any extra attributes
    protected Map<DapNode, List<DapAttribute>> attributes = new HashMap<DapNode, List<DapAttribute>>();
    // Computed information

    // Map original dimension to the redef
    protected Map<DapDimension, DapDimension> redef = new HashMap<DapDimension, DapDimension>();

    // list of all referenced original dimensions
    protected List<DapDimension> dimrefs = new ArrayList<>();

    // List of enumeration decls to be included
    protected List<DapEnumeration> enums = new ArrayList<>();

    // List of group decls to be included
    protected List<DapGroup> groups = new ArrayList<>();

    // List of referenced shared dimensions (including redefs)
    protected List<DapDimension> refdims = new ArrayList<>();

    protected boolean finished = false;

    protected Expand expansion = Expand.NONE;

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

    public CEConstraint()
    {
    }

    public CEConstraint(DapDataset dmr)
    {
        this.dmr = dmr;
    }

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

    public boolean isUniversal() {return false;}

    public DapDataset
    getDMR()
    {
        return this.dmr;
    }

    public DapDimension getRedefDim(DapDimension orig)
    {
        return redef.get(orig);
    }

    public void addRedef(DapDimension dim, Slice slice)
    {
        this.redefslice.put(dim, slice);
    }

    public void addVariable(DapVariable var, List<Slice> slices)
            throws DapException
    {
        if(findVariableIndex(var) < 0) {
            Segment segment = new Segment(var);
            segment.setSlices(slices);
            this.segments.add(segment);
            this.variables.add(var);
        }
    }

    public void addAttribute(DapNode node, DapAttribute attr)
    {
        List<DapAttribute> attrs = this.attributes.get(node);
        if(attrs == null) {
            attrs = new ArrayList<DapAttribute>();
            this.attributes.put(node, attrs);
        }
        attrs.add(attr);
    }

    public void setFilter(DapVariable var, CEAST filter)
    {
        Segment seg = findSegment(var);
        if(seg != null)
            seg.filter = filter;
    }

    public List<Slice>
    getConstrainedSlices(DapVariable var)
            throws DapException
    {
        List<Slice> slices = null;
        Segment seg = findSegment(var);
        if(seg != null)
            slices = seg.slices;
        if(slices == null)
            slices = Universal.universalSlices(var);
        return slices;
    }

    public List<DapDimension>
    getConstrainedDimensions(DapVariable var)
    {
        List<DapDimension> dimset = null;
        int index = findVariableIndex(var);
        if(index >= 0)
            dimset = segments.get(index).dimset;
        return dimset;
    }

    //////////////////////////////////////////////////
    // Standard

    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        for(int i = 0; i < segments.size(); i++) {
            Segment seg = segments.get(i);
            if(!seg.var.isTopLevel())
                continue;
            if(!first) buf.append(";");
            first = false;
            dumpvar(seg, buf, false);
        }
        return buf.toString();
    }

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

    /**
     * Finish creating this Constraint.
     *
     * @throws DapException
     * @returns this - fluent interface
     */
    public CEConstraint
    finish()
            throws DapException
    {
        if(!finished) {
            finished = true;
            // order is important
            computeenums();
            computedimensions();
            computegroups();
        }
        return this;
    }

    /**
     * Convert the view to a constraint string suitable
     * for use in a URL, except not URL encoded.
     *
     * @return constraint string
     */
    public String toConstraintString()
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        for(int i = 0; i < segments.size(); i++) {
            Segment seg = segments.get(i);
            if(!seg.var.isTopLevel())
                continue;
            if(!first) buf.append(";");
            first = false;
            dumpvar(seg, buf, true);
        }
        return buf.toString();
    }

    /**
     * Recursive helper for tostring/toConstraintString
     *
     * @param seg
     * @param buf
     * @param forconstraint
     */
    protected void
    dumpvar(Segment seg, StringBuilder buf, boolean forconstraint)
    {
        if(seg.var.isTopLevel())
            buf.append(seg.var.getFQN());
        else
            buf.append(seg.var.getShortName());
        List<DapDimension> dimset = seg.var.getDimensions();
        // Add any slices
        List<Slice> slices = seg.slices;
        if(slices == null)
            dimset = new ArrayList<DapDimension>();
        else
            assert (dimset.size() == 0 && DapUtil.isScalarSlices(slices))
                    || (dimset.size() == slices.size());
        for(int i = 0; i < dimset.size(); i++) {
            Slice slice = slices.get(i);
            DapDimension dim = dimset.get(i);
            try {
                buf.append(forconstraint ? slice.toConstraintString() : slice.toString());
            } catch (DapException de) {
            }
        }
        DapType basetype = seg.var.getBaseType();

        // if the var is atomic, then we are done
        if(basetype.isAtomic())
            return;
        // If structure and all fields are in the view, then done
        if(basetype.getTypeSort().isCompound()) {
            DapStructure struct = (DapStructure)basetype;
            if(!isWholeCompound(struct)) {
                // Need to insert {...} and recurse
                buf.append(LBRACE);
                boolean first = true;
                for(DapVariable field : struct.getFields()) {
                    if(!first) buf.append(";");
                    first = false;
                    Segment fseg = findSegment(field);
                    dumpvar(fseg, buf, forconstraint);
                }
                buf.append(RBRACE);
            }
            if(basetype.getTypeSort().isSeqType() && seg.filter != null) {
                buf.append("|");
                buf.append(seg.filter.toString());
            }
        }

    }

    //////////////////////////////////////////////////
    // Reference processing

    /**
     * Reference X match
     *
     * @param node to test
     * @return true if node is referenced by this constraint
     */

    public boolean
    references(DapNode node)
    {
        boolean isref = false;
        switch (node.getSort()) {
        case DIMENSION:
            DapDimension dim = this.redef.get((DapDimension) node);
            if(dim == null) dim = (DapDimension) node;
            isref = this.dimrefs.contains(dim);
            break;
        case ENUMERATION:
            isref = (this.enums.contains((DapEnumeration) node));
            break;
        case VARIABLE:
            isref = (findVariableIndex((DapVariable) node) >= 0);
            break;
        case GROUP:
        case DATASET:
            isref = (this.groups.contains((DapGroup) node));
            break;
        default:
            break;
        }
        return isref;
    }

    /**
     * Reference X Iterator
     * Iterate over the variables and return
     * those that are referenced. The order of
     * return is preorder.
     * Inputs:
     * 1.  the variable whose slices are to be iterated.
     *
     * @return ReferenceIterator
     * @throws DapException if could not create.
     */
    /*
    public ReferenceIterator
    referenceIterator()
        throws DapException
    {
        return new ReferenceIterator(this);
    }
    */

    //////////////////////////////////////////////////
    // Projection processing

    /**
     * Projection X match
     * This is actually rather difficult because it requires
     * sort of the inverse of an odometer. For this reason,
     * It's implementation is deferred.
     */

    /**
     * Projection X Iterator
     * This basically returns an odometer that
     * will iterate over the appropriate values.
     *
     * @param var over whose dimensions to iterate
     * @throws DapException
     */
    /*
    public Odometer
    projectionIterator(DapVariable var)
        throws DapException
    {
        Segment seg = findSegment(var);
        if(seg == null)
            return null;
        return Odometer.factory(seg.slices, seg.dimset, false);
    }
    */

    //////////////////////////////////////////////////
    // Selection (Filter) processing

    /**
     * Selection X match
     *

     * Evaluate a filter with respect to a Sequence record.
     * Assumes the filter has been canonicalized.
     *
     * @param seq the template
     * @param rec the record to evaluate
     * @throws DapException
     * @returns true if the filter matches the record
     */
    public boolean match(DapVariable sqvar, DapSequence seq, DataCursor rec)
            throws DapException
    {
        Segment sseq = findSegment(sqvar);
        if(sseq == null)
            return false;
        CEAST filter = sseq.filter;
        if(filter == null)
            return true;
        return matches(sqvar, seq, rec, filter);

    }

    /**
     * Evaluate a filter with respect to a Sequence record.
     *
     * @param seq    the template
     * @param rec    the record to evaluate
     * @param filter the filter
     * @throws DapException
     * @returns true if a match
     */
    protected boolean
    matches(DapVariable var, DapSequence seq, DataCursor rec, CEAST filter)
            throws DapException
    {
        Object value = eval(var, seq, rec, filter);
        return ((Boolean) value);
    }

    //////////////////////////////////////////////////
    // Utilities

    /* Search the set of variables */
    protected int findVariableIndex(DapVariable var)
    {
        for(int i = 0; i < variables.size(); i++) {
            if(variables.get(i) == var)
                return i;
        }
        return -1;
    }

    protected Segment findSegment(DapVariable var)
    {
        for(int i = 0; i < segments.size(); i++) {
            if(segments.get(i).var == var)
                return segments.get(i);
        }
        return null;
    }

    /**
     * Locate each unexpanded Structure|Sequence and:
     * 1. check that none of its fields is referenced => do not expand
     * 2. add all of its fields as leaves
     * Note that #2 may end up adding additional leaf structs &/or seqs
     */
    public void
    expand()
    {
        // Create a queue of unprocessed leaf compounds
        Queue<DapVariable> queue = new ArrayDeque<DapVariable>();

        for(int i = 0; i < variables.size(); i++) {
            DapVariable var = variables.get(i);
            if(!var.isTopLevel())
                continue;
            // prime the queue
            DapType base = var.getBaseType();
            if(base.getTypeSort().isCompound()) {
                DapStructure struct = (DapStructure) base; // remember Sequence subclass Structure
                if(expansionCount(struct) == 0)
                    queue.add(var);
            }
        }
        // Process the queue in prefix order
        while(queue.size() > 0) {
            DapVariable vvstruct = queue.remove();
            DapStructure dstruct = (DapStructure) vvstruct.getBaseType();
            for(DapVariable field : dstruct.getFields()) {
                if(findVariableIndex(field) < 0) {
                    // Add field as leaf
                    this.segments.add(new Segment(field));
                    this.variables.add(field);
                }
                DapType fbase = field.getBaseType();
                if(fbase.getTypeSort().isCompound()) {
                    if(expansionCount((DapStructure) fbase) == 0)
                        queue.add(field);
                }
            }
        }
        this.expansion = Expand.EXPANDED;
    }

    /**
     * Locate each Structure|Sequence and:
     * 1. check that all of its fields are referenced recursively and not constrained,
     * otherwise ignore
     * 2. contract by removing all of the fields of the Structure or Sequence.
     * This is intended to be (not quite) the dual of expand();
     */
    public void
    contract()
    {
        // Create a set of contracted compounds
        Set<DapStructure> contracted = new HashSet<>();
        for(int i = 0; i < variables.size(); i++) {
            DapVariable var = variables.get(i);
            if(var.isTopLevel()) {
                DapType base = var.getBaseType();
                if(base.getTypeSort().isCompound()) {
                    contractR((DapStructure) base, contracted);
                }
            }
        }
        this.expansion = Expand.CONTRACTED;
    }

    /**
     * Recursive helper
     *
     * @param dstruct    to contract
     * @param contracted set of already contracted compounds
     * @return true if this structure was contracted, false otherwise
     */

    protected boolean
    contractR(DapStructure dstruct, Set<DapStructure> contracted)
    {
        if(contracted.contains(dstruct))
            return true;
        int processed = 0;
        List<DapVariable> fields = dstruct.getFields();
        for(DapVariable field : fields) {
            if(findVariableIndex(field) < 0)
                break; // this compound cannot be contracted
            DapType base = field.getBaseType();
            if(base.getTypeSort().isCompound()
                    && !contracted.contains((field))) {
                if(!contractR((DapStructure)base, contracted))
                    break; // this compound cannot be contracted
            }
            processed++;
        }
        if(processed < fields.size())
            return false;
        contracted.add(dstruct); // all compound fields were successfully contracted.
        return true;
    }

    /**
     * Count the number of fields of a structure that
     * already in this view.
     *
     * @param struct the dapstructure to check
     * @return # of fields in this view
     * @throws DapException
     */

    protected int
    expansionCount(DapStructure struct)
    {
        int count = 0;
        for(DapVariable field : struct.getFields()) {
            if(findVariableIndex(field) >= 0) count++;
        }
        return count;
    }

    /**
     * See if a structure is "whole", which
     * means that none of its fields is missing from the
     * constraint, all of fields use default (non-constrained)
     * dimension), and all of its fields are also whole.
     * This must be done recursively.
     *
     * @param dstruct to test
     * @return true if this structure is whole.
     */

    protected boolean
    isWholeCompound(DapStructure dstruct)
    {
        int processed = 0;
        List<DapVariable> fields = dstruct.getFields();
        for(DapVariable field : fields) {
            // not contractable if this field has non-original dimensions
            Segment seg = findSegment(field);
            if(seg == null)
                break; // this compound is not whole
            List<Slice> slices = seg.slices;
            if(slices != null) {
                for(Slice slice : slices) {
                    if(slice.isConstrained())
                        break;
                }
            }
            DapType base = field.getBaseType();
            if(base.getTypeSort().isCompound()) {
                if(!isWholeCompound((DapStructure) base))
                    break; // this compound is not whole

            }
            processed++;
        }
        return (processed == fields.size());
    }

    //////////////////////////////////////////////////
    // Utilities for computing inferred information

    /**
     * Compute dimension related information
     * using slicing and redef info.
     * In effect, this is where projection constraints
     * are applied
     *

     * Assume that the constraint compiler has given us the following info:
     *

     *
 A list of the variables to include.
     *
 A pair (DapDimension,Slice) for each redef
     *
 For each variable in #1, a list of slices
     * taken from the constraint expression
     *

     *

     * Two products will be produced.
     *

     *
 The variables map will be modified so that the
     * slices properly reflect any original or redef dimensions.
     *
 A set, dimrefs, of all referenced original dimensions.
     *

     *

     * The processing is as follows
     *

     *
 For each redef create a new redef dimension
     *
 For each variable:
     *

     *
 if the variable is scalar, do nothing.
     *
 if the variable has no associated slices, then make its
     * new dimensions be the original dimensions.
     *
 otherwise, walk the slices and create new dimensions
     * from them; use redefs where indicated
     *

     *

     *

     */
    protected void
    computedimensions()
            throws DapException
    {
        // Build the redefmap
        for(DapDimension key : redefslice.keySet()) {
            Slice slice = redefslice.get(key);
            DapDimension newdim = (DapDimension) key.clone();
            newdim.setSize(slice.getCount());
            redef.put(key, newdim);
        }

        // Process each variable
        for(int i = 0; i < segments.size(); i++) {
            Segment seg = segments.get(i);
            if(seg.var.getRank() == 0)
                continue;
            List<Slice> slices = seg.slices;
            List<DapDimension> orig = seg.var.getDimensions();
            List<DapDimension> newdims = new ArrayList<>();
            // If the slice list is short then pad it with
            // default slices
            if(slices == null)
                slices = new ArrayList<Slice>();
            while(slices.size() < orig.size()) // pad
            {
                slices.add(new Slice().setConstrained(false));
            }
            assert (slices != null && slices.size() == orig.size());
            for(int j = 0; j < slices.size(); j++) {
                Slice slice = slices.get(j);
                DapDimension dim0 = orig.get(j);
                DapDimension newdim = redef.get(dim0);
                if(newdim == null)
                    newdim = dim0;
                // fill in the undefined last value
                slice.setMaxSize(newdim.getSize());
                slice.finish();
                Slice newslice = null;
                if(slice.isConstrained()) {
                    // Construct an anonymous dimension for this slice
                    newdim = new DapDimension(slice.getCount());
                } else { // replace with a new slice from the dim
                    newslice = new Slice(newdim);
                    if(newslice != null) {
                        // track set of referenced non-anonymous dimensions
                        if(!dimrefs.contains(dim0)) dimrefs.add(dim0);
                        slices.set(j, newslice);
                    }
                }
                // record the dimension per variable
                newdims.add(newdim);
            }
            seg.setDimset(newdims);
        }
    }

    /**
     * Walk all the included variables and accumulate
     * the referenced enums
     */
    protected void computeenums()
    {
        for(int i = 0; i < variables.size(); i++) {
            DapVariable var = variables.get(i);
            if(var.getSort() != DapSort.VARIABLE)
                continue;
            DapType daptype = var.getBaseType();
            if(!daptype.isEnumType())
                continue;
            if(!this.enums.contains((DapEnumeration) daptype))
                this.enums.add((DapEnumeration) daptype);
        }
    }

    /**
     * Walk all the included declarations
     * and accumulate the set of referenced groups
     */
    protected void computegroups()
    {
        // 1. variables
        for(int i = 0; i < variables.size(); i++) {
            DapVariable var = variables.get(i);
            List<DapGroup> path = var.getGroupPath();
            for(DapGroup group : path) {
                if(!this.groups.contains(group))
                    this.groups.add(group);
            }
        }
        // 2. Dimensions
        for(DapDimension dim : this.dimrefs) {
            if(!dim.isShared())
                continue;
            List<DapGroup> path = dim.getGroupPath();
            for(DapGroup group : path) {
                if(!this.groups.contains(group))
                    this.groups.add(group);
            }
        }
        // 2. enumerations
        for(DapEnumeration en : this.enums) {
            List<DapGroup> path = en.getGroupPath();
            for(DapGroup group : path) {
                if(!this.groups.contains(group))
                    this.groups.add(group);
            }
        }
    }

    //////////////////////////////////////////////////
    // Static Utility for compiling a constraint string

    static public CEConstraint
    compile(String sce, DapDataset dmr)
            throws DapException
    {
        // Process any constraint
        if(sce == null || sce.length() == 0)
            return CEConstraint.getUniversal(dmr);
        CEParserImpl ceparser = new CEParserImpl(dmr);
        if(PARSEDEBUG)
            ceparser.setDebugLevel(1);
        if(DEBUG) {
            System.err.println("Dap4Servlet: parsing constraint: |" + sce + "|");
        }
        boolean ok;
        try {
            ok = ceparser.parse(sce);
        } catch (ParseException pe) {
            ok = false;
        }
        if(!ok)
            throw new DapException("Constraint parse failed: " + sce);
        CEAST root = ceparser.getCEAST();
        CECompiler compiler = new CECompiler();
        CEConstraint ce = compiler.compile(dmr, root);
        ce.expand();
        ce.finish();
        return ce;
    }

}


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

package opendap.dap;

import java.io.*;

/**
 * A helper class for DVector.  It allows DVector,
 * and by extension, DArray and DList, to use more
 * efficient primitive types to hold large arrays of data.
 * A PrimitiveVector class is defined for each
 * primitive type supported in OPeNDAP, and a
 * BaseTypePrimitiveVector class handles DArrays
 * and DLists of compound types.
 *
 * @author jehamby
 * @version $Revision: 15901 $
 * @see BaseType
 * @see DVector
 */
abstract public class PrimitiveVector extends DAPNode implements ClientIO
{
    /**
     * Template variable to use for printDecl and
     * deserialize (BaseTypePrimitiveVector only).
     */
    private BaseType var;

    /**
     * Constructs a new PrimitiveVector.
     *
     * @param var the template BaseType to use.
     */
    public PrimitiveVector(BaseType var) {
        this.var = var;
    }

    /**
     * Returns the template variable for this vector.
     *
     * @return the template variable for this vector.
     * @see BaseTypePrimitiveVector#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public final BaseType getTemplate() {
        return var;
    }

    /**
     * Returns the number of elements in the array.
     *
     * @return the number of elements in the array.
     */
    abstract public int getLength();

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

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

    /**
     * Reads data from a DataInputStream. This method is only used
     * on the client side of the OPeNDAP client/server connection.
     *
     * @param source   a DataInputStream to read from.
     * @param sv       The ServerVersion returned by the server.
     *                 (used by DSequence to determine which protocol version was
     *                 used).
     * @param statusUI The StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws DataReadException when invalid data is read, or if the user
     *                           cancels the download.
     * @throws EOFException      if EOF is found before the variable is completely
     *                           deserialized.
     * @throws IOException       thrown on any other InputStream exception.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    abstract public void deserialize(DataInputStream source, ServerVersion sv,
                                     StatusUI statusUI)
            throws IOException, EOFException, DataReadException;

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

    /**
     * Write a subset of the data to a DataOutputStream.
     *
     * @param sink a DataOutputStream to write to.
     * @param start: starting index (i=start)
     * @param stop: ending index (i<=stop)
     * @param stride: index stride (i+=stride)
     * @exception IOException thrown on any OutputStream exception.
     */
    // abstract public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException;

    /**
     * Write the variable's declaration in a C-style syntax. This
     * method is used to create textual representation of the Data
     * Descriptor Structure (DDS).
     *
     * @param os          The PrintWriter on which to print the
     *                    declaration.
     * @param space       Each line of the declaration will begin with the
     *                    characters in this string.  Usually used for leading spaces.
     * @param print_semi  a boolean value indicating whether to print a
     *                    semicolon at the end of the declaration.
     * @param constrained a boolean value indicating whether to print
     *                    the declartion dependent on the projection information. This
     *                    is only used by Server side code.
     * @see BaseType#printDecl(PrintWriter, String, boolean, boolean)
     */
    public final void printDecl(PrintWriter os, String space,
                                boolean print_semi, boolean constrained) {

        //os.println("PrimitiveVector.printDecl()");
        //os.println(var.getTypeName()+".isProject():"+((ServerMethods)var).isProject());
        var.printDecl(os, space, print_semi, constrained);
    }

    /**
     * Write the variable's declaration in a C-style syntax. This
     * method is used to create textual representation of the Data
     * Descriptor Structure (DDS).
     *
     * @param os         The PrintWriter on which to print the
     *                   declaration.
     * @param space      Each line of the declaration will begin with the
     *                   characters in this string.  Usually used for leading spaces.
     * @param print_semi a boolean value indicating whether to print a
     *                   semicolon at the end of the declaration.
     * @see BaseType#printDecl(PrintWriter, String, boolean)
     */
    public final void printDecl(PrintWriter os, String space,
                                boolean print_semi) {
        printDecl(os, space, print_semi, false);
    }

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

/**
     * Prints the value of all variables in this vector.  This
     * method is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param space this value is passed to the printDecl method,
     *              and controls the leading spaces of the output.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    abstract public void printVal(PrintWriter os, String space);

    /**
     * Prints the value of a single variable in this vector.
     * method is used by DArray's printVal method.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param index the index of the variable to print.
     * @see DArray#printVal(PrintWriter, String, boolean)
     */
    abstract public void printSingleVal(PrintWriter os, int index);

    /**
     * Returns (a reference to) the internal storage for PrimitiveVector.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     */
    abstract public Object getInternalStorage();

    /**
     * Set the internal storage for PrimitiveVector.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     */
    abstract public void setInternalStorage(Object o);

    /**
     * Create a new primitive vector using a subset of the data.
     *
     * @param start  starting index (i=start)
     * @param stop   ending index (i<=stop)
     * @param stride index stride (i+=stride)
     * @return new primitive vector
     */
    abstract public PrimitiveVector subset(int start, int stop, int stride);

    /**
     * Returns a clone of this PrimitiveVector.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this PrimitiveVector.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
            PrimitiveVector v = (PrimitiveVector) super.cloneDAG(map);
            v.var = (BaseType) cloneDAG(map,var);
            return v;
    }

}


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

import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.AttributeContainerHelper;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.util.Optional;

import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Time2DCoordSys with run and 2D time coordinate axes. LOOK not complete
 * A CoordSys has 0 or 1 Time2DCoordSys, but there may be several in a Dataset.
 *
 * @author John
 * @since 8/14/2015
 */
@Immutable
public class Time2DCoordSys {
  public final CoverageCoordAxis1D runAxis;
  public final TimeAxis2DFmrc timeAxis2D;

  public Time2DCoordSys(CoverageCoordAxis1D runAxis, TimeAxis2DFmrc timeAxis) {
    this.runAxis = runAxis;
    this.timeAxis2D = timeAxis;
  }

  public List<CoverageCoordAxis> getCoordAxes() throws InvalidRangeException {
    List<CoverageCoordAxis> result = new ArrayList<>();
    result.add(runAxis);
    result.add(timeAxis2D);
    return result;
  }

  public int[] getShape() {
    return timeAxis2D.getShape();
  }

  /*
    (from CdmrfParams.adoc) :

    2D Time subsetting
    A 2D time dataset will have CoverageType set to FMRC.
    You may specify a runtime with a date, latest or all; specify a timeOffset with a numeric value, first, or all.
      If only one is set, use the default for the other. If neither is set, then return all times for latest runtime.
    Time parameters are only used if explicitly set and timeOffset is not set. There are only 2 cases where time can be used:
    Set runtime to a specific value or latest (not all). Time parameters (point or range) can be used.
    Set runtime to all. Time point (date, or present) can be used.
    Special cases:
      set specific runtime = constant runtime dataset
      set specific timeOffset, set runTime to all = constant offset dataset
      set specific time, set runTime to all = constant forecast dataset
   */
  /*
  1) single runtime
     1a timeOffset
     1b time or timeRange
     1c none = constant runtime dataset
  2) multiple runtimes
     2a timeOffset       = constant offset dataset
     2b time (not range) = constant forecast dataset
   */
  public Optional<List<CoverageCoordAxis>> subset(SubsetParams params, AtomicBoolean isConstantForcast, boolean makeCFcompliant) {
    return Optional.empty("not implemented by "+getClass().getName());

    /* List<CoverageCoordAxis> result = new ArrayList<>();

    Optional<CoverageCoordAxis> axiso = runAxis.subset(params);
    if (!axiso.isPresent())
      return Optional.empty(axiso.getErrorMessage());
    CoverageCoordAxis1D runAxisSubset = (CoverageCoordAxis1D) axiso.get();
    result.add(runAxisSubset);

    // subset on timeOffset (1a, 1c, 2a)
    if (params.hasTimeOffsetParam() || !params.hasTimeParam()) {
      axiso = timeAxis2D.subset(params);
      if (!axiso.isPresent())
        return Optional.empty(axiso.getErrorMessage());
      CoverageCoordAxis timeOffsetSubset = axiso.get();
      result.add(timeOffsetSubset);

      if (makeCFcompliant)
        result.add(makeCFTimeCoord(runAxisSubset, (CoverageCoordAxis1D) timeOffsetSubset)); // possible the twoD time case, if nruns > 1
      return Optional.of(result);
    }

    // subset on time, # runtimes = 1 (1b)
    if (runAxisSubset.getNcoords() == 1) {
      double val = runAxisSubset.getCoord(0);   // not sure runAxis is needed. maybe use runtimeSubset
      CalendarDate runDate = runAxisSubset.makeDate(val);
      Optional<TimeOffsetAxis> too = timeAxis2D.subsetFromTime(params, runDate);
      if (!too.isPresent())
        return Optional.empty(too.getErrorMessage());
      TimeOffsetAxis timeOffsetSubset =  too.get();
      result.add(timeOffsetSubset);

      if (makeCFcompliant)
        result.add(makeCFTimeCoord(runAxisSubset, timeOffsetSubset));
      return Optional.of(result);
    }

    // tricky case 2b time (point only not range) = constant forecast dataset
    // data reader has to skip around the 2D times
    // 1) the runtimes may be subset by whats available
    // 2) timeOffset could become an aux coordinate
    // 3) time coordinate becomes a scalar,
    isConstantForcast.set(true);

    CalendarDate dateWanted;
    if (params.isTrue(SubsetParams.timePresent))
      dateWanted = CalendarDate.present();
    else
      dateWanted = (CalendarDate) params.get(SubsetParams.time);
    if (dateWanted == null)
      throw new IllegalStateException("Must have time parameter");

    double wantOffset = runAxisSubset.convert(dateWanted); // forecastDate offset from refdate
    double start = timeAxis.getStartValue();
    double end = timeAxis.getEndValue();
    CoordAxisHelper helper = new CoordAxisHelper(timeOffset);

    // brute force search LOOK specialize for regular ?
    List<Integer> runtimeIdx = new ArrayList<>();  // list of runtime indexes that have this forecast
    // List<Integer> offsetIdx = new ArrayList<>();  // list of offset indexes that have this forecast
    List<Double> offset = new ArrayList<>();      // corresponding offset from start of run
    for (int i=0; i<runAxisSubset.getNcoords(); i++) {
      // public double getOffsetInTimeUnits(CalendarDate convertFrom, CalendarDate convertTo);
      double runOffset = runAxisSubset.getCoord(i);
      if (end + runOffset < wantOffset) continue;
      if (wantOffset < start + runOffset) break;
      int idx = helper.search(wantOffset - runOffset);
      if (idx >= 0) {
        runtimeIdx.add(i);  // the ith runtime
        // offsetIdx.add(idx);   // the idx time offset
        offset.add(wantOffset - runOffset);   // the offset from the runtime
      }
    }

    // here are the runtimes
    int ncoords = runtimeIdx.size();
    double[] runValues = new double[ncoords];
    double[] offsetValues = new double[ncoords];
    int count = 0;
    for (int k=0; k<ncoords; k++) {
      offsetValues[count] = offset.get(k);
      runValues[count++] = runAxisSubset.getCoord( runtimeIdx.get(k));
    }

    CoverageCoordAxisBuilder runbuilder = new CoverageCoordAxisBuilder(runAxisSubset)
            .subset(null, CoverageCoordAxis.Spacing.irregularPoint, ncoords, runValues); // LOOK check for regular (in CovCoordAxis ?)
    CoverageCoordAxis1D runAxisSubset2 = new CoverageCoordAxis1D(runbuilder);

    CoverageCoordAxisBuilder timebuilder = new CoverageCoordAxisBuilder(timeOffset)
            .subset(runAxisSubset2.getName(), CoverageCoordAxis.Spacing.irregularPoint, ncoords, offsetValues); // aux coord (LOOK interval) ??
    CoverageCoordAxis1D timeOffsetSubset = new TimeOffsetAxis(timebuilder);

    CoverageCoordAxis scalarTimeCoord = makeScalarTimeCoord(wantOffset, runAxisSubset);

    // nothing needed for CF, the run coordinate acts as the CF time independent coord. timeOffset is aux, forecastTime is scalar
    return Optional.of(Lists.newArrayList(runAxisSubset2, timeOffsetSubset, scalarTimeCoord)); // */
  }

  private CoverageCoordAxis makeScalarTimeCoord(double val, CoverageCoordAxis1D runAxisSubset) {
    String name = "constantForecastTime";
    String desc = "forecast time";
    AttributeContainerHelper atts = new AttributeContainerHelper(name);
    atts.addAttribute(new Attribute(CDM.UNITS, runAxisSubset.getUnits()));
    atts.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    atts.addAttribute(new Attribute(CDM.LONG_NAME, desc));
    atts.addAttribute(new Attribute(CF.CALENDAR, runAxisSubset.getCalendar().toString()));

    CoverageCoordAxisBuilder builder = new CoverageCoordAxisBuilder(name, runAxisSubset.getUnits(), desc, DataType.DOUBLE, AxisType.Time, atts,
            CoverageCoordAxis.DependenceType.scalar, null, CoverageCoordAxis.Spacing.regularPoint, 1, val, val, 0.0, null, null);
    builder.setIsSubset(true);

    return new CoverageCoordAxis1D(builder);
  }

  private CoverageCoordAxis makeCFTimeCoord(CoverageCoordAxis1D runAxisSubset, CoverageCoordAxis1D timeAxisSubset) {
    String name = timeAxisSubset.getName()+"Forecast";
    String desc = "forecast time";
    AttributeContainerHelper atts = new AttributeContainerHelper(name);
    atts.addAttribute(new Attribute(CDM.UNITS, runAxisSubset.getUnits()));
    atts.addAttribute(new Attribute(CF.STANDARD_NAME, CF.TIME));
    atts.addAttribute(new Attribute(CDM.LONG_NAME, desc));
    atts.addAttribute(new Attribute(CF.CALENDAR, runAxisSubset.getCalendar().toString()));

    if (runAxisSubset.getNcoords() == 1) {
      CoverageCoordAxisBuilder builder = new CoverageCoordAxisBuilder();
      builder.name = name;
      builder.units = runAxisSubset.getUnits();
      builder.description = desc;
      builder.dataType = DataType.DOUBLE;
      builder.axisType = AxisType.Time;
      builder.attributes = atts;
      builder.dependenceType = CoverageCoordAxis.DependenceType.dependent;
      builder.setDependsOn(timeAxisSubset.getName());

      builder.spacing = timeAxisSubset.getSpacing();
      builder.ncoords = timeAxisSubset.ncoords;
      builder.isSubset = true;

      // conversion from timeOffset to runtime units
      double offset = timeAxisSubset.getOffsetInTimeUnits(runAxis.getRefDate(), timeAxisSubset.getRefDate());

      switch (timeAxisSubset.getSpacing()) {
        case regularInterval:
        case regularPoint:
          builder.startValue = timeAxisSubset.getStartValue() + offset;
          builder.endValue = timeAxisSubset.getEndValue() + offset;
          break;

        case contiguousInterval:
        case irregularPoint:
        case discontiguousInterval:
          builder.values = timeAxisSubset.getValues(); // this is a copy
          for (int i=0; i<builder.values.length; i++)
            builder.values[i] += offset;
          break;
      }

      return new CoverageCoordAxis1D(builder);
    }

    return null;
  }

}


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

package ucar.nc2.grib.collection;

import ucar.nc2.constants.DataFormatType;
import ucar.nc2.grib.grib2.*;
import ucar.nc2.grib.*;
import ucar.nc2.grib.grib2.table.Grib2Tables;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.io.http.HTTPRandomAccessFile;
import ucar.unidata.util.StringUtil2;

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

/**
 * Grib-2 Collection IOSP.
 * Handles both collections and single GRIB files.
 *
 * @author caron
 * @since 4/6/11
 */
public class Grib2Iosp extends GribIosp {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Grib2Iosp.class);

  static String makeVariableNameFromTable(Grib2Tables cust,
      GribCollectionImmutable gribCollection,
      GribCollectionImmutable.VariableIndex vindex, boolean useGenType) {

    try (Formatter f = new Formatter()) {
      GribTables.Parameter param = cust
          .getParameter(vindex.getDiscipline(), vindex.getCategory(), vindex.getParameter());

      if (param == null) {
        f.format("VAR%d-%d-%d_FROM_%d-%d-%d", vindex.getDiscipline(), vindex.getCategory(),
            vindex.getParameter(), gribCollection.getCenter(),
            gribCollection.getSubcenter(), vindex.getTableVersion());
      } else {
        f.format("%s", GribUtils.makeNameFromDescription(param.getName()));
      }

      if (vindex.getGenProcessType() == 6 || vindex.getGenProcessType() == 7) {
        f.format("_error");  // its an "error" type variable - add to name

      } else if (useGenType && vindex.getGenProcessType() >= 0) {
          String genType = cust.getGeneratingProcessTypeName(vindex.getGenProcessType());
          String s = StringUtil2.substitute(genType, " ", "_");
          f.format("_%s", s);
      }

      if (vindex.getLevelType() != GribNumbers.UNDEFINED) { // satellite data doesnt have a level
        f.format("_%s", cust.getLevelNameShort(
            vindex.getLevelType())); // vindex.getLevelType()); // code table 4.5
        if (vindex.isLayer()) {
          f.format("_layer");
        }
      }

      String intvName = vindex.getIntvName();
      if (intvName != null && !intvName.isEmpty()) {
        f.format("_%s", intvName);
      }

      if (vindex.getIntvType() >= 0) {
        String statName = cust.getStatisticNameShort(vindex.getIntvType());
        if (statName != null) {
          f.format("_%s", statName);
        }
      }

      if (vindex.getSpatialStatisticalProcessType() >= 0) {
        String statName = cust.getTableValue("4.10", vindex.getSpatialStatisticalProcessType());
        if (statName != null) {
          f.format("_%s", statName);
        }
      }

      if (vindex.getEnsDerivedType() >= 0) {
        f.format("_%s", cust.getProbabilityNameShort(vindex.getEnsDerivedType()));
      } else if (vindex.getProbabilityName() != null && vindex.getProbabilityName().length() > 0) {
        String s = StringUtil2.substitute(vindex.getProbabilityName(), ".", "p");
        f.format("_probability_%s", s);
      } else if (vindex.isEnsemble()) {
        f.format("_ens");
      }
      return f.toString();
    }
  }

  static String makeVariableLongName(Grib2Tables cust,
      GribCollectionImmutable.VariableIndex vindex, boolean useGenType) {

    try (Formatter f = new Formatter()) {
      boolean isProb = (vindex.getProbabilityName() != null
          && vindex.getProbabilityName().length() > 0);
      if (isProb) {
        f.format("Probability ");
      }

      GribTables.Parameter gp = cust
          .getParameter(vindex.getDiscipline(), vindex.getCategory(), vindex.getParameter());
      if (gp == null) {
        f.format("Unknown Parameter %d-%d-%d", vindex.getDiscipline(), vindex.getCategory(),
            vindex.getParameter());
      } else {
        f.format("%s", gp.getName());
      }

      if (vindex.getIntvType() >= 0 && vindex.getIntvName() != null && !vindex.getIntvName()
          .isEmpty()) {
        String intvName = cust.getStatisticNameShort(vindex.getIntvType());
        if (intvName == null || intvName.equalsIgnoreCase("Missing")) {
          intvName = cust.getStatisticNameShort(vindex.getIntvType());
        }
        if (intvName == null) {
          f.format(" (%s)", vindex.getIntvName());
        } else {
          f.format(" (%s %s)", vindex.getIntvName(), intvName);
        }

      } else if (vindex.getIntvType() >= 0) {
        String intvName = cust.getStatisticNameShort(vindex.getIntvType());
        f.format(" (%s)", intvName);
      }

      if (vindex.getSpatialStatisticalProcessType() >= 0) {
        String statName = cust.getTableValue("4.10", vindex.getSpatialStatisticalProcessType());
        if (statName != null) {
          f.format("_%s", statName);
        }
      }

      if (vindex.getEnsDerivedType() >= 0) {
        f.format(" (%s)", cust.getTableValue("4.10", vindex.getEnsDerivedType()));
      } else if (isProb) {
        f.format(" %s %s", vindex.getProbabilityName(),
            getVindexUnits(cust, vindex)); // add data units here
      }

      if (vindex.getGenProcessType() == 6 || vindex.getGenProcessType() == 7) {
        f.format(" error");  // its an "error" type variable - add to name

      } else if (useGenType && vindex.getGenProcessType() >= 0) {
        f.format(" %s", cust.getGeneratingProcessTypeName(vindex.getGenProcessType()));
      }

      if (vindex.getLevelType() != GribNumbers.UNDEFINED) { // satellite data doesnt have a level
        f.format(" @ %s", cust.getTableValue("4.5", vindex.getLevelType()));
        if (vindex.isLayer()) {
          f.format(" layer");
        }
      }
      return f.toString();
    }
  }

  @Override
  protected String makeVariableName(GribCollectionImmutable.VariableIndex vindex) {
    return makeVariableNameFromTable(cust, gribCollection, vindex, gribCollection.config.gribConfig.useGenType);
  }

  @Override
  protected String makeVariableLongName(GribCollectionImmutable.VariableIndex vindex) {
    return makeVariableLongName(cust, vindex, gribCollection.config.gribConfig.useGenType);
  }

  @Override
  protected String makeVariableUnits(GribCollectionImmutable.VariableIndex vindex) {
    return makeVariableUnits(cust, vindex);
  }

  static String makeVariableUnits(Grib2Tables tables,
      GribCollectionImmutable.VariableIndex vindex) {
    if (vindex.getProbabilityName() != null && vindex.getProbabilityName().length() > 0) return "%";
    return getVindexUnits(tables, vindex);
  }

  private static String getVindexUnits(Grib2Tables tables, GribCollectionImmutable.VariableIndex vindex) {
    GribTables.Parameter gp = tables.getParameter(vindex.getDiscipline(), vindex.getCategory(), vindex.getParameter());
    String val = (gp == null) ? "" : gp.getUnit();
    return (val == null) ? "" : val;
  }

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

  private Grib2Tables cust;

  // accept grib2 or ncx files
  @Override
  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    if (raf instanceof HTTPRandomAccessFile) { // only do remote if memory resident
      if (raf.length() > raf.getBufferSize())
        return false;

    } else {                                  // wont accept remote index
      GribCdmIndex.GribCollectionType type = GribCdmIndex.getType(raf);
      if (type == GribCdmIndex.GribCollectionType.GRIB2) return true;
      if (type == GribCdmIndex.GribCollectionType.Partition2) return true;
    }

    // check for GRIB2 data file
    return Grib2RecordScanner.isValidFile(raf);
  }

  @Override
  public String getFileTypeId() {
    return DataFormatType.GRIB2.getDescription();
  }

  @Override
  public String getFileTypeDescription() {
    return "GRIB2 Collection";
  }

  // public no-arg constructor for reflection
  public Grib2Iosp() {
    super(false, logger);
  }

  Grib2Iosp(GribCollectionImmutable.GroupGC gHcs, GribCollectionImmutable.Type gtype) {
    super(false, logger);
    this.gHcs = gHcs;
    this.owned = true;
    this.gtype = gtype;
  }

  // LOOK more likely we will set an individual dataset
  Grib2Iosp(GribCollectionImmutable gc) {
    super(false, logger);
    this.gribCollection = gc;
    this.owned = true;
  }

  @Override
  protected ucar.nc2.grib.GribTables createCustomizer() {
    cust = Grib2Tables.factory(gribCollection.getCenter(), gribCollection.getSubcenter(), gribCollection.getMaster(), gribCollection.getLocal(),
            gribCollection.getGenProcessId());
    return cust;
  }

  @Override
  protected String getVerticalCoordDesc(int vc_code) {
    return cust.getTableValue("4.5", vc_code);
  }

  @Override
  protected GribTables.Parameter getParameter(GribCollectionImmutable.VariableIndex vindex) {
    return cust.getParameter(vindex.getDiscipline(), vindex.getCategory(), vindex.getParameter());
  }

  public Object getLastRecordRead() {
    return Grib2Record.lastRecordRead;
  }

  public void clearLastRecordRead() {
    Grib2Record.lastRecordRead = null;
  }

  public Object getGribCustomizer() {
    return cust;
  }
}


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

import thredds.client.catalog.*;
import ucar.nc2.constants.DataFormatType;
import ucar.nc2.ui.widget.*;
import ucar.nc2.util.IO;
import ucar.util.prefs.PreferencesExt;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;

/**
 * This is the THREDDS User Interface for nj22 ToolsUI.
 * Throws PropertyChangeEvent when a dataset is selected, see addPropertyChangeListener.
 *
 * @author caron
 */
public class ThreddsUI extends JPanel {
  // store keys
  static private final String VIEWER_SIZE = "ViewerSize";
  static private final String SOURCE_WINDOW_SIZE = "SourceWindowSize";
  static private final String XML_WINDOW_SIZE = "XmlWindowSize";

  // tabs

  private PreferencesExt store;
  private Component parent;

  // the main components
  private ThreddsDatasetChooser datasetChooser = null;

  // UI components that need global scope
  private TextGetPutPane sourcePane;
  private JTabbedPane tabbedPane;
  //private JMenu debugMenu;

  private TextHistoryPane xmlPane;
  private IndependentDialog xmlWindow = null;
  private IndependentWindow sourceWindow = null;

  // the various managers and dialog boxes
  FileManager fileChooser = null; // shared with component viewers

  //private JDialog datasetChooserDialog;
  //private IndependentDialog datasetURLDialog = null;

  // debugging
  //private boolean debugBeans = false, debugChooser = false, debugPrint = false, debugHelp = false;
  private boolean debugSelection = false;
  private boolean debugTab = false;

  public ThreddsUI(JFrame parent, PreferencesExt store) {
    this.store = store;
    this.parent = parent;
    //parent = topLevel.getRootPaneContainer().getRootPane();

    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    Dimension d = (Dimension) store.getBean(VIEWER_SIZE, null);
    int defaultWidth = 700;
    int defaultHeight = 350;
    setPreferredSize((d != null) ? d : new Dimension(defaultWidth, defaultHeight));

    try {
      makeActionsSystem();
      makeActionsDataset();

      //makeMenu();
      makeUI();

    } catch (Exception e) {
      System.out.println("UI creation Exception");
      e.printStackTrace();
    }

    // other components
    PreferencesExt fcPrefs = (PreferencesExt) store.node("FileManager");
    FileFilter[] filters = new FileFilter[]{new FileManager.NetcdfExtFilter()};
    fileChooser = new FileManager(parent, null, filters, fcPrefs);
  }

  private void makeUI() throws Exception {
    tabbedPane = new JTabbedPane(JTabbedPane.TOP);

    /// catalog, DQC, query choosers
    datasetChooser = makeDatasetChooser(); // adds itself to the JTabbedPane

    // panel to show source
    sourcePane = new TextGetPutPane((PreferencesExt) store.node("getputPane"));
    sourceWindow = new IndependentWindow("Source", BAMutil.getImage("threddsIcon.png"), sourcePane);
    sourceWindow.setBounds((Rectangle) store.getBean(SOURCE_WINDOW_SIZE, new Rectangle(50, 50, 725, 450)));

    // panel to show xml data
    xmlPane = new TextHistoryPane(false);
    xmlWindow = new IndependentDialog(null, false, "XML data", xmlPane);
    xmlWindow.setBounds((Rectangle) store.getBean(XML_WINDOW_SIZE, new Rectangle(50, 50, 725, 450)));

    //catIndexer = new thredds.catalog.search.ui.CatalogIndexer((PreferencesExt) store.node("catIndexer"), topLevel.getJFrame());
    // tabbedPane.addTab("Indexer", catIndexer);

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

  private ThreddsDatasetChooser makeDatasetChooser() {

    datasetChooser = new ThreddsDatasetChooser((PreferencesExt) store.node("ThreddsDatasetChooser"), tabbedPane);

    //if (Debug.isSet("System/filterDataset"))
    //  datasetChooser.setDatasetFilter(new DatasetFilter.ByServiceType(ServiceType.DODS));

    datasetChooser.addPropertyChangeListener(new java.beans.PropertyChangeListener() {

      public void propertyChange(java.beans.PropertyChangeEvent e) {
        if (e.getPropertyName().equals("InvAccess")) {
          firePropertyChangeEvent(e);
          return;
        }

        if (e.getPropertyName().equals("Dataset") || e.getPropertyName().equals("CoordSys") || e.getPropertyName().equals("File")) {
          // intercept XML, ASCII return types
          Dataset ds = (Dataset) e.getNewValue();
          Access access = ds.getAccess(ServiceType.HTTPServer);
          if (access != null) {
            DataFormatType format = access.getDataFormatType();
            if (format == DataFormatType.PLAIN || format == DataFormatType.XML) {
              String urlString = access.getWrappedUrlName();
              //System.out.println("got station XML data access = "+urlString);
              IO.readURLcontents(urlString);
              xmlPane.setText(IO.readURLcontents(urlString));
              xmlPane.gotoTop();
              xmlWindow.setVisible(true);
              return;
            }
          }
          firePropertyChangeEvent(e);
        }
      }
    });

    // add a show source button to catalog chooser
    JButton catSource = new JButton("Source");
    catSource.addActionListener(e -> {
        CatalogChooser cc = datasetChooser.getCatalogChooser();
        String catURL = cc.getCurrentURL();
        //InvCatalogImpl cat = (InvCatalogImpl) datasetChooser.getCatalogChooser().getCurrentCatalog();
        //String catURL = cat.getUriString();
        if (debugSelection) System.out.println("Catalog Source: url = " + catURL);
        sourcePane.setURL(catURL);
        sourcePane.gotoTop();
        sourceWindow.show();
    });
    datasetChooser.getCatalogChooser().addButton(catSource);

    return datasetChooser;
  }

  // deferred creation of components to minimize startup

  /* private void makeComponent(JTabbedPane parent, String title) {
    if (parent == null) parent = tabbedPane;

    // find the correct index
    int n = parent.getTabCount();
    int idx;
    for (idx = 0; idx < n; idx++) {
      String cTitle = parent.getTitleAt(idx);
      if (cTitle.equals(title)) break;
    }
    if (idx >= n) {
      if (debugTab) System.out.println("Cant find " + title + " in " + parent);
      return;
    }

    Component c;
    if (title.equals("Catalog Crawler")) {
      catCrawler = new DLCrawler((PreferencesExt) store.node("catCrawler"), parent);
      c = catCrawler;

    } else if (title.equals("Catalog Enhancer")) {
      catEditor = new CatalogEnhancer((PreferencesExt) store.node("catEditor"), parent);
      c = catEditor;

    } else if (title.equals("Catalog Copier")) {
      catCopier = new CatalogCopier((PreferencesExt) store.node("catCopier"), parent);
      c = catCopier;

    } else if (title.equals("TDS Configure")) {
      serverConfigure = new TDServerConfigurator((PreferencesExt) store.node("serverConfigure"), parent);
      c = serverConfigure;

    } else {
      System.out.println("tabbedPane unknown component " + title);
      return;
    }

    parent.setComponentAt(idx, c);
    if (debugTab) System.out.println("tabbedPane changed " + title + " added ");
  }  */

  /**
   * save all data in the PersistentStore
   */
  public void storePersistentData() {
    store.putBeanObject(VIEWER_SIZE, getSize());
    store.putBeanObject(SOURCE_WINDOW_SIZE, (Rectangle) sourceWindow.getBounds());

    if (fileChooser != null)  fileChooser.save();
    if (datasetChooser != null) datasetChooser.save();
    if (sourcePane != null)  sourcePane.save();
    /* if (catEditor != null) catEditor.save();
    if (catCrawler != null) catCrawler.save();
    if (serverConfigure != null) serverConfigure.save();
    if (catCopier != null) catCopier.save();   */
  }

  /**
   * Fires a PropertyChangeEvent:
   * <ul>
   * <li>  propertyName = "Dataset" or "File", getNewValue() = InvDataset chosen.
   * <li>  propertyName = "Datasets", getNewValue() = InvDataset[] chosen. This can only happen if
   * you have set doResolve = true, and the resolved dataset is a list of datasets.
   * </ul>
   */
  private void firePropertyChangeEvent(PropertyChangeEvent event) {
    firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue());
  }

  public void setDataset(String location) {
    datasetChooser.getCatalogChooser().setCatalog(location);
    tabbedPane.setSelectedComponent(datasetChooser.getCatalogChooser());
  }

  /* private boolean chooseDataset(String url) {
   InvDataset invDs = new InvDatasetImpl( fname, ServerType.NETCDF);
   return chooseDataset( invDs);
 }

 void setDataset(InvDataset ds) {
   if (ds == null) return;

   InvAccess access = ds.getAccess( ServiceType.HTTPServer);
   if (access != null) {
     if ((access.getDataFormatType() == DataFormatType.PLAIN) ||
       (access.getDataFormatType() == DataFormatType.XML)) {
       String urlString = access.getStandardUrlName();
       System.out.println("got station XML data access = "+urlString);
       thredds.util.IO.readURLcontents(urlString);
       xmlPane.setText( thredds.util.IO.readURLcontents(urlString));
       xmlPane.gotoTop();
       xmlWindow.show();
       return;
     }
   }

   if (ds.getDataType() == DataType.IMAGE) {
     //setImageAccess( ds);
     //setTab(TAB_IMAGE);
     return;
   }

   else { // !! if (ds.getDataType() == DataType.GRID) {
     // LOOK what to do with dataset ??
     // gridViewer.setDataset( ds);

     //setTab(TAB_GRID);
     return;
   }

   /* JOptionPane.showMessageDialog(UI.this, ds.getName()+": unknown data type= "+ds.getDataType(),
       "Error opening dataset", JOptionPane.ERROR_MESSAGE);

   return;
 } */

  /* private boolean setImageAccess(InvDataset ds) {
    if (ds == null) return false;

    InvAccess access = ds.getAccess( ServiceType.ADDE);
    if (access == null) return false;

    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

    if (!(imageViewer.setImageFromUrl( access.getStandardUrlName()))) {
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      return false;
    }

    /* if (!imageViewer.isSelected()) {
      gridViewer.setSelected(false);
      imageViewer.setSelected(true);
      // stnViewer.setSelected(false);
      //viewerPanel.removeAll();
      //viewerPanel.add(imageViewer, BorderLayout.CENTER);
      revalidate();
    }

    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    return true;

/*
    } else if (ds.getDataType() == DataType.STATION) {

      if (!(stnViewer.setStations( ds))) {
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        return false;
      }

      if (!stnViewer.isSelected()) {
        gridViewer.setSelected(false);
        imageViewer.setSelected(false);
        stnViewer.setSelected(true);
        viewerPanel.removeAll();
        viewerPanel.add(stnViewer, BorderLayout.CENTER);
        revalidate();
      }

    }
    return true;

  } */

  /* private void addToDatasetList( String name) {
    int idx;
    if (0 <= (idx = recentDatasetList.indexOf(name))) // move to the top
      recentDatasetList.remove(idx);
    recentDatasetList.add(0, name);
  } */

  // actions that are system-wide

  private void makeActionsSystem() {

    /* aboutAction = new AbstractAction() {
      public void actionPerformed(ActionEvent evt) {
        new AboutWindow();
      }
    };
    BAMutil.setActionProperties( aboutAction, null, "About", false, 'A', 0); */

    /* printAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        PrinterJob printJob = PrinterJob.getPrinterJob();
        PageFormat pf = printJob.defaultPage();

        // do we need to rotate ??
        if (panz.wantRotate( pf.getImageableWidth(), pf.getImageableHeight()))
          pf.setOrientation( PageFormat.LANDSCAPE);
        else
          pf.setOrientation(PageFormat.PORTRAIT);

        printJob.setPrintable(controller.getPrintable(), pf);
        if (printJob.printDialog()) {
          try {
            if (Debug.isSet("print.job")) System.out.println("call printJob.print");
            printJob.print();
            if (Debug.isSet("print.job")) System.out.println(" printJob done");
          } catch (Exception PrintException) {
            PrintException.printStackTrace();
          }
        }
      }
    };
    BAMutil.setActionProperties( printAction, "Print", "Print...", false, 'P', KeyEvent.VK_P);

    sysConfigAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        if (sysConfigDialog == null)
          makeSysConfigWindow();
        sysConfigDialog.show();
      }
    };
    BAMutil.setActionProperties( sysConfigAction, "Preferences", "Configure...", false, 'C', -1);

    clearDebugFlagsAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { Debug.clear(); }
    };
    BAMutil.setActionProperties( clearDebugFlagsAction, null, "Clear DebugFlags", false, 'D', -1);

    clearRecentAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        recentDatasetList = new ArrayList();
      }
    };
    BAMutil.setActionProperties( clearRecentAction, null, "Clear Recent Datasets", false, 'R', -1);
    */

    AbstractAction clearDebugFlagsAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { /* Debug.clear(); */ }
    };
    BAMutil.setActionProperties(clearDebugFlagsAction, null, "Clear Debug Flags", false, 'D', -1);

    /* AbstractAction setDebugFlagsAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        // LOOK set netcdf debug flags

        InvCatalogFactory.debugURL = Debug.isSet("InvCatalogFactory/debugURL");
        InvCatalogFactory.debugOpen = Debug.isSet("InvCatalogFactory/debugOpen");
        InvCatalogFactory.debugVersion = Debug.isSet("InvCatalogFactory/debugVersion");
        InvCatalogFactory.showParsedXML = Debug.isSet("InvCatalogFactory/showParsedXML");
        InvCatalogFactory.showStackTrace = Debug.isSet("InvCatalogFactory/showStackTrace");
        InvCatalogFactory.debugXML = Debug.isSet("InvCatalogFactory/debugXML");
        InvCatalogFactory.debugDBurl = Debug.isSet("InvCatalogFactory/debugDBurl");
        InvCatalogFactory.debugXMLopen = Debug.isSet("InvCatalogFactory/debugXMLopen");
        InvCatalogFactory.showCatalogXML = Debug.isSet("InvCatalogFactory/showCatalogXML");
      }
    };
    BAMutil.setActionProperties(setDebugFlagsAction, null, "Set Debug Flags", false, 'S', -1);  */

    /* exitAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        topLevel.close();
      }
    };
    BAMutil.setActionProperties( exitAction, "Exit", "Exit", false, 'X', -1); */

  }

  // actions that control the dataset

  private void makeActionsDataset() {

    /* choose local dataset
chooseLocalDatasetAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if ( null == fileChooser) {
 String dirName = store.get(FILECHOOSER_DEFAULTDIR, null);
 fileChooser = new FileManager(parent, dirName, new FileManager.NetcdfExtFilter());
}
java.io.File file = fileChooser.chooseFile();
if (file == null) return;

InvDataset invDs;
try {
 invDs = new InvDatasetImpl( file.toURI().toString(), DataType.GRID, ServiceType.NETCDF);
} catch (Exception ue) {
 javax.swing.JOptionPane.showMessageDialog(UI.this, "Invalid filename = <"+file+">\n"+ue.getMessage());
 ue.printStackTrace();
 return;
}
setDataset( invDs);
}
};
BAMutil.setActionProperties( chooseLocalDatasetAction, "Choose", "open Local dataset...", false, 'L', -1); */

    /*choose THREDDS dataset
    chooseThreddsDatasetAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        datasetChooserDialog.show();
      }
    };
    BAMutil.setActionProperties( chooseThreddsDatasetAction, "Choose", "open THREDDS dataset...", false, 'R', -1);
    */
    /* enter dataset URL
    datasetURLAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        if (datasetURLDialog == null) {
          datasetURLpp = new faqo.ui.PreferencePanel( "Enter Dataset URL", new faqo.ui.PreferenceData(store), null);
          datasetURLpp.addTextField( DATASET_URL, "dataset URL", "                                                  ");
          datasetURLpp.finishSetup();
          datasetURLpp.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              String url = (String) datasetURLpp.getValue(DATASET_URL);
              System.out.println("dataset url = "+url);
              if (chooseDataset( url)) {
                datasetURLDialog.setVisible(false);
              }
            }
          });
          datasetURLDialog = new IndependentWindow( "Enter Dataset URL", datasetURLpp);
        }
        datasetURLDialog.show();
      }
    };
    BAMutil.setActionProperties( datasetURLAction, null, "enter dataset URL...", false, 'U', -1);

    /* saveDatasetAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        String fname = controller.getDatasetName();
        if (fname != null) {
          savedDatasetList.add( fname);
          BAMutil.addActionToMenu( savedDatasetMenu, new DatasetAction( fname), 0);
        }
      }
    };
    BAMutil.setActionProperties( saveDatasetAction, null, "save dataset", false, 'S', 0);
    */

  }

  /* private void setTab( String tabName) {
    if (tabName.equals(TAB_CHOOSER))
      tabbedPane.setSelectedIndex(0);
    else if (tabName.equals(TAB_GRID))
      tabbedPane.setSelectedComponent(gridViewer);
    else if (tabName.equals(TAB_IMAGE))
      tabbedPane.setSelectedComponent(imageViewer);
  } */

  /* private void makeMenu() {
    JMenuBar mb = new JMenuBar();
    JRootPane rootPane = topLevel.getRootPaneContainer().getRootPane();
    rootPane.setJMenuBar(mb);

    /// System menu
    JMenu sysMenu = new JMenu("System");
    sysMenu.setMnemonic('S');
    mb.add(sysMenu);
    //BAMutil.addActionToMenu( sysMenu, printAction);

    JMenu plafMenu = new JMenu("Look and Feel");
    plafMenu.setMnemonic('L');
    sysMenu.add(plafMenu);
    PLAF plaf = new PLAF(rootPane);
    plaf.addToMenu( plafMenu);

    if (Debug.isSet("util/configure")) BAMutil.addActionToMenu( sysMenu, sysConfigAction);

    debugMenu = (JMenu) sysMenu.add(new JMenu("Debug"));
    debugMenu.addMenuListener( new MenuListener() {
      public void menuSelected(MenuEvent e) { ucar.util.prefs.ui.Debug.constructMenu(debugMenu); }
      public void menuDeselected(MenuEvent e) {}
      public void menuCanceled(MenuEvent e) {}
    });

    BAMutil.addActionToMenu( sysMenu, setDebugFlagsAction);
    //BAMutil.addActionToMenu( sysMenu, clearDebugFlagsAction);
    /* if (null != System.getProperty("admin")) {
      JMenu adminMenu = new JMenu("Admin");
      plafMenu.setMnemonic('A');
      sysMenu.add(adminMenu);
      BAMutil.addActionToMenu( adminMenu, clearRecentAction);
      BAMutil.addActionToMenu( adminMenu, clearDebugFlagsAction);
    }

    BAMutil.addActionToMenu( sysMenu, exitAction);
    if (topLevel.isApplet())
      exitAction.setEnabled( false);

    /* Data menu
    dataMenu = new JMenu("Dataset");
    dataMenu.setMnemonic('D');
    mb.add(dataMenu);
    //BAMutil.addActionToMenu( dataMenu, chooseLocalDatasetAction);
    //BAMutil.addActionToMenu( dataMenu, chooseThreddsDatasetAction);
    //BAMutil.addActionToMenu( dataMenu, datasetURLAction);

      /* permanent datasets
      savedDatasetMenu = new JMenu("saved");
      //dataMenu.add( savedDatasetMenu);
      savedDatasetList = (ArrayList) store.getBean( "savedDatasetList", null);
      if (null != savedDatasetList) {
        Iterator iter = savedDatasetList.iterator();
        while (iter.hasNext())
          BAMutil.addActionToMenu( savedDatasetMenu, new DatasetAction( (String) iter.next()));
      } else
        savedDatasetList = new ArrayList(); */

  // Info
  /* JMenu infoMenu = new JMenu("Info");
   infoMenu.setMnemonic('I');
   mb.add(infoMenu);

   /// Configure
   configMenu = new JMenu("Configure");
   configMenu.setMnemonic('C');
   mb.add(configMenu);

   //// tools menu
   JMenu toolMenu = new JMenu("Tools");
   toolMenu.setMnemonic( 'T');
   mb.add(toolMenu);

  // Help
   JMenu helpMenu = new JMenu("Help");
   helpMenu.setMnemonic('H');
   mb.add(helpMenu);
   // BAMutil.addActionToMenu( helpMenu, controller.helpAction);
   BAMutil.addActionToMenu( helpMenu, aboutAction);

   // other UI's add their actions to menu
   //gridViewer.addActionsToMenus(dataMenu, configMenu, toolMenu);
 } */
}


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

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.featurecollection.FeatureCollectionType;
import thredds.util.ThreddsConfigReader;
import ucar.nc2.grib.GribIndexCache;
import ucar.nc2.util.AliasTranslator;
import ucar.nc2.util.Counters;
import ucar.nc2.util.DiskCache2;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;

/**
 * GribCollection Building - make summary of the feature collections.
 * calls GCpass1 and writes the gbx files if needed.
 * Does not build ncx files, so no large memory is needed.
 * So can run with many threads.
 *
 * @author caron
 * @since 12/20/2015.
 */
public class GCsummary {
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GCsummary.class);
  private static final boolean debug = false;
  private static final boolean debugOpenFiles = false;

  private Path contentDir;
  private Path contentThreddsDir;
  private Path contentTdmDir;
  private Path threddsConfig;

  private java.util.concurrent.ExecutorService executor;
  private Resource catalog;
  private boolean showOnly = false; // if true, just show dirs and exit

  List<Resource> catalogRoots = new ArrayList<>();

  Map<String, GCsummaryTask> fcMap = new TreeMap<>();

  public void setContentDir(String contentDir) throws IOException {
    System.out.printf("contentDir=%s%n", contentDir);
    this.contentDir = Paths.get(contentDir);
    this.contentThreddsDir = Paths.get(contentDir, "thredds");
    this.threddsConfig = Paths.get(contentDir, "thredds", "threddsConfig.xml");
    this.contentTdmDir = Paths.get(contentDir, "tdm");
    this.catalog = new FileSystemResource(contentThreddsDir.toString() + "/catalog.xml");
  }

  public void setShowOnly(boolean showOnly) {
    this.showOnly = showOnly;
  }

  public void setNThreads(int n) {
    executor = Executors.newFixedThreadPool(n);
    log.info(" TDM nthreads= {}", n);
  }

  // spring beaned
  public void setExecutor(ExecutorService executor) {
    this.executor = executor;
  }

  public void setCatalog(String catalog) throws IOException {
    this.catalog = new FileSystemResource(contentThreddsDir.toString() + "/" + catalog);
    System.out.printf("use catalog=%s%n", this.catalog.getFile().getPath());

  }

  ////////////////////////////////////////////////////////////////////
  boolean init() {
    System.setProperty("tds.log.dir", contentTdmDir.toString());

    if (!Files.exists(threddsConfig)) {
      log.error("config file {} does not exist, set -Dtds.content.root.path=<dir>", threddsConfig);
      System.out.printf("threddsConfig does not exist=%s%n", threddsConfig);
      return false;
    }
    ThreddsConfigReader reader = new ThreddsConfigReader(threddsConfig.toString(), log);

    for (String location : reader.getRootList("catalogRoot")) {
      Resource r = new FileSystemResource(contentThreddsDir.toString() + "/" + location);
      catalogRoots.add(r);
    }

    // LOOK check TdsInit
     /* 4.3.15: grib index file placement, using DiskCache2  */
    String gribIndexDir = reader.get("GribIndex.dir", new File(contentThreddsDir.toString(), "cache/grib/").getPath());
    Boolean gribIndexAlwaysUse = reader.getBoolean("GribIndex.alwaysUse", false);
    Boolean gribIndexNeverUse = reader.getBoolean("GribIndex.neverUse", false);
    String gribIndexPolicy = reader.get("GribIndex.policy", null);
    DiskCache2 gribCache = gribIndexNeverUse ? DiskCache2.getNoop() : new DiskCache2(gribIndexDir, false, -1, -1);
    gribCache.setPolicy(gribIndexPolicy);
    gribCache.setAlwaysUseCache(gribIndexAlwaysUse);
    gribCache.setNeverUseCache(gribIndexNeverUse);
    GribIndexCache.setDiskCache2(gribCache);
    log.info("TDM set " + gribCache);

    return true;
  }

  void start() throws IOException {
    System.out.printf("GCsummary startup at %s%n", new Date());

    List<FeatureCollectionConfig> fcList = new ArrayList<>();
    CatalogConfigReader reader = new CatalogConfigReader(contentThreddsDir, catalog);
    fcList.addAll(reader.getFcList());

    // do the catalogRoots
    for (Resource catr : catalogRoots) {
      CatalogConfigReader r = new CatalogConfigReader(contentThreddsDir, catr);
      fcList.addAll(r.getFcList());
    }

    if (showOnly) {
      List<String> result = new ArrayList<>();
      for (FeatureCollectionConfig config : fcList) {
        result.add(config.collectionName);
      }
      Collections.sort(result);

      System.out.printf("%nFeature Collection names:%n");
      for (String name : result)
        System.out.printf(" %s%n", name);

      executor.shutdown();
      return;
    }

    for (FeatureCollectionConfig config : fcList) {
      if (config.type != FeatureCollectionType.GRIB1 && config.type != FeatureCollectionType.GRIB2) continue;
      System.out.printf(" FeatureCollection scheduled %s == %s %n", config.collectionName, config.spec);

      // schedule execution of the GCsummary task
      GCsummaryTask task = new GCsummaryTask(config);
      fcMap.put(config.getCollectionName(), task);
      executor.execute(task);
    }
  }

  public void finish() {
    // no more tasks will be submitted, wait until all are done
    executor.shutdown();

    try {
      executor.awaitTermination(1, TimeUnit.DAYS);
    } catch (InterruptedException e) {
      System.out.printf("Interupted%n");
    }

    System.out.printf("Finished All tasks%n");
    GCpass1.Accum all = new GCpass1.Accum();
    long tookAll = 0;

    Formatter f = new Formatter();
    f.format("%40s,  type, ptype,    took,  nfiles, nrecords,  idx(MB), data(GB), data/idx, bytes/rec, variables, runtimes, gds %n", "Collection");
    for (GCsummaryTask task : fcMap.values()) {
      f.format("%40s, %5s, %5s, %8d,", task.config.collectionName, task.config.type, task.config.ptype, task.took);
      GCpass1.Accum acc = task.pass1.accumAll;
      f.format("%8d, %8d, %8.3f, %8.3f, ", acc.nfiles, acc.nrecords, acc.indexSize, acc.fileSize / 1000);
      f.format("%8.3f, %8.0f,", acc.fileSize / acc.indexSize, acc.indexSize * 1000 * 1000 / acc.nrecords);

      Counters counters = task.pass1.countersAll;
      f.format("%8d,", counters.get("variable").getUnique());
      f.format("%8d,", counters.get("referenceDate").getUnique());
      f.format("%8d,", counters.get("gds").getUnique());
      f.format("%n");

      all.add(acc);
      tookAll += task.took;
    }

    f.format("%n");
    f.format("%40s, %5s, %5s, %8d,", "total", "", "", tookAll);
    f.format("%8d, %8d, %8.3f, %8.3f, ", all.nfiles, all.nrecords, all.indexSize, all.fileSize / 1000);
    f.format("%8.3f, %8.0f", all.fileSize / all.indexSize, all.indexSize * 1000 * 1000 / all.nrecords);
    f.format("%n");

    System.out.printf("%s%n", f);

    try (FileOutputStream fileOut = new FileOutputStream("GCsummary.csv")) {
      fileOut.write(f.toString().getBytes());
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

  private class GCsummaryTask implements Runnable {
    String name;
    FeatureCollectionConfig config;
    GCpass1 pass1;
    long took;

    private GCsummaryTask(FeatureCollectionConfig config) {
      this.name = config.collectionName;
      this.config = config;
    }

    @Override
    public void run() {
      try {
        long start = System.currentTimeMillis();

        String fileOutName = config.collectionName + ".GCsummary.txt";
        try (FileOutputStream fileOut = new FileOutputStream(fileOutName)) {
          System.out.printf("GCsummaryTask %s started write to %s%n", name, fileOutName);
          Formatter f = new Formatter(fileOut);
          this.pass1 = new GCpass1(config, f);
          pass1.scanAndReport();
          f.flush();
          fileOut.flush();
        }
        this.took = (System.currentTimeMillis() - start) / 1000; // secs
        System.out.printf(" GCsummaryTask %s finished in %d secs%n", name, took);

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

  }

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

  /*
        // /opt/jdk/bin/java -d64 -Xmx3g -jar -Dtds.content.root.path=/opt/tds-dev/content tdm-4.5.jar -cred tdm:trigger -tds "http://thredds-dev.unidata.ucar.edu/"
           System.out.printf("usage: <Java> <Java_OPTS> -Dtds.content.root.path=<contentDir> [-catalog <cat>] [-tds <tdsServer>] [-cred <user:passwd>] [-showOnly] [-forceOnStartup]%n");
            System.out.printf("example: /opt/jdk/bin/java -Xmx3g -Dtds.content.root.path=/my/content -jar tdm-4.5.jar -tds http://thredds-dev.unidata.ucar.edu/%n");
 */

  private static class CommandLine {
    @Parameter(names = {"-catalog"}, description = "specific catalog", required = false)
    public String catalog;

    @Parameter(names = {"-nthreads"}, description = "number of threads", required = false)
    public int nthreads;

    @Parameter(names = {"-showOnly"}, description = "show collections and exit", required = false)
    public boolean showOnly;

    @Parameter(names = {"-h", "--help"}, description = "Display this help and exit", help = true)
    public boolean help = false;

    private final JCommander jc;

    public CommandLine(String progName, String[] args) throws ParameterException {
      this.jc = new JCommander(this, args);  // Parses args and uses them to initialize *this*.
      jc.setProgramName(progName);           // Displayed in the usage information.
    }

    public void printUsage() {
      jc.usage();
    }

  }

  public static void main(String args[]) throws IOException, InterruptedException {
    try (FileSystemXmlApplicationContext springContext = new FileSystemXmlApplicationContext("classpath:resources/application-config.xml")) {
      GCsummary app = (GCsummary) springContext.getBean("GCsummary");

      Map<String, String> aliases = (Map<String, String>) springContext.getBean("dataRootLocationAliasExpanders");
      for (Map.Entry<String, String> entry : aliases.entrySet())
        AliasTranslator.addAlias(entry.getKey(), entry.getValue());

      String progName = GCsummary.class.getName();

      try {
        CommandLine cmdLine = new CommandLine(progName, args);
        if (cmdLine.help) {
          cmdLine.printUsage();
          return;
        }

        String contentDir = System.getProperty("tds.content.root.path");
        if (contentDir == null) contentDir = "../content";
        app.setContentDir(contentDir);

        if (cmdLine.catalog != null)
          app.setCatalog(cmdLine.catalog);

        if (cmdLine.nthreads != 0)
          app.setNThreads(cmdLine.nthreads);

        if (cmdLine.showOnly)
          app.setShowOnly(true);

        if (app.init()) {
          app.start();
          app.finish();
          System.exit(0);
        } else {
          System.out.printf("%nEXIT DUE TO ERRORS");
        }

      } catch (ParameterException e) {
        System.err.println(e.getMessage());
        System.err.printf("Try \"%s --help\" for more information.%n", progName);
      }
    }
  }
}