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

import ucar.nc2.iosp.bufr.tables.TableD;

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

/**
 * Convert a list of data descriptors to a tree of DataDescriptor objects.
 * Expand Table D, process table C operators.
 *
 * @author caron
 * @since Jul 14, 2008
 */
public class DataDescriptorTreeConstructor {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataDescriptorTreeConstructor.class);

  //////////////////////////////////////////////////////////////////////////////////
  private DataDescriptor root;

  public DataDescriptor factory(BufrTableLookup lookup, BufrDataDescriptionSection dds) {
    root = new DataDescriptor();

    // convert ids to DataDescriptor
    List<DataDescriptor> keys = decode(dds.getDataDescriptors(), lookup);

    // deal with f3-60-4
    keys = preflatten(keys);

    // try to find useful struct names
    grabCompoundNames(keys);

    // make replicated keys into subKeys, constituting a tree
    List<DataDescriptor> tree = replicate(keys);

    // flatten the compounds
    root.subKeys = new ArrayList<DataDescriptor>();
    flatten(root.subKeys, tree);

    // process the operators
    operate( root.subKeys);

    // count the size
    root.total_nbits = root.countBits();

    return root;
  }

  // convert ids to DataDescriptors, expand table D
  private List<DataDescriptor> decode(List<Short> keyDesc, BufrTableLookup lookup) {
    if (keyDesc == null) return null;

    List<DataDescriptor> keys = new ArrayList<DataDescriptor>();
    for (short id : keyDesc) {
      DataDescriptor dd = new DataDescriptor(id, lookup);
      keys.add( dd);
      if (dd.f == 3) {
        TableD.Descriptor tdd = lookup.getDescriptorTableD(dd.fxy);
        if (tdd == null || tdd.getSequence() == null) {
          dd.bad = true;
        } else {
          dd.name = tdd.getName();
          dd.subKeys = decode(tdd.getSequence(), lookup);
        }
      }
    }
    return keys;
  }

  // look for replication, move replicated items into subtree
  private List<DataDescriptor> replicate(List<DataDescriptor> keys) {
    List<DataDescriptor> tree = new ArrayList<DataDescriptor>();
    Iterator<DataDescriptor> dkIter = keys.iterator();
    while (dkIter.hasNext()) {
      DataDescriptor dk = dkIter.next();
      if (dk.f == 1) {
        dk.subKeys = new ArrayList<DataDescriptor>();
        dk.replication = dk.y; // replication count

        if (dk.replication == 0) { // delayed replication
          root.isVarLength = true; // variable sized data == deferred replication == sequence data

          // the next one is the replication count size : does not count in field count (x)
          DataDescriptor replication = dkIter.next();
          if (replication.y == 0)
            dk.replicationCountSize = 1; // ??
          else if (replication.y == 1)
            dk.replicationCountSize = 8;
          else if (replication.y == 2)
            dk.replicationCountSize = 16;
          else if (replication.y == 11)
            dk.repetitionCountSize = 8;
          else if (replication.y == 12)
            dk.repetitionCountSize = 16;
          else
            log.error("Unknown replication type= "+replication);
        }

        // transfer to the subKey list
        for (int j = 0; j < dk.x && dkIter.hasNext(); j++) {
          dk.subKeys.add( dkIter.next());
        }

        // recurse
        dk.subKeys = replicate(dk.subKeys);

      } else if ((dk.f == 3) && (dk.subKeys != null)) {
        dk.subKeys = replicate( dk.subKeys); // do at all levels
      }

      tree.add(dk);
    }

    return tree;
  }

  /* Use case:
    3-62-1  : HEADR
      0-4-194 : FORECAST TIME
      0-1-205 : STATION NUMBER -- 6 DIGITS
      0-1-198 : REPORT IDENTIFIER
      0-5-2   : Latitude (coarse accuracy)
      0-6-2   : Longitude (coarse accuracy)
      0-10-194: GRID-POINT ELEVATION
      0-2-196 : CLASS OF PROFILE OUTPUT
    3-60-2  :
      1-01-000: replication
      0-31-1  : Delayed descriptor replication factor
    3-62-2  : PROFILE
      0-10-4  : Pressure
      0-12-1  : Temperature/dry-bulb temperature
      0-11-3  : u-component

     where the 3-62-2 should be replicated.
     This is from NCEP bufrtab.ETACLS1. Not sure if others use this idiom.

     Use case 2:
     not just top level
      3-61-37 : TMPSQ1   SYNOPTIC REPORT TEMPERATURE DATA
        0-33-193: QMAT
        0-12-101: TMDB
        0-33-194: QMDD
        0-12-103: TMDP
        0-2-38  : MSST
        0-33-218: QMST
        0-22-43 : SST1
        3-60-4  : DRP1BIT
          1-01-000: replication
          0-31-0  : DRF1BIT
        3-61-38 : TMPSQ2   SYNOPTIC REPORT WET BULB TEMPERATURE DATA
          0-2-39  : MWBT
          0-12-102: TMWB
          0-13-3  : REHU
        3-60-4  : DRP1BIT
          1-01-000: replication
          0-31-0  : DRF1BIT
        3-61-39 : TMPSQ3   SYNOPTIC REPORT MAXIMUM AND MINIMUM TEMPERATURE DATA
          0-4-31  : DTH
          0-12-111: MXTM
          0-4-31  : DTH
          0-12-112: MITM

          I think that a 3-60-4 should just be flattened:
     3-61-37 : TMPSQ1   SYNOPTIC REPORT TEMPERATURE DATA
        0-33-193: QMAT
        0-12-101: TMDB
        0-33-194: QMDD
        0-12-103: TMDP
        0-2-38  : MSST
        0-33-218: QMST
        0-22-43 : SST1
        1-01-000: replication
        0-31-0  : DRF1BIT
        3-61-38 : TMPSQ2   SYNOPTIC REPORT WET BULB TEMPERATURE DATA
          0-2-39  : MWBT
          0-12-102: TMWB
          0-13-3  : REHU
        1-01-000: replication
        0-31-0  : DRF1BIT
        3-61-39 : TMPSQ3   SYNOPTIC REPORT MAXIMUM AND MINIMUM TEMPERATURE DATA
          0-4-31  : DTH
          0-12-111: MXTM
          0-4-31  : DTH
          0-12-112: MITM
   */

  // LOOK this is NCEP specific !!
  static boolean isNcepDRP( DataDescriptor key) {
    return key.f == 3 && key.x == 60;
  }

  private List<DataDescriptor> preflatten(List<DataDescriptor> tree) {
    if (tree == null) return null;

    // do we need to flatten, ie have f3604 ??
    boolean flatten = false;
    for (DataDescriptor key : tree) {
      if (isNcepDRP(key)) flatten = true;
    }

    if (flatten) {
      List<DataDescriptor> result = new ArrayList<DataDescriptor>(tree.size());
      for (DataDescriptor key : tree) {
        if (isNcepDRP(key)) {
          result.addAll(key.subKeys); // remove f3604
        } else {
          result.add(key); // leave others
        }
      }
      tree = result;
    }

    // recurse
    for (DataDescriptor key : tree) {
      key.subKeys = preflatten(key.subKeys);
    }

    return tree;
  }

  /* OLD preflatten replications inside a top level compound (!)
  private List<DataDescriptor> preflattenOld(List<DataDescriptor> tree) {
    List<DataDescriptor> result = new ArrayList<DataDescriptor>(tree.size());
    for (DataDescriptor key : tree) {
      boolean preflatten = false;

      if ((key.f == 3) && (key.subKeys != null)) {
        List<DataDescriptor> subkeys = key.subKeys;
        for (int i=0; i<subkeys.size();i++) {
          DataDescriptor subkey = subkeys.get(i);
          if (subkey.f == 1)  {
            int need = subkey.x;
            int have = subkeys.size() - i - 1;
            if (subkey.y == 0) have--;
            if (need > have) {
              preflatten = true;
              // ScannerSystem.out.printf("preflatten replication %d > %d%n", need, have);
            }
          }
        }
      }

      if (preflatten)
        result.addAll(key.subKeys);
      else
       result.add(key);

    }
    return result;
  }  */

  /* try to grab names of compounds (structs)
     if f=1 is followed by f=3, eg:
     0-40-20 : GQisFlagQualDetailed - Quality flag for the system
      1-01-010: replication
      3-40-2  : (IASI Level 1c band description)
        0-25-140: Start channel
        0-25-141: End channel
        0-25-142: Channel scale factor
      1-01-087: replication
      3-40-3  : (IASI Level 1c 100 channels)
        1-04-100: replication
        2-01-136: Operator= change data width
        0-5-42  : Channel number
        2-01-000: Operator= change data width
        0-14-46 : Scaled IASI radiance
      0-2-19  : Satellite instruments
      0-25-51 : AVHRR channel combination
      1-01-007: replication
      3-40-4  : (IASI Level 1c AVHRR single scene)
        0-5-60  : Y angular position from centre of gravity
        0-5-61  : Z angular position from centre of gravity
        0-25-85 : Fraction of clear pixels in HIRS FOV
      ...

      sequence:
        3-60-4  : DRP1BIT
          1-01-000: replication
          0-31-0  : DRF1BIT
        3-61-38 : TMPSQ2   SYNOPTIC REPORT WET BULB TEMPERATURE DATA
          0-2-39  : MWBT
          0-12-102: TMWB
          0-13-3  : REHU

     which has been preflattened into:

       1-01-000: replication
       0-31-0  : DRF1BIT
       3-61-38 : TMPSQ2   SYNOPTIC REPORT WET BULB TEMPERATURE DATA
          0-2-39  : MWBT
          0-12-102: TMWB
          0-13-3  : REHU

*/
  private void grabCompoundNames(List<DataDescriptor> tree) {

    for (int i=0; i<tree.size(); i++) {
      DataDescriptor key = tree.get(i);
      if (key.bad) continue;

      if ((key.f == 3) && (key.subKeys != null)) {
        grabCompoundNames(key.subKeys);

      } else if (key.f == 1 && key.x == 1 && i < tree.size()-1) {  // replicator with 1 element
        DataDescriptor nextKey = tree.get(i+1);
        if (nextKey.f == 3) {  // the one element is a compound
          if (nextKey.name != null && !nextKey.name.isEmpty())
            key.name = nextKey.name;

        } else if (key.y == 0 && i < tree.size()-2) {  // seq has an extra key before the 3
          DataDescriptor nnKey = tree.get(i+2);
          if (nnKey.f == 3 )
            if (nnKey.name != null && !nnKey.name.isEmpty())
              key.name = nnKey.name;
        }
      }
    }
  }

  // flatten the compounds (type 3); but dont remove bad ones
  private void flatten(List<DataDescriptor> result, List<DataDescriptor> tree) {

    for (DataDescriptor key : tree) {
      if (key.bad) {
        root.isBad = true;
        result.add(key); // add it anyway so we can see it in debug
        continue;
      }

      if ((key.f == 3) && (key.subKeys != null)) {
        flatten(result, key.subKeys);

      } else if (key.f == 1) { // flatten the subtrees
        List<DataDescriptor> subTree = new ArrayList<DataDescriptor>();
        flatten(subTree, key.subKeys);
        key.subKeys = subTree;
        result.add(key);

      } else {
        result.add(key);
      }
    }
  }

  private DataDescriptor changeWidth = null; // 02 01 Y
  private DataDescriptor changeScale = null; // 02 02 Y
  private DataDescriptor changeRefval = null; // 02 03 Y
  private DataDescriptor changeWtf = null; // 02 07 Y
  private DataPresentIndicator dpi = null; // assume theres only one in effect at a time

  private void operate(List<DataDescriptor> tree) {
    if (tree == null) return;
    boolean hasAssFields = false;
    // boolean hasDpiFields = false;
    DataDescriptor.AssociatedField assField = null; // 02 04 Y

    Iterator<DataDescriptor> iter = tree.iterator();
    while (iter.hasNext()) {
      DataDescriptor dd = iter.next();

      if (dd.f == 2) {
        if (dd.x == 1) {
          changeWidth = (dd.y == 0) ? null : dd;
          iter.remove();

        } else if (dd.x == 2) {
          changeScale = (dd.y == 0) ? null : dd;
          iter.remove();
          // throw new UnsupportedOperationException("2-2-Y (change scale)");

        } else if (dd.x == 3) {
          changeRefval = (dd.y == 255) ? null : dd;
          iter.remove();
          // throw new UnsupportedOperationException("2-3-Y (change reference values)");  // untested - no examples

        } else if (dd.x == 4) {
          assField = (dd.y == 0) ? null : new DataDescriptor.AssociatedField(dd.y);
          iter.remove();
          hasAssFields = true;

        } else if (dd.x == 5) { // char data - this allows arbitrary string to be inserted
          dd.type = 1; // String
          dd.bitWidth = dd.y * 8;
          dd.name = "Note";

        } else if (dd.x == 6) {
          // see L3-82 (3.1.6.5)
          // "Y bits of data are described by the immediately following descriptor". could they speak English?
          iter.remove();
          if ((dd.y != 0) && iter.hasNext()) {  // fnmoc using 2-6-0 as cancel (apparently)
            DataDescriptor next = iter.next();
            next.bitWidth = dd.y;
          }

        } else if (dd.x == 7) {
          changeWtf = (dd.y == 0) ? null : dd;
          iter.remove();

        } else if (dd.x == 36) {
          if (iter.hasNext()) {
            DataDescriptor dpi_dd = iter.next(); // this should be a replicated data present field
            dpi = new DataPresentIndicator(tree, dpi_dd);
            dd.dpi = dpi;
            dpi_dd.dpi = dpi;
          }

        } else if ((dd.x == 37) && (dd.y == 255)) { // cancel dpi
          dpi = null;

        } else if ((dd.x == 24) && (dd.y == 255)) {
           dd.dpi = dpi;
        }

      } else if (dd.subKeys != null) {
        operate(dd.subKeys);

      } else if (dd.f == 0) {

        if (dd.type != 3) {  // numeric or string or enum, not compound
          if (changeWidth != null)
            dd.bitWidth += changeWidth.y-128;
          if (changeScale != null)
            dd.scale += changeScale.y-128;
          if (changeRefval != null)
            dd.refVal += changeRefval.y-128;  // LOOK wrong

          if (changeWtf != null && dd.type == 0) {
            // see I.2 – BUFR Table C — 4
            // For Table B elements, which are not CCITT IA5 (character data), code tables, or flag tables:
            //  1. Add Y to the existing scale factor
            //  2. Multiply the existing reference value by 10 Y
            //  3. Calculate ((10 x Y) + 2) ÷  3, disregard any fractional remainder and add the result to the existing bit width.
            // HAHAHAHAHAHAHAHA
            int y = changeWtf.y;
            dd.scale += y;
            dd.refVal *= Math.pow(10, y);
            int wtf = ((10 * y) + 2) /  3;
            dd.bitWidth += wtf;
          }
        }

        if ((dd.f == 0) && (assField != null)) {
          assField.nfields++;
          dd.assField = assField;
          assField.dataFldName = dd.name;
        }
      }
    }

    if (hasAssFields) addAssFields(tree);
    // if (hasDpiFields) addDpiFields(tree);
  }

  private void addAssFields(List<DataDescriptor> tree) {
    if (tree == null) return;

    int index = 0;
    while (index < tree.size()) {
      DataDescriptor dd = tree.get(index);
      if (dd.assField != null) {
        DataDescriptor.AssociatedField assField = dd.assField;

        if ((dd.f == 0) && (dd.x == 31) && (dd.y == 21)) {  // the meaning field
          dd.name = assField.dataFldName+"_associated_field_significance";
          dd.assField = null;

        } else {
          DataDescriptor assDD = dd.makeAssociatedField(assField.nbits);
          tree.add(index, assDD);
          index++;
        }
      }

      index++;
    }
  }

  /* private void addDpiFields(List<DataDescriptor> tree) {
    if (tree == null) return;

    Iterator<DataDescriptor> iter = tree.iterator();
    while (iter.hasNext()) {
      DataDescriptor dd = iter.next();
      if (dd.dpi != null) { // make this into a compound type
        dd.name = "firstOrderStatistics";
        dd.type = 3;
        dd.replication = 1;
        dd.subKeys = new ArrayList<DataDescriptor>();
        while (iter.hasNext()) {
          DataDescriptor dd2 = iter.next();
          iter.remove();
          dd.subKeys.add(dd2);
          if (dd2 == dd.dpi.stop) {
            dd2.subKeys = new ArrayList<DataDescriptor>();

            dd2.subKeys.add(dd.dpi.statField);
            dd.dpi.statField.dpi = dd.dpi; // also need it here
            //dd.dpi = null;
           // DataDescriptor extra = dd.dpi.linear.get(184); // temp kludge
          //  for (int i=0; i< dd2.replication; i++)
           //   dd.subKeys.add(extra);
            break;
          }
        }
        System.out.printf("addDpiFields for %s %n", dd);
      }
    }
  }

  /* private DataPresentIndicator processDataPresentIndicator(List<DataDescriptor> tree, int hellRealmIndex) {
    DataPresentIndicator dpi = new DataPresentIndicator();
    Iterator<DataDescriptor> iter = tree.iterator();
    while (iter.hasNext()) {
      DataDescriptor dd = iter.next();
      if ((dd.f == 2) && (dd.x == hellRealmIndex) && (dd.y == 0)) {
        dpi.start = dd;

        while (iter.hasNext()) {
          dd = iter.next();
          if (dd.f == 1) {
            for (DataDescriptor dd2 : dd.getSubKeys()){
              if ((dd2.f == 2) && (dd2.x == hellRealmIndex) && (dd2.y == 255)) {
                dpi.stop = dd;
                dpi.statField = dd2;
              } else if ((dd2.f == 0) && (dd2.x == 31) && (dd2.y == 31)) {
                dpi.dataPresent = dd;
              }
            }
          }
        }
      }
    }

    dpi.linear = new ArrayList<DataDescriptor>();
    linearize(tree, dpi);
    // LOOK System.out.printf("DPI: data count = %d linear count = %d %n", dpi.dataPresent.replication, dpi.linear.size());

    return dpi;
  }  */

  static class DataPresentIndicator {
    DataDescriptor dataPresent; // replication of bit present field
    List<DataDescriptor> linear = new ArrayList<DataDescriptor>(); // linear list of dds

    DataPresentIndicator(List<DataDescriptor> tree, DataDescriptor dpi_dd) {
      this.dataPresent = dpi_dd;

      linear = new ArrayList<DataDescriptor>();
      linearize(tree);
    }

    int getNfields() { return dataPresent.replication; }

    private void linearize(List<DataDescriptor> tree) {
      for (DataDescriptor dd : tree) {
        if (dd.f == 0) {
          linear.add(dd);

        } else if (dd.f == 1) {
          for (int i = 0; i < dd.replication; i++) // whut about defered replication hahahahahah
            linearize(dd.getSubKeys());
        }
      }
    }
  }

}


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

import javax.annotation.Nullable;
import ucar.nc2.grib.GribNumbers;
import ucar.nc2.grib.GribStatType;
import ucar.nc2.grib.coord.VertCoordType;
import ucar.nc2.grib.grib1.Grib1ParamLevel;
import ucar.nc2.grib.grib1.Grib1ParamTime;
import ucar.nc2.grib.grib1.Grib1SectionProductDefinition;

import java.util.HashMap;
import java.util.Map;

/**
 * Local tables for JMA (center 34)
 *
 * @author caron
 * @see "http://rda.ucar.edu/datasets/ds628.0/docs/JRA-55.grib_20130319.xlsx"
 * @since 1/27/2015
 */
public class JmaTables extends Grib1Customizer {
  private static Map<Integer, VertCoordType> levelTypesMap;  // shared by all instances

  JmaTables(Grib1ParamTables tables) {
    super(34, tables);
  }

  /////////////////// local time types
  @Override
  public Grib1ParamTime getParamTime(Grib1SectionProductDefinition pds) {
    int p1 = pds.getTimeValue1();  // octet 19
    int p2 = pds.getTimeValue2();  // octet 20
    int timeRangeIndicator = pds.getTimeRangeIndicator(); // octet 21
    int n = pds.getNincluded();

    int start;
    int end;
    int forecastTime = 0;
    boolean isInterval;

    switch (timeRangeIndicator) {
      /*
      Monthly-diurnal (ds628.5/fcst_phy2m125_diurnal)
      Average over the days of the month.
      128: Average of N forecast products with a valid time ranging between reference time + P1 and reference time + P2;
        products have reference times at Intervals of 24 hours, beginning at the given reference time.
       */
      case 128:
        isInterval = true;
        start = p1;
        end = p2;
        break;

      /* 129: Temporal variance of N forecasts; each product has valid time ranging between reference time + P1 and reference time + P2;
         products have reference times at intervals of 24 hours, beginning at the given reference time;
         unit of measurement is square of that in Code Table 2 */  // LOOK

      /* 131: Temporal variance of N forecasts; valid time of the first product ranges between R + P1 and R + P2,
          where R is reference time given in octets 13 to 17, then subsequent products have valid time range at interval of P2 - P1;
          thus all N products cover continuous time span; products have reference times at intervals of P2 - P1, beginning at the given reference time;
          unit of measurement is square of that in Code Table 2 */ // LOOK

      case 129:
      case 131:
        isInterval = true;
        start = p1;
        end = p2;
        break;

      /* 130: Average of N forecast products; valid time of the first product ranges between R + P1 and R + P2, where R is reference time given in octets 13 to 17,
        then subsequent products have valid time range at interval of P2 - P1; thus all N products cover continuous time span; products have reference times at
        intervals of P2 - P1, beginning at the given reference time
       */
      case 130:
        isInterval = true;
        start = p1;
        end = (n > 0) ? p1 + n * (p2 - p1) : p2;  // prob n >= 1
        break;

       /* Temporal variance of N uninitialized analyses (P1 = 0) or instantaneous forecasts (P1 > 0);
          each product has valid time at the reference time + P1;
          products have reference times at intervals of P2, beginning at the given reference time;
          unit of measurement is square of that in Code Table 2 */ // LOOK
      case 132:
        forecastTime = p1;
        start = p1;
        end = (n > 0) ? p1 + (n - 1) * p2 : p1;  // LOOK ??
        isInterval = (n > 0);
        break;

      default:
        return super.getParamTime(pds);
    }

    return new Grib1ParamTime(this, timeRangeIndicator, isInterval, start, end, forecastTime);
  }

  @Override
  public String getTimeTypeName(int timeRangeIndicator) {
    switch (timeRangeIndicator) {
      case 128:
        return "Average over the days in the month";
      case 129:
        return "Temporal variance of N forecasts at 24 hour intervals";
      case 130:
        return "Forecast, 6-hour averaged, then one-month averaged";
      case 131:
        return "Temporal variance of N forecasts at intervals of P1 - P2";
      case 132:
        return "Temporal variance of N uninitialized analyses (P1 = 0) or instantaneous forecasts (P1 > 0)";

      default:
        return super.getTimeTypeName(timeRangeIndicator);
    }
  }

  @Override
  public GribStatType getStatType(int timeRangeIndicator) {
    switch (timeRangeIndicator) {
      case 128:
      case 130:
        return GribStatType.Average;

      case 129:
      case 131:
      case 132:
        return GribStatType.Variance;
      default:
        return super.getStatType(timeRangeIndicator);
    }
  }

  ///////////////////// levels
  @Override
  public Grib1ParamLevel getParamLevel(Grib1SectionProductDefinition pds) {
    int levelType = pds.getLevelType();
    int pds11 = pds.getLevelValue1();
    int pds12 = pds.getLevelValue2();
    int pds1112 = pds11 << 8 | pds12;

    switch (levelType) {
      case 211:
      case 212:
        return new Grib1ParamLevel(this, levelType, GribNumbers.MISSING, GribNumbers.MISSING);

      case 100:
        return new Grib1ParamLevel(this, levelType, pds1112, GribNumbers.MISSING);

      case 213:
        return new Grib1ParamLevel(this, levelType, pds1112, GribNumbers.MISSING);

      default:
        return new Grib1ParamLevel(this, pds);
    }
  }

  protected VertCoordType getLevelType(int code) {
    if (levelTypesMap == null) makeLevelTypesMap();
    VertCoordType levelType = levelTypesMap.get(code);
    if (levelType != null) return levelType;
    return super.getLevelType(code);
  }

  private static void makeLevelTypesMap() {
    levelTypesMap = new HashMap<>(10);
    // (int code, String desc, String abbrev, String units, String datum, boolean isPositiveUp, boolean isLayer)
    levelTypesMap.put(100, new VertCoordType(100, "Isobaric Surface", "isobaric_surface_low", "hPa", null, false, false));   // 3D
    levelTypesMap.put(211, new VertCoordType(211, "Entire soil", "entire_soil", "", null, false, false));
    levelTypesMap.put(212, new VertCoordType(212, "The bottom of land surface model", "bottom_of_model", "", null, false, false));
    levelTypesMap.put(213, new VertCoordType(213, "Underground layer number of land surface model", "underground_layer", "layer", null, false, false));   // 3D
  }

  //////////////////// gen process
  private static Map<Integer, String> genProcessMap;

  @Override
  @Nullable
  public String getGeneratingProcessName(int genProcess) {
    if (genProcessMap == null) makeGenProcessMap();
    return genProcessMap.get(genProcess);
  }

  private static void makeGenProcessMap() {
    genProcessMap = new HashMap<>(100);
    genProcessMap.put(0, "Undefined (not to specify generating process)");
    genProcessMap.put(1, "Global Spectral Model (GSM8803_T63L16)");
    genProcessMap.put(2, "Global Spectral Model (GSM8903_T106L21)");
    genProcessMap.put(3, "Global Spectral Model (GSM9603_T213L30)");
    genProcessMap.put(4, "Global Spectral Model (GSM0103_T213L40) ");
    genProcessMap.put(21, "One-week EPS (GSM0103_T106L40)");
    genProcessMap.put(31, "Regional Spectral Model (RSM0103)");
    genProcessMap.put(32, "Mesoscale Model (MSM0103)");
    genProcessMap.put(51, "One-month EPS (GSM9603_T63L30)");
    genProcessMap.put(52, "One-month EPS (GSM9603_T106L40)");
    genProcessMap.put(53, "One-month EPS (GSM0603C_TL159L40)");
    genProcessMap.put(70, "Seasonal EPS (GSM0103_T63L40)");
    genProcessMap.put(71, "Seasonal EPS (GSM0502C_TL95L40)");
    genProcessMap.put(90, "Sea surface wind correction");
    genProcessMap.put(101, "NOAA-AVHRR analysis data");
    genProcessMap.put(102, "VISSR grid point data (for cloudiness, TBB etc.)");
    genProcessMap.put(103, "Long-wave radiation data");
    genProcessMap.put(104, "GMS data (sea surface temperature)");
    genProcessMap.put(105, "Snow/ice area data");
    genProcessMap.put(106, "Global solar radiation");
    genProcessMap.put(141, "Sea surface temperature analysis (average for dekad or ten days)");
    genProcessMap.put(142, "Sea surface temperature analysis");
    genProcessMap.put(143, "Ocean current analysis");
    genProcessMap.put(144, "Global ocean wave model");
    genProcessMap.put(150, "Regional ocean wave model");
    genProcessMap.put(200, "Volcaninc ash prediction");
    genProcessMap.put(201, "Japanese 55-year Reanalysis (JRA-55)");
  }

}


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

package opendap.servers;

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

import opendap.dap.*;

/**
 * ServerDDS is a specialization of DDS for the server-side of OPeNDAP. This
 * class includes methods used to distinguish synthesized variables
 * (variables added to the DDS by a constraint expression function), methods
 * for CE function management and methods used to return a `constrained DDS'
 * as part of a OPeNDAP data document.
 *

 * All of the variables contained by a ServerDDS must implement the
 * Projection interface.
 *
 * @author jhrg
 * @version $Revision: 15901 $
 * @see DDS
 * @see CEEvaluator
 */
public class ServerDDS extends DDS
{

    protected ServerDDS() {
        super();
    }

    /**
     * Creates an empty Server DDS with the given dataset name.
     *
     * @param n the dataset name
     */
    protected ServerDDS(String n) {
        super(n);
    }

    /**
     * Creates an empty ServerDDS with the given
     * BaseTypeFactory. This will be used for OPeNDAP servers
     * which need to construct subclasses of the various
     * BaseType objects to hold additional server-side
     * information.
     *
     * @param factory the server BaseTypeFactory object.
     */
    public ServerDDS(BaseTypeFactory factory) {
        super(factory);
    }

    /**
     * Creates an empty ServerDDS with the given dataset name
     * and BaseTypeFactory. This will be used for OPeNDAP servers
     * which need to construct subclasses of the various
     * BaseType objects to hold additional server-side
     * information.
     *
     * @param n       the dataset name
     * @param factory the server BaseTypeFactory object.
     */
    public ServerDDS(String n, BaseTypeFactory factory) {
        super(n, factory);
    }

    /**
     * Creates an empty DDS with the given dataset name and
     * BaseTypeFactory.  This will be used for OPeNDAP servers which
     * need to construct subclasses of the various BaseType objects
     * to hold additional server-side information.
     *
     * @param n       the dataset name
     * @param factory the server BaseTypeFactory object.
     * @param schema  the URL where the parser can find an instance of the
     *                OPeNDAP namespace schema.
     */
    public ServerDDS(String n, BaseTypeFactory factory, String schema) {
        super(n, factory, schema);
    }

    /**
     * Set the filename of the dataset. This must be passed to the
     * read() method of the FileIO interface. The filename of
     * the dataset may be a real filename or may be any other string that
     * can be used to identify for the read method how to
     * access the data-store of which a particular variable is a member.
     *
     * @param n The name of the dataset.
     * @see ServerMethods#read(String, Object) ServerMethods.read()
     */
    public void setDatasetFilename(String n) {
        setClearName(n);
    }

    /**
     * Get the dataset filename.
     *
     * @return The filename of the dataset.
     * @see #setDatasetFilename(String)
     */
    public String getDatasetFilename() {
        String s = getEncodedName();
        System.out.println(s);
        return (s);
    }

    /**
     * Print the constrained DDS on the given
     * PrintWriter.
     *
     * @param os the PrintWriter to use for output.
     */
    public void printConstrained(PrintWriter os) {
        os.println("Dataset {");
        for (Enumeration e = getVariables(); e.hasMoreElements();) {
            BaseType bt = (BaseType) e.nextElement();
            // System.err.println("check: "+bt.getLongName()+" = "+((ServerMethods) bt).isProject());
            ServerMethods sm = (ServerMethods) bt;
            if(sm.isProject()) {
                bt.printDecl(os, "    ", true, true);
            }
        }
        os.print("} ");
        if (getEncodedName() != null)
            os.print(getEncodedName());
        os.println(";");
    }

    /**
     * Print the constrained DDS on the given
     * OutputStream.
     *
     * @param os the OutputStream to use for output.
     * @see DDS#print(PrintWriter)
     */
    public final void printConstrained(OutputStream os) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printConstrained(pw);
        pw.flush();
    }

    /**
     * Print the constrained DDS on the given
     * OutputStream.
     *
     * @param os the OutputStream to use for output.
     * @see DDS#print(PrintWriter)
     * @opendap.ddx.experimental
     */
    public final void printConstrainedXML(OutputStream os) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printConstrained(pw);
        pw.flush();
    }

    /**
     * Print the constrained DDS on the given
     * PrintWriter.
     *
     * @param pw the PrintWriter to use for output.
     * @opendap.ddx.experimental
     */
    public void printConstrainedXML(PrintWriter pw) {
        printXML(pw, "", true);
    }
}


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

package ucar.nc2.iosp.gempak;

import ucar.ma2.*;

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

import ucar.unidata.io.RandomAccessFile;

import ucar.unidata.util.StringUtil2;

import java.io.IOException;

import java.nio.ByteBuffer;

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

/**
 * An IOSP for Gempak Surface data.
 *
 * @author dmurray
 */
public class GempakSurfaceIOSP extends GempakStationFileIOSP {

  /**
   * Make the station reader
   *
   * @return GempakSurfaceFileReader
   */
  protected AbstractGempakStationFileReader makeStationReader() {
    return new GempakSurfaceFileReader();
  }

  /**
   * Is this a valid file?
   *
   * @param raf RandomAccessFile to check
   * @return true if a valid Gempak grid file
   * @throws IOException problem reading file
   */
  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    if (!super.isValidFile(raf)) {
      return false;
    }
    // TODO:  handle other types of surface files
    return gemreader.getFileSubType()
            .equals(GempakSurfaceFileReader.STANDARD) || gemreader
            .getFileSubType().equals(GempakSurfaceFileReader.SHIP);
  }

  /**
   * Get the file type id
   *
   * @return the file type id
   */
  public String getFileTypeId() {
    return "GempakSurface";
  }

  /**
   * Get the file type description
   *
   * @return the file type description
   */
  public String getFileTypeDescription() {
    return "GEMPAK Surface Obs Data";
  }

  /**
   * Get the CF feature type
   *
   * @return the feature type
   */
  public String getCFFeatureType() {
    if (gemreader.getFileSubType().equals(GempakSurfaceFileReader.SHIP)) {
      return CF.FeatureType.point.toString();
    }
    return CF.FeatureType.timeSeries.toString();
  }

  /**
   * Read the data for the variable
   *
   * @param v2      Variable to read
   * @param section section infomation
   * @return Array of data
   * @throws IOException           problem reading from file
   * @throws InvalidRangeException invalid Range
   */
  public Array readData(Variable v2, Section section)
          throws IOException, InvalidRangeException {
    if (gemreader == null) {
      return null;
    }
    //System.out.println("looking for " + v2);
    //System.out.println("Section = " + section);
    //Trace.call1("GEMPAKSIOSP: readData");
    Array array = null;
    if (gemreader.getFileSubType().equals(GempakSurfaceFileReader.SHIP)) {
      array = readShipData(v2, section);
    } else if (gemreader.getFileSubType().equals(
            GempakSurfaceFileReader.STANDARD)) {
      array = readStandardData(v2, section);
    } else {  // climate data
      //array = readClimateData(v2, section);
    }
    //  long took = System.currentTimeMillis() - start;
    //  System.out.println("  read data took=" + took + " msec ");
    //Trace.call2("GEMPAKSIOSP: readData");
    return array;
  }

  /**
   * Read in the data for the variable.  In this case, it should be
   * a Structure.  The section should be rank 2 (station, time).
   *
   * @param v2      variable to read
   * @param section section of the variable
   * @return the array of data
   * @throws IOException problem reading the file
   */
  private Array readStandardData(Variable v2, Section section)
          throws IOException {

    Array array = null;
    if (v2 instanceof Structure) {
      List<GempakParameter> params =
              gemreader.getParameters(GempakSurfaceFileReader.SFDT);
      Structure pdata = (Structure) v2;
      StructureMembers members = pdata.makeStructureMembers();
      List<StructureMembers.Member> mbers =
              members.getMembers();
      int i = 0;
      int numBytes = 0;
      int totalNumBytes = 0;
      for (StructureMembers.Member member : mbers) {
        member.setDataParam(4 * i++);
        numBytes = member.getDataType().getSize();
        totalNumBytes += numBytes;
      }
      // one member is a byte
      members.setStructureSize(totalNumBytes);
      float[] missing = new float[mbers.size()];
      int missnum = 0;
      for (Variable v : pdata.getVariables()) {
        Attribute att = v.findAttribute("missing_value");
        missing[missnum++] = (att == null)
                ? GempakConstants.RMISSD
                : att.getNumericValue().floatValue();
      }

      //int num = 0;
      Range stationRange = section.getRange(0);
      Range timeRange = section.getRange(1);
      int size = stationRange.length() * timeRange.length();
      // Create a ByteBuffer using a byte array
      byte[] bytes = new byte[totalNumBytes * size];
      ByteBuffer buf = ByteBuffer.wrap(bytes);
      array = new ArrayStructureBB(members, new int[]{size}, buf, 0);

      for (int stnIdx : stationRange) {
        for (int timeIdx : timeRange) {
          GempakFileReader.RData vals = gemreader.DM_RDTR(timeIdx + 1, stnIdx + 1, GempakSurfaceFileReader.SFDT);
          if (vals == null) {
            int k = 0;
            for (StructureMembers.Member member : mbers) {
              if (member.getDataType().equals(DataType.FLOAT)) {
                buf.putFloat(missing[k]);
              } else {
                buf.put((byte) 1);
              }
              k++;
            }
          } else {
            float[] reals = vals.data;
            int var = 0;
            for (GempakParameter param : params) {
              if (members.findMember(param.getName()) != null) {
                buf.putFloat(reals[var]);
              }
              var++;
            }
            // always add the missing flag
            buf.put((byte) 0);
          }
        }
      }
      //Trace.call2("GEMPAKSIOSP: readStandardData");
    }
    return array;
  }

  /**
   * Read in the data for the record variable.  In this case, it should be
   * a Structure of record dimension.  We can handle a subset of the
   * variables in a structure.
   *
   * @param v2      variable to read
   * @param section section of the variable
   * @return the array of data
   * @throws IOException problem reading the file
   */
  private Array readShipData(Variable v2, Section section)
          throws IOException {

    Array array = null;
    if (v2 instanceof Structure) {
      List<GempakParameter> params =
              gemreader.getParameters(GempakSurfaceFileReader.SFDT);
      Structure pdata = (Structure) v2;
      StructureMembers members = pdata.makeStructureMembers();
      List<StructureMembers.Member> mbers = members.getMembers();
      int ssize = 0;
      //int stnVarNum = 0;
      List<String> stnKeyNames = gemreader.getStationKeyNames();
      for (StructureMembers.Member member : mbers) {
        if (stnKeyNames.contains(member.getName())) {
          int varSize = getStnVarSize(member.getName());
          member.setDataParam(ssize);
          ssize += varSize;
        } else if (member.getName().equals(TIME_VAR)) {
          member.setDataParam(ssize);
          ssize += 8;
        } else if (member.getName().equals(MISSING_VAR)) {
          member.setDataParam(ssize);
          ssize += 1;
        } else {
          member.setDataParam(ssize);
          ssize += 4;
        }
      }
      members.setStructureSize(ssize);

      // TODO:  figure out how to get the missing value for data
      //float[] missing = new float[mbers.size()];
      //int     missnum = 0;
      //for (Variable v : pdata.getVariables()) {
      //    Attribute att = v.findAttribute("missing_value");
      //    missing[missnum++] = (att == null)
      //                         ? GempakConstants.RMISSD
      //                         : att.getNumericValue().floatValue();
      //}

      Range recordRange = section.getRange(0);
      int size = recordRange.length();
      // Create a ByteBuffer using a byte array
      byte[] bytes = new byte[ssize * size];
      ByteBuffer buf = ByteBuffer.wrap(bytes);
      array = new ArrayStructureBB(members, new int[]{size}, buf, 0);
      List<GempakStation> stationList = gemreader.getStations();
      List<Date> dateList = gemreader.getDates();
      boolean needToReadData = !pdata.isSubset();
      if (!needToReadData) {  // subset, see if we need some param data
        for (GempakParameter param : params) {
          if (members.findMember(param.getName()) != null) {
            needToReadData = true;
            break;
          }
        }
      }
      //boolean hasTime = (members.findMember(TIME_VAR) != null);

      // fill out the station information
      for (int recIdx : recordRange) {
        GempakStation stn = stationList.get(recIdx);
        for (String varname : stnKeyNames) {
          if (members.findMember(varname) == null) {
            continue;
          }
          String temp = null;
          switch (varname) {
            case GempakStation.STID:
              temp = StringUtil2.padRight(stn.getName(), 8);
              break;
            case GempakStation.STNM:
              buf.putInt(stn.getSTNM());
              break;
            case GempakStation.SLAT:
              buf.putFloat((float) stn.getLatitude());
              break;
            case GempakStation.SLON:
              buf.putFloat((float) stn.getLongitude());
              break;
            case GempakStation.SELV:
              buf.putFloat((float) stn.getAltitude());
              break;
            case GempakStation.STAT:
              temp = StringUtil2.padRight(stn.getSTAT(), 2);
              break;
            case GempakStation.COUN:
              temp = StringUtil2.padRight(stn.getCOUN(), 2);
              break;
            case GempakStation.STD2:
              temp = StringUtil2.padRight(stn.getSTD2(), 4);
              break;
            case GempakStation.SPRI:
              buf.putInt(stn.getSPRI());
              break;
            case GempakStation.SWFO:
              temp = StringUtil2.padRight(stn.getSWFO(), 4);
              break;
            case GempakStation.WFO2:
              temp = StringUtil2.padRight(stn.getWFO2(), 4);
              break;
          }
          if (temp != null) {
            buf.put(temp.getBytes(CDM.utf8Charset));
          }
        }
        if (members.findMember(TIME_VAR) != null) {
          // put in the time
          Date time = dateList.get( recIdx);
          buf.putDouble(time.getTime() / 1000.d);
        }

        if (needToReadData) {
          int column = stn.getIndex();
          GempakFileReader.RData vals =
                  gemreader.DM_RDTR(1, column,
                          GempakSurfaceFileReader.SFDT);
          if (vals == null) {
            for (GempakParameter param : params) {
              if (members.findMember(param.getName()) != null) {
                buf.putFloat(GempakConstants.RMISSD);
              }
            }
            buf.put((byte) 1);
          } else {
            float[] reals = vals.data;
            int var = 0;
            for (GempakParameter param : params) {
              if (members.findMember(param.getName()) != null) {
                buf.putFloat(reals[var]);
              }
              var++;
            }
            buf.put((byte) 0);
          }
        }
      }
      //Trace.call2("GEMPAKSIOSP: readShipData");
    }
    return array;
  }

  /**
   * Build the netCDF file
   *
   * @throws IOException problem reading the file
   */
  protected void fillNCFile() throws IOException {
    String fileType = gemreader.getFileSubType();
    switch (fileType) {
      case GempakSurfaceFileReader.STANDARD:
        buildStandardFile();
        break;
      case GempakSurfaceFileReader.SHIP:
        buildShipFile();
        break;
      default:
        buildClimateFile();
        break;
    }
  }

  /**
   * Build a standard station structure
   */
  private void buildStandardFile() {
    // Build station list
    List<GempakStation> stations = gemreader.getStations();
    //Trace.msg("GEMPAKSIOSP: now have " + stations.size() + " stations");
    Dimension station = new Dimension("station", stations.size(), true);
    ncfile.addDimension(null, station);
    ncfile.addDimension(null, DIM_LEN8);
    ncfile.addDimension(null, DIM_LEN4);
    ncfile.addDimension(null, DIM_LEN2);
    List<Variable> stationVars = makeStationVars(stations, station);
    // loop through and add to ncfile
    for (Variable stnVar : stationVars) {
      ncfile.addVariable(null, stnVar);
    }

    // Build variable list (var(station,time))
    // time
    List<Date> timeList = gemreader.getDates();
    int numTimes = timeList.size();
    Dimension times = new Dimension(TIME_VAR, numTimes, true);
    ncfile.addDimension(null, times);
    Array varArray;
    Variable timeVar = new Variable(ncfile, null, null, TIME_VAR, DataType.DOUBLE, TIME_VAR);
    timeVar.addAttribute(new Attribute(CDM.UNITS, "seconds since 1970-01-01 00:00:00"));
    timeVar.addAttribute(new Attribute("long_name", TIME_VAR));
    varArray = new ArrayDouble.D1(numTimes);
    int i = 0;
    for (Date date : timeList) {
      ((ArrayDouble.D1) varArray).set(i, date.getTime() / 1000.d);
      i++;
    }
    timeVar.setCachedData(varArray, false);
    ncfile.addVariable(null, timeVar);

    List<Dimension> stationTime = new ArrayList<>();
    stationTime.add(station);
    stationTime.add(times);
    // TODO: handle other parts
    Structure sfData = makeStructure(GempakSurfaceFileReader.SFDT,
            stationTime, true);
    if (sfData == null) {
      return;
    }
    sfData.addAttribute(new Attribute(CF.COORDINATES, "time SLAT SLON SELV"));
    ncfile.addVariable(null, sfData);
    ncfile.addAttribute(
            null,
            new Attribute(
                    "CF:featureType",
                    CF.FeatureType.timeSeries.toString()));
  }

  /**
   * Build a ship station structure.  Here the columns are the
   * stations/reports and the rows (1) are the reports.
   */
  private void buildShipFile() {
    // Build variable list (var(station,time))
    List<GempakStation> stations = gemreader.getStations();
    int numObs = stations.size();
    //Trace.msg("GEMPAKSIOSP: now have " + numObs + " stations");
    Dimension record = new Dimension("record", numObs, true,
            (numObs == 0), false);
    ncfile.addDimension(null, record);
    List<Dimension> records = new ArrayList<>(1);
    records.add(record);

    // time
    Variable timeVar = new Variable(ncfile, null, null, TIME_VAR,
            DataType.DOUBLE, (String)null);
    timeVar.addAttribute(
            new Attribute(CDM.UNITS, "seconds since 1970-01-01 00:00:00"));
    timeVar.addAttribute(new Attribute("long_name", TIME_VAR));

    ncfile.addDimension(null, DIM_LEN8);
    ncfile.addDimension(null, DIM_LEN4);
    ncfile.addDimension(null, DIM_LEN2);
    List<Variable> stationVars = makeStationVars(stations, null);

    List<GempakParameter> params =
            gemreader.getParameters(GempakSurfaceFileReader.SFDT);
    if (params == null) {
      return;
    }
    Structure sVar = new Structure(ncfile, null, null, "Obs");
    sVar.setDimensions(records);
    // loop through and add to ncfile
    boolean hasElevation = false;
    for (Variable stnVar : stationVars) {
      if (stnVar.getShortName().equals("SELV")) {
        hasElevation = true;
      }
      sVar.addMemberVariable(stnVar);
    }
    sVar.addMemberVariable(timeVar);

    for (GempakParameter param : params) {
      Variable var = makeParamVariable(param, null);
      sVar.addMemberVariable(var);
    }
    sVar.addMemberVariable(makeMissingVariable());
    String coords = "Obs.time Obs.SLAT Obs.SLON";
    if (hasElevation) {
      coords = coords + " Obs.SELV";
    }
    sVar.addAttribute(new Attribute(CF.COORDINATES, coords));
    ncfile.addVariable(null, sVar);
    ncfile.addAttribute(null,
            new Attribute("CF:featureType",
                    CF.FeatureType.point.toString()));
  }

  /**
   * Build a ship station structure.  Here the columns are the
   * times and the rows are the stations.
   */
  private void buildClimateFile() {
  }

}


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

package ucar.nc2.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;

/**
 * Miscellaneous static routines.
 *
 * @author caron
 */
public class Misc {
  public static final int referenceSize = 4;   // estimates pointer size, in principle JVM dependent
  public static final int objectSize = 16;   // estimates pointer size, in principle JVM dependent

  /**
   * The default maximum {@link #relativeDifference(float, float) relative difference} that two floats can have in
   * order to be deemed {@link #nearlyEquals(float, float) nearly equal}.
   */
  public static final float defaultMaxRelativeDiffFloat = 1.0e-5f;

  /**
   * The default maximum {@link #relativeDifference(double, double) relative difference} that two doubles can have in
   * order to be deemed {@link #nearlyEquals(double, double) nearly equal}.
   */
  public static final double defaultMaxRelativeDiffDouble = 1.0e-8;

  /**
   * Returns the absolute difference between two numbers, i.e. {@code |a - b|}.
   *
   * @param a  first number.
   * @param b  second number.
   * @return   the absolute difference.
   */
  public static float absoluteDifference(float a, float b) {
    if (Float.compare(a, b) == 0) {  // Shortcut: handles infinities and NaNs.
      return 0;
    } else {
      return Math.abs(a - b);
    }
  }

  /** Same as {@link #absoluteDifference(float, float)}, but for doubles. */
  public static double absoluteDifference(double a, double b) {
    if (Double.compare(a, b) == 0) {  // Shortcut: handles infinities and NaNs.
      return 0;
    } else {
      return Math.abs(a - b);
    }
  }

  /**
   * Returns the relative difference between two numbers, i.e. {@code |a - b| / max(|a|, |b|)}.
   *

   * For cases where {@code a == 0}, {@code b == 0}, or {@code a} and {@code b} are extremely close, traditional
   * relative difference calculation breaks down. So, in those instances, we compute the difference relative to
   * {@link Float#MIN_NORMAL}, i.e. {@code |a - b| / Float.MIN_NORMAL}.
   *
   * @param a  first number.
   * @param b  second number.
   * @return   the relative difference.
   * @see The Floating-Point Guide
   * @see
   *          Comparing Floating Point Numbers, 2012 Edition
   */
  public static float relativeDifference(float a, float b) {
    float absDiff = absoluteDifference(a, b);

    if (Float.compare(a, b) == 0) {  // Shortcut: handles infinities and NaNs.
      return 0;
    } else if (a == 0 || b == 0 || absDiff < Float.MIN_NORMAL) {
      return absDiff / Float.MIN_NORMAL;
    } else {
      float maxAbsValue = Math.max(Math.abs(a), Math.abs(b));
      return absDiff / maxAbsValue;
    }
  }

  /** Same as {@link #relativeDifference(float, float)}, but for doubles. */
  public static double relativeDifference(double a, double b) {
    double absDiff = absoluteDifference(a, b);

    if (Double.compare(a, b) == 0) {  // Shortcut: handles infinities and NaNs.
      return 0;
    } else if (a == 0 || b == 0 || absDiff < Double.MIN_NORMAL) {
      return absDiff / Double.MIN_NORMAL;
    } else {
      double maxAbsValue = Math.max(Math.abs(a), Math.abs(b));
      return absDiff / maxAbsValue;
    }
  }

  /** Returns the result of {@link #nearlyEquals(float, float, float)}, with {@link #defaultMaxRelativeDiffFloat}. */
  public static boolean nearlyEquals(float a, float b) {
    return nearlyEquals(a, b, defaultMaxRelativeDiffFloat);
  }

  /**
   * Returns {@code true} if {@code a} and {@code b} are nearly equal. Specifically, it checks whether the
   * {@link #relativeDifference(float, float) relative difference} of the two numbers is less than {@code maxRelDiff}.
   *
   * @param a  first number.
   * @param b  second number.
   * @param maxRelDiff  the maximum {@link #relativeDifference relative difference} the two numbers may have.
   * @return {@code true} if {@code a} and {@code b} are nearly equal.
   */
  public static boolean nearlyEquals(float a, float b, float maxRelDiff) {
    return relativeDifference(a, b) < maxRelDiff;
  }

  /**
   * Returns the result of {@link #nearlyEquals(double, double, double)}, with {@link #defaultMaxRelativeDiffDouble}.
   */
  public static boolean nearlyEquals(double a, double b) {
    return nearlyEquals(a, b, defaultMaxRelativeDiffDouble);
  }

  /** Same as {@link #nearlyEquals(float, float, float)}, but for doubles. */
  public static boolean nearlyEquals(double a, double b, double maxRelDiff) {
    return relativeDifference(a, b) < maxRelDiff;
  }

  /**
   * Check if two numbers are nearly equal with given absolute tolerance.
   *
   * @param a  first number.
   * @param b  second number.
   * @param maxAbsDiff  the maximum {@link #absoluteDifference absolute difference} the two numbers may have.
   * @return true if within tolerance.
   */
  public static boolean nearlyEqualsAbs(float a, float b, float maxAbsDiff) {
    return absoluteDifference(a, b) <= Math.abs(maxAbsDiff);
  }

  /** Same as {@link #nearlyEqualsAbs(float, float, float)}, but with doubles. */
  public static boolean nearlyEqualsAbs(double a, double b, double maxAbsDiff) {
    return absoluteDifference(a, b) <= Math.abs(maxAbsDiff);
  }

  static public String showInts(int[] inta) {
    if (inta == null) return "null";
    Formatter f = new Formatter();
    for (int i : inta) f.format("%d,", i);
    return f.toString();
  }

  static public String showInts(List<Integer> intList) {
    if (intList == null) return "null";
    Formatter f = new Formatter();
    for (int i : intList) f.format("%d,", i);
    return f.toString();
  }

  static public void showInts(int[] inta, Formatter f) {
    if (inta == null) {
      f.format("null");
      return;
    }
    for (int i : inta) f.format("%d, ", i);
  }

  static public String showBytes(byte[] buff) {
    StringBuilder sbuff = new StringBuilder();
    for (int i = 0; i < buff.length; i++) {
      byte b = buff[i];
      int ub = (b < 0) ? b + 256 : b;
      if (i > 0) sbuff.append(" ");
      sbuff.append(ub);
    }
    return sbuff.toString();
  }

  static public void showBytes(byte[] buff, Formatter f) {
    for (byte b : buff) {
      int ub = (b < 0) ? b + 256 : b;
      f.format("%3d ", ub);
    }
  }

  static public int getSize(Iterable ii) {
    if (ii instanceof Collection)
      return ((Collection) ii).size();
    int count = 0;
    for (Object i : ii) count++;
    return count;
  }

  static public List getList(Iterable ii) {
    if (ii instanceof List)
      return (List) ii;
    List<Object> result = new ArrayList<>();
    for (Object i : ii) result.add(i);
    return result;
  }

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

  static public boolean compare(byte[] raw1, byte[] raw2, Formatter f) {
    if (raw1 == null || raw2 == null) return false;

    if (raw1.length != raw2.length) {
      f.format("length 1= %3d != length 2=%3d%n", raw1.length, raw2.length);
    }
    int len = Math.min(raw1.length, raw2.length);

    int ndiff = 0;
    for (int i = 0; i < len; i++) {
      if (raw1[i] != raw2[i]) {
        f.format(" %3d : %3d != %3d%n", i + 1, raw1[i], raw2[i]);
        ndiff++;
      }
    }
    f.format("tested %d bytes  diff = %d %n", len, ndiff);
    return ndiff == 0 && (raw1.length == raw2.length);
  }

  static public void compare(float[] raw1, float[] raw2, Formatter f) {
    if (raw1.length != raw2.length) {
      f.format("compareFloat: length 1= %3d != length 2=%3d%n", raw1.length, raw2.length);
    }
    int len = Math.min(raw1.length, raw2.length);

    int ndiff = 0;
    for (int i = 0; i < len; i++) {
      if (!Misc.nearlyEquals(raw1[i], raw2[i]) && !Double.isNaN(raw1[i]) && !Double.isNaN(raw2[i])) {
        f.format(" %5d : %3f != %3f%n", i, raw1[i], raw2[i]);
        ndiff++;
      }
    }
    f.format("tested %d floats diff = %d %n", len, ndiff);
  }

  // from Java7
  public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
  }

  public static int compare(long x, long y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
  }

  public static String stackTraceToString(StackTraceElement[] stackTrace) {
    StringBuffer buf = new StringBuffer();
    for (StackTraceElement ste : stackTrace) {
      buf.append(ste.toString());
      buf.append("\n");
    }
    return buf.toString();
  }

}


package thredds.catalog.util;

import thredds.catalog.*;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

/**
 * _more_
 *
 * @author edavis
 * @since 4.2
 */
public class DeepCopyUtils
{
  private DeepCopyUtils() {}

  private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( DeepCopyUtils.class );

  public static InvCatalog copyCatalog( InvCatalog catalog )
  {
    if ( catalog == null ) throw new IllegalArgumentException( "Catalog may not be null." );

    InvCatalogImpl resultCatalog = new InvCatalogImpl( catalog.getName(), "1.0", catalog.getExpires(), ((InvCatalogImpl) catalog).getBaseURI() );

    List<InvService> copiedServices = copyServicesIntoCopiedCatalog( catalog, resultCatalog );

    for ( InvDataset curDs : catalog.getDatasets()) {
      resultCatalog.addDataset( (InvDatasetImpl) DeepCopyUtils.copyDataset( curDs, copiedServices, false ) );
    }

    resultCatalog.finish();
    return resultCatalog;
  }

  private static List<InvService> copyServicesIntoCopiedCatalog( InvCatalog catalog, InvCatalogImpl resultCatalog )
  {
    List<InvService> services = catalog.getServices();
    for ( InvService service : services ) {
      resultCatalog.addService( DeepCopyUtils.copyService( service ) );
    }
    return resultCatalog.getServices();
  }

  public static InvCatalog subsetCatalogOnDataset( InvCatalog catalog, String datasetId )
  {
    if ( catalog == null ) throw new IllegalArgumentException( "Catalog may not be null." );
    if ( datasetId == null ) throw new IllegalArgumentException( "Dataset ID may not be null." );
    InvDataset ds = catalog.findDatasetByID( datasetId );
    if ( ds == null )
      throw new IllegalArgumentException( "The dataset ID [" + datasetId + "] does not match the ID of a dataset in the catalog." );
    return subsetCatalogOnDataset( catalog, ds );
  }

  public static InvCatalog subsetCatalogOnDataset( InvCatalog catalog, InvDataset dataset)
  {
    if ( catalog == null ) throw new IllegalArgumentException( "Catalog may not be null." );
    if ( dataset == null ) throw new IllegalArgumentException( "Dataset may not be null." );
    if ( dataset.getParentCatalog() != catalog )
      throw new IllegalArgumentException( "Catalog must contain the dataset." );

    URI docBaseUri = formDocBaseUriForSubsetCatalog( catalog, dataset );

    InvCatalogImpl resultCatalog = new InvCatalogImpl( dataset.getName(), "1.0", docBaseUri );

    List<InvService> copiedServices = copyServicesIntoCopiedCatalog( catalog, resultCatalog );

    InvDataset topDs = DeepCopyUtils.copyDataset( dataset, copiedServices, true );

    resultCatalog.addDataset( (InvDatasetImpl) topDs );

    resultCatalog.finish();
    return resultCatalog;
  }

  private static URI formDocBaseUriForSubsetCatalog( InvCatalog catalog, InvDataset dataset )
  {
    String catDocBaseUri = catalog.getUriString();
    String subsetDocBaseUriString = catDocBaseUri + "/" + ( dataset.getID() != null ? dataset.getID() : dataset.getName() );
    try {
//      URI thisDocBaseUri = ((InvCatalogImpl) catalog ).getBaseURI();
//      String uriPath = thisDocBaseUri.getPath() + "/" + ( dataset.getID() != null ? dataset.getID() : dataset.getName() );
//      URI subsetDocBaseUri = new URI( thisDocBaseUri.getScheme(), thisDocBaseUri.getUserInfo(), thisDocBaseUri.getHost(),
//                                      thisDocBaseUri.getPort(), uriPath, null, null );
      return new URI( subsetDocBaseUriString);
    }
    catch ( URISyntaxException e ) {
      // This shouldn't happen. But just in case ...
      throw new IllegalStateException( "Bad document Base URI for new catalog [" + catalog.getUriString() + "/" + (dataset.getID() != null ? dataset.getID() : dataset.getName()) + "].", e );
    }
  }

//  public static InvDataset copyDatasetAddingInheritedMetadata( InvDataset dataset, List<ThreddsMetadata> inheritedMd, List<InvService> availableServices)
//  {
//
//  }

  public static InvDataset copyDataset( InvDataset dataset, List<InvService> availableServices,
                                        boolean copyInheritedMetadataFromParents )
  {
    if ( dataset == null ) throw new IllegalArgumentException( "Dataset may not be null." );
    if ( availableServices == null ) throw new IllegalArgumentException( "List of available services may not be null.");

    InvDatasetImpl resultDs;
    // ToDo Deal with InvDatasetScan and its ilk.
    if ( dataset instanceof InvCatalogRef )
    {
      InvCatalogRef catRef = (InvCatalogRef) dataset;
      resultDs = new InvCatalogRef( null, catRef.getName(), catRef.getXlinkHref());
    } else {
      resultDs = new InvDatasetImpl( null, dataset.getName() );
    }
    resultDs.setID( dataset.getID() );

    resultDs.transferMetadata( (InvDatasetImpl) dataset, copyInheritedMetadataFromParents );

    // Only copy child InvAccess if the current dataset is not an InvCatalogRef.
    if ( !( dataset instanceof InvCatalogRef ) ) {
      String urlPath = ( (InvDatasetImpl) dataset ).getUrlPath();
      if ( urlPath != null )
        resultDs.setUrlPath( urlPath );
      else
      {
        for ( InvAccess curAccess : dataset.getAccess()) {
          InvAccess access = copyAccess( curAccess, resultDs, availableServices );
          if ( access != null ) resultDs.addAccess( access );
        }
      }
    }

    // Only recurse into child datasets if the current dataset is not an InvCatalogRef.
    if ( !( dataset instanceof InvCatalogRef ) ) {
      for ( InvDataset curDs : dataset.getDatasets() )
      {
        InvDatasetImpl curDsCopy = (InvDatasetImpl) copyDataset( curDs, availableServices, false );
        curDsCopy.setParent( resultDs );
        resultDs.addDataset( curDsCopy );
      }
    }

    return resultDs;
  }

  public static InvAccess copyAccess( InvAccess access, InvDataset parentDataset, List<InvService> availableServices )
  {
    if ( parentDataset == null ) throw new IllegalArgumentException( "Parent dataset may not be null.");
    String serviceName = access.getService().getName();
    InvService service = findServiceByName( serviceName, availableServices);
    if ( service == null ) {
      logger.warn( "Access service [" + serviceName + "] not in available service list.");
      return null;
      // ToDo Support copying service.
      //service = copyService( access.getService() );
    }
    DataFormatType dataFormatType = access.getDataFormatType();
    InvAccessImpl resultAccess;
    if ( dataFormatType == null ) {
      resultAccess = new InvAccessImpl( parentDataset, access.getUrlPath(), service );
      resultAccess.setSize( access.getDataSize() );
    }
    else
      resultAccess = new InvAccessImpl( parentDataset, access.getUrlPath(), service.getName(), null,
                                        dataFormatType.toString(), access.getDataSize() );

    return resultAccess;
  }

  static public InvService findServiceByName( String name, List<InvService> servicePool)
  {
    if ( servicePool == null ) return null;

    for ( InvService curService : servicePool )
    {
      if (curService.getName().equals( name ))
        return curService;
      List<InvService> nestedServices = curService.getServices();
      InvService target = nestedServices != null ? findServiceByName( name, nestedServices ) : null;
      if ( target != null )
        return target;
    }
    return null;
  }

  public static InvService copyService( InvService service )
  {
    if ( service == null ) throw new IllegalArgumentException( "Service may not be null.");
    InvService resultService = new InvService( service.getName(), service.getServiceType().toString(),
                                               service.getBase(), service.getSuffix(), service.getDescription() );
    for ( InvService curService : service.getServices()) {
      resultService.addService( copyService( curService ) );
    }
    for ( InvProperty curProperty : service.getProperties()) {
      resultService.addProperty( copyProperty( curProperty ) );
    }
    for ( InvProperty curDatasetRoot : service.getDatasetRoots()) {
      resultService.addDatasetRoot( copyProperty( curDatasetRoot) );
    }

    return resultService;
  }

  public static InvProperty copyProperty( InvProperty property) {
    return new InvProperty( property.getName(), property.getValue());
  }
}


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

package ucar.nc2.iosp.bufr.tables;

import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import ucar.nc2.wmo.Util;
import ucar.unidata.util.StringUtil2;

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

/**
 * Read WMO BUFR XML formats
 *
 * @author John
 * @since 8/10/11
 */
public class WmoXmlReader {
  public enum Version {
    BUFR_14_1_0, BUFR_14_2_0, BUFR_15_1_1, BUFR_16_0_0, BUFR_WMO;

    String[] getElemNamesB() {
      if (this == BUFR_14_1_0) {
        return new String[]{"BC_TableB_BUFR14_1_0_CREX_6_1_0", "ElementName_E"};

      } else if (this == BUFR_14_2_0) {
        return new String[]{"Exporting_BCTableB_E", "ElementName"};

      } else if (this == BUFR_15_1_1) {
        return new String[]{"Exp_JointTableB_E", "ElementName_E"};

      } else if (this == BUFR_16_0_0) {
        return new String[]{"Exp_BUFRCREXTableB_E", "ElementName_E"};

      } else if (this == BUFR_WMO) {   // from now on this is the element name
        return new String[]{null, "ElementName_en"};

      }
      return null;
    }

    String[] getElemNamesD() {
      if (this == BUFR_14_1_0) {
        return new String[]{"B_TableD_BUFR14_1_0_CREX_6_1_0", "ElementName1_E"};

      } else if (this == BUFR_14_2_0) {
        return new String[]{"Exporting_BUFRTableD_E", "ElementName1"};

      } else if (this == BUFR_15_1_1) {
        return new String[]{"Exp_BUFRTableD_E", "ElementName_E"};

      } else if (this == BUFR_16_0_0) {
        return new String[]{"Exp_BUFRTableD_E", "ElementName_E"};

      } else if (this == BUFR_WMO) {
        return new String[]{null, "ElementName_en"};

      }
      return null;
    }
  }

  /*
  14.1
  <BC_TableB_BUFR14_1_0_CREX_6_1_0>
    <SNo>1</SNo>
    <Class>00</Class>
    <FXY>000001</FXY>
    <ElementName_E>Table A: entry</ElementName_E>
    <ElementName_F>Table A : entr?e</ElementName_F>
    <ElementName_R>??????? ?: ???????</ElementName_R>
    <ElementName_S>Tabla A: elemento</ElementName_S>
    <BUFR_Unit>CCITT IA5</BUFR_Unit>
    <BUFR_Scale>0</BUFR_Scale>
    <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
    <BUFR_DataWidth_Bits>24</BUFR_DataWidth_Bits>
    <CREX_Unit>Character</CREX_Unit>
    <CREX_Scale>0</CREX_Scale>
    <CREX_DataWidth>3</CREX_DataWidth>
    <Status>Operational</Status>
    <NotesToTable_E>Notes: (see)#BUFR14_1_0_CREX6_1_0_Notes.doc#BC_Cl000</NotesToTable_E>
</BC_TableB_BUFR14_1_0_CREX_6_1_0>

14.2
<Exporting_BCTableB_E>
  <No>2</No>
  <ClassNo>00</ClassNo>
  <ClassName>BUFR/CREX table entries</ClassName>
  <FXY>000002</FXY>
  <ElementName>Table A: data category description, line 1 </ElementName>
  <BUFR_Unit>CCITT IA5 </BUFR_Unit>
  <BUFR_Scale>0</BUFR_Scale>
  <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
  <BUFR_DataWidth_Bits>256</BUFR_DataWidth_Bits>
  <CREX_Unit>Character</CREX_Unit>
  <CREX_Scale>0</CREX_Scale>
  <CREX_DataWidth>32</CREX_DataWidth>
  <Status>Operational</Status>
</Exporting_BCTableB_E>

15.1
<Exp_JointTableB_E>
  <No>1</No>
  <ClassNo>00</ClassNo>
  <ClassName_E>BUFR/CREX table entries</ClassName_E>
  <FXY>000001</FXY>
  <ElementName_E>Table A: entry</ElementName_E>
  <BUFR_Unit>CCITT IA5</BUFR_Unit>
  <BUFR_Scale>0</BUFR_Scale>
  <BUFR_ReferenceValue>0</BUFR_ReferenceValue>
  <BUFR_DataWidth_Bits>24</BUFR_DataWidth_Bits>
  <CREX_Unit>Character</CREX_Unit>
  <CREX_Scale>0</CREX_Scale>
  <CREX_DataWidth_Char>3</CREX_DataWidth_Char>
  <Status>Operational</Status>
</Exp_JointTableB_E>

16.0
<Exp_BUFRCREXTableB_E>
  <No>681</No>
  <ClassNo>13</ClassNo>
  <ClassName_E>Hydrographic and hydrological elements</ClassName_E>
  <FXY>013060</FXY>
  <ElementName_E>Total accumulated precipitation</ElementName_E>
  <BUFR_Unit>kg m-2</BUFR_Unit>
  <BUFR_Scale>1</BUFR_Scale>
  <BUFR_ReferenceValue>-1</BUFR_ReferenceValue>
  <BUFR_DataWidth_Bits>17</BUFR_DataWidth_Bits>
  <CREX_Unit>kg m-2</CREX_Unit>
  <CREX_Scale>1</CREX_Scale>
  <CREX_DataWidth_Char>5</CREX_DataWidth_Char>
  <Status>Operational</Status>
</Exp_BUFRCREXTableB_E>

<BUFRCREX_17_0_0_TableB_en>
<No>8</No>
<ClassNo>00</ClassNo>
<ClassName_en>BUFR/CREX table entries</ClassName_en>
<FXY>000008</FXY>
<ElementName_en>BUFR Local table version number</ElementName_en>
<Note_en>(see Note 4)</Note_en>
<BUFR_Unit>CCITT IA5</BUFR_Unit>
<BUFR_Scale>0</BUFR_Scale>
<BUFR_ReferenceValue>0</BUFR_ReferenceValue>
<BUFR_DataWidth_Bits>16</BUFR_DataWidth_Bits>
<CREX_Unit>Character</CREX_Unit>
<CREX_Scale>0</CREX_Scale>
<CREX_DataWidth_Char>2</CREX_DataWidth_Char>
<Status>Operational</Status>
</BUFRCREX_17_0_0_TableB_en>

<BUFRCREX_22_0_1_TableB_en>
<No>1018</No>
<ClassNo>21</ClassNo>
<ClassName_en>BUFR/CREX Radar data</ClassName_en>
<FXY>021073</FXY>
<ElementName_en>Satellite altimeter instrument mode</ElementName_en>
<BUFR_Unit>Flag table</BUFR_Unit>
<BUFR_Scale>0</BUFR_Scale>
<BUFR_ReferenceValue>0</BUFR_ReferenceValue>
<BUFR_DataWidth_Bits>9</BUFR_DataWidth_Bits>
<CREX_Unit>Flag table</CREX_Unit>
<CREX_Scale>0</CREX_Scale>
<CREX_DataWidth_Char>3</CREX_DataWidth_Char>
<Status>Operational</Status>
</BUFRCREX_22_0_1_TableB_en>
   */

  static void readWmoXmlTableB(InputStream ios, TableB b) throws IOException {
    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(ios);
    } catch (JDOMException e) {
      throw new IOException(e.getMessage());
    }

    Element root = doc.getRootElement();

    String[] elems = null;
    for (Version v : Version.values()) {
      elems = v.getElemNamesB();
      List<Element> featList = root.getChildren(elems[0]);
      if (featList != null && featList.size() > 0) {
        break;
      }
    }

    // if not found using element name, assume its BUFR_WMO
    if (elems == null) {
      elems = Version.BUFR_WMO.getElemNamesB();
    }

    List<Element> featList = root.getChildren();
    for (Element elem : featList) {
      Element ce = elem.getChild(elems[1]);
      if (ce == null) continue;

      String name = Util.cleanName(elem.getChildTextNormalize(elems[1]));
      String units = cleanUnit(elem.getChildTextNormalize("BUFR_Unit"));
      int x = 0, y = 0, scale = 0, reference = 0, width = 0;

      String fxy = null;
      String s = null;
      try {
        fxy = elem.getChildTextNormalize("FXY");
        int xy = Integer.parseInt(cleanNumber(fxy));
        x = xy / 1000;
        y = xy % 1000;

      } catch (NumberFormatException e) {
        System.out.printf(" key %s name '%s' fails parsing %n", fxy, name);
      }

      try {
        s = elem.getChildTextNormalize("BUFR_Scale");
        scale = Integer.parseInt(cleanNumber(s));
      } catch (NumberFormatException e) {
        System.out.printf(" key %s name '%s' has bad scale='%s'%n", fxy, name, s);
      }

      try {
        s = elem.getChildTextNormalize("BUFR_ReferenceValue");
        reference = Integer.parseInt(cleanNumber(s));
      } catch (NumberFormatException e) {
        System.out.printf(" key %s name '%s' has bad reference='%s' %n", fxy, name, s);
      }

      try {
        s = elem.getChildTextNormalize("BUFR_DataWidth_Bits");
        width = Integer.parseInt(cleanNumber(s));
      } catch (NumberFormatException e) {
        System.out.printf(" key %s name '%s' has bad width='%s' %n", fxy, name, s);
      }

      b.addDescriptor((short) x, (short) y, scale, reference, width, name, units, null);
    }
    ios.close();
  }

  static String cleanNumber(String s) {
    return StringUtil2.remove(s, ' ');
  }

  public static String cleanUnit(String unit) {
    String result = StringUtil2.remove(unit, 176);
    return StringUtil2.replace(result, (char) 65533, "2"); // seems to be a superscript 2 in some language
  }

  /*
  <B_TableD_BUFR14_1_0_CREX_6_1_0>
    <SNo>2647</SNo>
    <Category>10</Category>
    <FXY1>310013</FXY1>
    <ElementName1_E>(AVHRR (GAC) report)</ElementName1_E>
    <FXY2>004005</FXY2>
    <ElementName2_E>Minute</ElementName2_E>
    <Remarks_E>Minute</Remarks_E>
    <Status>Operational</Status>
  </B_TableD_BUFR14_1_0_CREX_6_1_0>

14.2.0
  <Exporting_BUFRTableD_E>
    <No>2901</No>
    <Category>10</Category>
    <CategoryOfSequences>Vertical sounding sequences (satellite data)</CategoryOfSequences>
    <FXY1>310025</FXY1>
    <ElementName1>(SSMIS Temperature data record)</ElementName1>
    <FXY2>004006</FXY2>
    <Status>Operational</Status>
  </Exporting_BUFRTableD_E>

  15.1.1
  <Exp_BUFRTableD_E>
    <No>102</No>
    <Category>01</Category>
    <CategoryOfSequences_E>Location and identification sequences</CategoryOfSequences_E>
    <FXY1>301034</FXY1>
    <Title_E>(Buoy/platform - fixed)</Title_E>
    <FXY2>001005</FXY2>
    <ElementName_E>Buoy/platform identifier</ElementName_E>
    <ExistingElementName_E>Buoy/platform identifier</ExistingElementName_E>
    <Status>Operational</Status>
  </Exp_BUFRTableD_E>

  16.0.0
  <Exp_BUFRTableD_E>
    <No>402</No>
    <Category>02</Category>
    <CategoryOfSequences_E>Meteorological sequences common to surface data</CategoryOfSequences_E>
    <FXY1>302001</FXY1>
    <FXY2>010051</FXY2>
    <ElementName_E>Pressure reduced to mean sea level</ElementName_E>
    <ExistingElementName_E>Pressure reduced to mean sea level</ExistingElementName_E>
    <Status>Operational</Status>
  </Exp_BUFRTableD_E>

  <BUFR_19_1_1_TableD_en>
  <No>4</No>
  <Category>00</Category>
  <CategoryOfSequences_en>BUFR table entries sequences</CategoryOfSequences_en>
  <FXY1>300003</FXY1>
  <Title_en>(F, X, Y of descriptor to be added or defined)</Title_en>
  <FXY2>000011</FXY2>
  <ElementName_en>X descriptor to be added or defined</ElementName_en>
  <Status>Operational</Status>
  </BUFR_19_1_1_TableD_en>

  <BUFR_22_0_1_TableD_en>
  <No>5874</No>
  <Category>15</Category>
  <CategoryOfSequences_en>Oceanographic report sequences</CategoryOfSequences_en>
  <FXY1>315004</FXY1>
  <Title_en>(XBT temperature profile data sequence)</Title_en>
  <FXY2>025061</FXY2>
  <ElementName_en>Software identification and version number</ElementName_en>
  <Status>Operational</Status>
  </BUFR_22_0_1_TableD_en>

   */
  static void readWmoXmlTableD(InputStream ios, TableD tableD) throws IOException {
    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(ios);
    } catch (JDOMException e) {
      throw new IOException(e.getMessage());
    }

    int currSeqno = -1;
    TableD.Descriptor currDesc = null;

    Element root = doc.getRootElement();

    String[] elems = null;
    for (Version v : Version.values()) {
      elems = v.getElemNamesD();
      List<Element> featList = root.getChildren(elems[0]);
      if (featList != null && featList.size() > 0) {
        break;
      }
    }

    if (elems == null) {
      elems = Version.BUFR_WMO.getElemNamesD();
    }

    List<Element> featList = root.getChildren();
    for (Element elem : featList) {
      Element ce = elem.getChild(elems[1]);
      if (ce == null) continue;

      String seqs = elem.getChildTextNormalize("FXY1");
      int seq = Integer.parseInt(seqs);

      if (currSeqno != seq) {
        int y = seq % 1000;
        int w = seq / 1000;
        int x = w % 100;
        String seqName = Util.cleanName(elem.getChildTextNormalize(elems[1]));
        currDesc = tableD.addDescriptor((short) x, (short) y, seqName, new ArrayList<Short>());
        currSeqno = seq;
      }

      String fnos = elem.getChildTextNormalize("FXY2");
      int fno = Integer.parseInt(fnos);
      int y = fno % 1000;
      int w = fno / 1000;
      int x = w % 100;
      int f = w / 100;
      int fxy = (f << 14) + (x << 8) + y;
      currDesc.addFeature((short) fxy);
    }
    ios.close();
  }

}


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

package ucar.nc2.grib.collection;

import com.google.protobuf.ByteString;
import thredds.inventory.MCollection;
import ucar.nc2.grib.coord.Coordinate;
import ucar.nc2.grib.coord.CoordinateEns;
import ucar.nc2.grib.coord.CoordinateRuntime;
import ucar.nc2.grib.coord.CoordinateTime;
import ucar.nc2.grib.coord.CoordinateTime2D;
import ucar.nc2.grib.coord.CoordinateTimeIntv;
import ucar.nc2.grib.coord.CoordinateVert;
import ucar.nc2.grib.coord.EnsCoordValue;
import ucar.nc2.grib.coord.TimeCoordIntvValue;
import ucar.nc2.grib.coord.VertCoordValue;

/**
 * Common superclass for writing Grib ncx files
 *
 * @author caron
 * @since 2/20/14
 */
class GribCollectionWriter {
  static final int currentVersion = 1;

  protected final MCollection dcm; // may be null, when read in from index
  protected final org.slf4j.Logger logger;

  GribCollectionWriter(MCollection dcm, org.slf4j.Logger logger) {
    this.dcm = dcm;
    this.logger = logger;
  }

  protected GribCollectionProto.Gds writeGdsProto(GribHorizCoordSystem hcs) {
    return writeGdsProto(hcs.getRawGds(), hcs.getPredefinedGridDefinition());
  }

  /*
    message Gds {
      bytes gds = 1;                        // raw gds: Grib1SectionGridDefinition or Grib2SectionGridDefinition
      uint32 predefinedGridDefinition = 2;  // only grib1; instead of gds raw bytes; need center, subcenter to interpret
    }
   */
  static GribCollectionProto.Gds writeGdsProto(byte[] rawGds, int predefinedGridDefinition) {
    GribCollectionProto.Gds.Builder b = GribCollectionProto.Gds.newBuilder();

    if (predefinedGridDefinition >= 0)
      b.setPredefinedGridDefinition(predefinedGridDefinition);
    else {
      b.setGds(ByteString.copyFrom(rawGds));
    }

    return b.build();
  }

    /*
  message Coord {
    GribAxisType axisType = 1;
    int32 code = 2;   // time unit; level type
    string unit = 3;
    repeated float values = 4;
    repeated float bound = 5; // only used if interval, then = (value, bound)
    repeated int64 msecs = 6; // calendar date
   */
    GribCollectionProto.Coord writeCoordProto(CoordinateRuntime coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType( convertAxisType (coord.getType()));
    b.setCode(coord.getCode());
    if (coord.getUnit() != null)
      b.setUnit(coord.getUnit());

    for (int idx=0; idx<coord.getSize(); idx++) {
      long runtime = coord.getRuntime(idx);
      b.addMsecs(runtime);
    }
    return b.build();
  }

  GribCollectionProto.Coord writeCoordProto(CoordinateTime coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType(convertAxisType(coord.getType()));
    b.setCode(coord.getCode());
    b.setUnit(coord.getTimeUnit().toString());
    b.addMsecs(coord.getRefDate().getMillis());
    for (Integer offset : coord.getOffsetSorted())
      b.addValues(offset);

    int[] time2runtime = coord.getTime2runtime();
    if (time2runtime != null)
      for (int val : time2runtime)
        b.addTime2Runtime(val);

    return b.build();
  }

  GribCollectionProto.Coord writeCoordProto(CoordinateTimeIntv coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType( convertAxisType (coord.getType()));
    b.setCode(coord.getCode());
    b.setUnit(coord.getTimeUnit().toString());
    b.addMsecs(coord.getRefDate().getMillis());

    for (TimeCoordIntvValue tinv : coord.getTimeIntervals()) {
      b.addValues(tinv.getBounds1());
      b.addBound(tinv.getBounds2());
    }

    int[] time2runtime = coord.getTime2runtime();
    if (time2runtime != null)
      for (int val : time2runtime)
        b.addTime2Runtime(val);

    return b.build();
  }

  GribCollectionProto.Coord writeCoordProto(CoordinateVert coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType(convertAxisType(coord.getType()));
    b.setCode(coord.getCode());

    if (coord.getUnit() != null) b.setUnit(coord.getUnit());
    for (VertCoordValue level : coord.getLevelSorted()) {
      if (coord.isLayer()) {
        b.addValues((float) level.getValue1());
        b.addBound((float) level.getValue2());
      } else {
        b.addValues((float) level.getValue1());
      }
    }
    return b.build();
  }

  GribCollectionProto.Coord writeCoordProto(CoordinateEns coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType(convertAxisType(coord.getType()));
    b.setCode(coord.getCode());

    if (coord.getUnit() != null) b.setUnit(coord.getUnit());
    for (EnsCoordValue level : coord.getEnsSorted()) {
      b.addValues((float) level.getCode());       // lame
      b.addBound((float) level.getEnsMember());
    }
    return b.build();
  }

  GribCollectionProto.Coord writeCoordProto(CoordinateTime2D coord) {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setAxisType(convertAxisType(coord.getType()));
    b.setCode(coord.getCode());
    b.setUnit(coord.getTimeUnit().toString());
    CoordinateRuntime runtimeCoord = coord.getRuntimeCoordinate();
    for (int idx=0; idx<runtimeCoord.getSize(); idx++) {
      long runtime = runtimeCoord.getRuntime(idx);
      b.addMsecs(runtime);
    }

    b.setIsOrthogonal(coord.isOrthogonal());
    b.setIsRegular(coord.isRegular());
    for (Coordinate time : coord.getTimesForSerialization()) {
      if (time.getType() == Coordinate.Type.time)
        b.addTimes(writeCoordProto((CoordinateTime)time));
      else
        b.addTimes(writeCoordProto((CoordinateTimeIntv)time));
    }

    int[] time2runtime = coord.getTime2runtime();
    if (time2runtime != null)
      for (int val : time2runtime)
        b.addTime2Runtime(val);

    return b.build();
  }

  ///////////////////////////////////////////////////////
  /* not currently used

  protected GribCollectionProto.FcConfig writeConfig(FeatureCollectionConfig config) throws IOException {
    GribCollectionProto.FcConfig.Builder b = GribCollectionProto.FcConfig.newBuilder();
    b.setName(config.collectionName);
    b.setCollectionSpec(config.spec);
    b.setPartitionType(config.ptype.toString());
    if (config.dateFormatMark != null)
      b.setDateFormatMark(config.dateFormatMark);

    FeatureCollectionConfig.GribConfig gribConfig = config.gribConfig;
    if (gribConfig.gdsHash != null) {
      for (Map.Entry<Integer, Integer> entry : gribConfig.gdsHash.entrySet()) {
        GribCollectionProto.IntMap.Builder bIntMap = GribCollectionProto.IntMap.newBuilder();
        bIntMap.setFrom(entry.getKey());
        bIntMap.setTo(entry.getValue());
        b.addGdsConvert(bIntMap);
      }
    }

    b.setPdsUseGenType(gribConfig.useGenType);
    b.setPdsUseTableVersion(gribConfig.useTableVersion);
    b.setPdsIntvMerge(gribConfig.intvMerge);
    b.setPdsUseCenter(gribConfig.useCenter);

    if (gribConfig.intvFilter != null) {
      b.setIntvExcludeZero(gribConfig.intvFilter.isZeroExcluded());
      for (FeatureCollectionConfig.GribIntvFilterParam intvFilter : gribConfig.intvFilter.filterList) {
        GribCollectionProto.IntvFilter.Builder bIntv = GribCollectionProto.IntvFilter.newBuilder();
        bIntv.setVariableId(intvFilter.id);
        bIntv.setIntvLength(intvFilter.intvLength);
        bIntv.setIntvProb(intvFilter.prob);
        b.addIntvFilter(bIntv);
      }
    }

    // time unit convert
    if (gribConfig.tuc != null) {
      for (Map.Entry<Integer, Integer> entry : gribConfig.tuc.map.entrySet()) {
        GribCollectionProto.IntMap.Builder bIntMap = GribCollectionProto.IntMap.newBuilder();
        bIntMap.setFrom(entry.getKey());
        bIntMap.setTo(entry.getValue());
        b.addTimeUnitConvert(bIntMap);
      }
    }

    if (gribConfig.userTimeUnit != null)
      b.setUserTimeUnit(gribConfig.userTimeUnit.toString());

    return b.build();
  }

  protected FeatureCollectionConfig readConfig(boolean isGrib1, GribCollectionProto.FcConfig pconfig) throws IOException {

    FeatureCollectionConfig config = new FeatureCollectionConfig();
    config.name = pconfig.getName();
    config.collectionName = pconfig.getName();
    config.type = isGrib1 ? FeatureCollectionType.GRIB1 : FeatureCollectionType.GRIB2;
    config.spec = pconfig.getCollectionSpec();
    config.ptype = FeatureCollectionConfig.PartitionType.valueOf(pconfig.getPartitionType());
    if (pconfig.getDateFormatMark().length() > 0)
      config.dateFormatMark = pconfig.getDateFormatMark();

    if ( pconfig.getGdsConvertCount() > 0) {
      config.gribConfig.gdsHash =  new HashMap<>();
      for (GribCollectionProto.IntMap pIntMap : pconfig.getGdsConvertList()) {
        config.gribConfig.gdsHash.put(pIntMap.getFrom(), pIntMap.getTo());
      }
    }

    config.gribConfig.useGenType = pconfig.getPdsUseGenType();
    config.gribConfig.useTableVersion = pconfig.getPdsUseTableVersion();
    config.gribConfig.intvMerge = pconfig.getPdsIntvMerge();
    config.gribConfig.useCenter = pconfig.getPdsUseCenter();

    boolean isZeroExcluded = pconfig.getIntvExcludeZero();
    if ( isZeroExcluded || pconfig.getIntvFilterCount() > 0) {
      config.gribConfig.intvFilter =  new FeatureCollectionConfig.GribIntvFilter();
      config.gribConfig.intvFilter.isZeroExcluded = isZeroExcluded;
      config.gribConfig.intvFilter.filterList = new ArrayList<>();
      for (GribCollectionProto.IntvFilter pi :  pconfig.getIntvFilterList()) {
        int prob =  pi.getIntvProb();
        config.gribConfig.intvFilter.filterList.add(new FeatureCollectionConfig.GribIntvFilterParam(pi.getVariableId(), pi.getIntvLength(), prob));
      }
    }

    if ( pconfig.getTimeUnitConvertCount() > 0) {
      config.gribConfig.tuc =  new FeatureCollectionConfig.TimeUnitConverterHash();
      for (GribCollectionProto.IntMap pIntMap :  pconfig.getTimeUnitConvertList()) {
        config.gribConfig.tuc.map.put(pIntMap.getFrom(), pIntMap.getTo());
      }
    }

    if (pconfig.getDateFormatMark().length() > 0)
      config.gribConfig.setUserTimeUnit(pconfig.getDateFormatMark());

    return config;
  } */

  public static GribCollectionProto.GribAxisType convertAxisType(Coordinate.Type type) {
    switch (type) {
      case runtime:
        return GribCollectionProto.GribAxisType.runtime;
      case time:
        return GribCollectionProto.GribAxisType.time;
      case time2D:
        return GribCollectionProto.GribAxisType.time2D;
      case timeIntv:
        return GribCollectionProto.GribAxisType.timeIntv;
      case ens:
        return GribCollectionProto.GribAxisType.ens;
      case vert:
        return GribCollectionProto.GribAxisType.vert;
    }
    throw new IllegalStateException("illegal axis type " + type);
  }

}


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

package dap4.core.ce;

import dap4.core.dmr.*;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.core.util.Slice;

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

/**
 * The Universal constraint is a constraint
 * that includes everything in a DMR, even
 * including items such as dimensions or enums
 * that are defined but never used.
 */

public class Universal extends CEConstraint
{

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

    public Universal()
    {
    }

    public Universal(DapDataset dmr)
            throws DapException
    {
        super(dmr);
        build();
    }

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

    @Override
    public boolean isUniversal() {return true;}

    /**
     * Finish creating this Constraint; for Universal,
     * this does nothing; the important stuff is in build().
     *
     * @throws DapException
     * @returns this - fluent interface
     */
    @Override
    public CEConstraint
    finish()
            throws DapException
    {
        super.finished = true;
        return this;
    }

    protected void
    build()
            throws DapException
    {
        List<DapVariable> top = dmr.getTopVariables();
        for(int i = 0; i < top.size(); i++) {
            DapVariable var = top.get(i);
            List<Slice> slices = DapUtil.dimsetToSlices(var.getDimensions());
            super.addVariable(var, slices);
        }
        super.enums = new ArrayList<DapEnumeration>();
        super.enums.addAll(dmr.getEnums());
        super.dimrefs = new ArrayList<DapDimension>();
        super.dimrefs.addAll(dmr.getDimensions());
        super.groups = new ArrayList<DapGroup>();
        super.groups.addAll(dmr.getGroups());
        super.expand();
        super.finish();
    }

    // Selected consult or iterator overrides for efficiency
    @Override
    public boolean references(DapNode node)
    {
        switch (node.getSort()) {
        case DIMENSION:
        case ENUMERATION:
        case VARIABLE:
        case GROUP:
        case DATASET:
            return true;
        default:
            break;
        }
        return false;
    }

    @Override
    public List<Slice>
    getConstrainedSlices(DapVariable var)
            throws DapException
    {
        List<Slice> slices = super.getConstrainedSlices(var);
        if(slices != null)
            return slices;
        // Create a universal slice
        return universalSlices(var);
    }

    static public List<Slice>
    universalSlices(DapVariable var)
            throws DapException
    {
        return DapUtil.dimsetToSlices(var.getDimensions());
    }

}


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

/**
 * Provides support for classes that parse and format unit specifications.
 *
 * Instances are thread-safe.
 *
 * @author Steven R. Emmerson
 */
public abstract class UnitFormatImpl implements UnitFormat {

	private static final long	serialVersionUID	= 1L;
	private static Object		MUTEX				= new Object();

	/**
	 * Parses a unit specification.  This method is thread-safe.
	 *
	 * @param spec
	 *            The unit specification (e.g. "m/s");
	 * @return The unit corresponding to the specification.
	 * @throws NoSuchUnitException
	 *             A unit in the specification couldn't be found (e.g. the "m"
	 *             in the example).
	 * @throws UnitParseException
	 *             The specification is grammatically incorrect.
	 * @throws SpecificationException
	 *             The specification is incorrect somehow.
	 * @throws UnitDBException
	 *             Problem with the unit database.
	 * @throws PrefixDBException
	 *             Problem with the unit-prefix database.
	 * @throws UnitSystemException
	 *             Problem with the system of units.
	 */
	public final Unit parse(final String spec) throws NoSuchUnitException,
			UnitParseException, SpecificationException, UnitDBException,
			PrefixDBException, UnitSystemException {
		synchronized (MUTEX) {
			return parse(spec, UnitDBManager.instance());
		}
	}

	/**
	 * Formats a Factor (a base unit/exponent pair).
	 *
	 * @param factor
	 *            The base unit/exponent pair.
	 * @return The formatted factor.
	 */
	public final String format(final Factor factor) {
		return format(factor, new StringBuffer(8)).toString();
	}

	/**
	 * Formats a unit. If the unit has a symbol or name, then one of them will
	 * be used; otherwise, a specification of the unit in terms of underlying
	 * units will be returned.
	 *
	 * @param unit
	 *            The unit.
	 * @return The formatted unit.
	 */
	public final String format(final Unit unit) throws UnitClassException {
		return format(unit, new StringBuffer(80)).toString();
	}

	/**
	 * Formats a unit using a long form. This always returns a specification for
	 * the unit in terms of underlying units: it doesn't return the name or
	 * symbol of the unit unless the unit is a base unit.
	 *
	 * @param unit
	 *            The unit.
	 * @return The formatted unit.
	 */
	public final String longFormat(final Unit unit) throws UnitClassException {
		return longFormat(unit, new StringBuffer(80)).toString();
	}
}