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

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

/**
 * Lambert AzimuthalEqualArea Projection spherical earth.
 * <p/>
 * See John Snyder, Map Projections used by the USGS, Bulletin 1532,
 * 2nd edition (1983), p 184
 *
 * @author Unidata Development Team
 * @see Projection
 * @see ProjectionImpl
 */

public class LambertAzimuthalEqualArea extends ProjectionImpl {

  /**
   * constants from Snyder's equations
   */
  private double R, sinLat0, cosLat0, lon0Degrees;

  private double lat0, lon0; // center lat/lon in degrees
  private double falseEasting, falseNorthing;

  @Override
  public ProjectionImpl constructCopy() {
    ProjectionImpl result =  new LambertAzimuthalEqualArea(getOriginLat(), getOriginLon(), getFalseEasting(), getFalseNorthing(), R);
    result.setDefaultMapArea(defaultMapArea);
    result.setName(name);
    return result;
  }

  /**
   * Constructor with default parameters
   */
  public LambertAzimuthalEqualArea() {
    this(40.0, 100.0, 0.0, 0.0, EARTH_RADIUS);
  }

  /**
   * Construct a LambertAzimuthalEqualArea Projection.
   *
   * @param lat0 lat origin of the coord system on the projection plane
   * @param lon0 lon origin of the coord system on the projection plane
   * @throws IllegalArgumentException
   */
  public LambertAzimuthalEqualArea(double lat0, double lon0) {
    this(lat0, lon0, 0.0, 0.0, EARTH_RADIUS);
  }

  /**
   * Construct a LambertAzimuthalEqualArea Projection.
   *
   * @param lat0           lat origin of the coord system on the projection plane
   * @param lon0           lon origin of the coord system on the projection plane
   * @param false_easting  natural_x_coordinate + false_easting = x coordinate in km
   * @param false_northing natural_y_coordinate + false_northing = y coordinate in km
   * @param earthRadius    radius of the earth in km
   * @throws IllegalArgumentException if lat0, par1, par2 = +/-90 deg
   */
  public LambertAzimuthalEqualArea(double lat0, double lon0, double false_easting, double false_northing, double earthRadius) {

    super("LambertAzimuthalEqualArea", false);

    this.lat0 = Math.toRadians(lat0);
    lon0Degrees = lon0;
    this.lon0 = Math.toRadians(lon0);
    R = earthRadius;

    if (Double.isNaN(false_easting))
      false_easting = 0.0;
    if (Double.isNaN(false_northing))
      false_northing = 0.0;
    this.falseEasting = false_easting;
    this.falseNorthing = false_northing;

    precalculate();

    addParameter(CF.GRID_MAPPING_NAME, CF.LAMBERT_AZIMUTHAL_EQUAL_AREA);
    addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0);
    addParameter(CF.LONGITUDE_OF_PROJECTION_ORIGIN, lon0);
    addParameter(CF.EARTH_RADIUS, earthRadius * 1000);
    if ((false_easting != 0.0) || (false_northing != 0.0)) {
      addParameter(CF.FALSE_EASTING, false_easting);
      addParameter(CF.FALSE_NORTHING, false_northing);
      addParameter(CDM.UNITS, "km");
    }
  }

  /**
   * Precalculate some stuff
   */
  private void precalculate() {
    sinLat0 = Math.sin(lat0);
    cosLat0 = Math.cos(lat0);
    lon0Degrees = Math.toDegrees(lon0);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    LambertAzimuthalEqualArea that = (LambertAzimuthalEqualArea) o;

    if (Double.compare(that.R, R) != 0) return false;
    if (Double.compare(that.falseEasting, falseEasting) != 0) return false;
    if (Double.compare(that.falseNorthing, falseNorthing) != 0) return false;
    if (Double.compare(that.lat0, lat0) != 0) return false;
    if (Double.compare(that.lon0, lon0) != 0) return false;
    if ((defaultMapArea == null) != (that.defaultMapArea == null)) return false; // common case is that these are null
    if (defaultMapArea != null && !that.defaultMapArea.equals(defaultMapArea)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    temp = R != +0.0d ? Double.doubleToLongBits(R) : 0L;
    result = (int) (temp ^ (temp >>> 32));
    temp = lat0 != +0.0d ? Double.doubleToLongBits(lat0) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = lon0 != +0.0d ? Double.doubleToLongBits(lon0) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = falseEasting != +0.0d ? Double.doubleToLongBits(falseEasting) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = falseNorthing != +0.0d ? Double.doubleToLongBits(falseNorthing) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

// bean properties

  /**
   * Get the origin longitude.
   *
   * @return the origin longitude.
   */
  public double getOriginLon() {
    return Math.toDegrees(lon0);
  }

  /**
   * Get the origin latitude.
   *
   * @return the origin latitude.
   */
  public double getOriginLat() {
    return Math.toDegrees(lat0);
  }

  /**
   * Get the false easting, in km.
   *
   * @return the false easting.
   */
  public double getFalseEasting() {
    return falseEasting;
  }

  /**
   * Get the false northing, in km.
   *
   * @return the false northing.
   */
  public double getFalseNorthing() {
    return falseNorthing;
  }

   //////////////////////////////////////////////
  // setters for IDV serialization - do not use except for object creating

  /**
    * Set the origin longitude.
    * @param lon   the origin longitude.
    */
   public void setOriginLon(double lon) {
       lon0 = Math.toRadians(lon);
       precalculate();
   }

  /**
   * Set the origin latitude.
   *
   * @param lat   the origin latitude.
   */
  public void setOriginLat(double lat) {
      lat0 = Math.toRadians(lat);
      precalculate();
  }

  /**
   * Set the false_easting, in km.
   * natural_x_coordinate + false_easting = x coordinate
   * @param falseEasting x offset
   */
  public void setFalseEasting(double falseEasting) {
      this.falseEasting = falseEasting;
  }

  /**
   * Set the false northing, in km.
   * natural_y_coordinate + false_northing = y coordinate
   * @param falseNorthing y offset
   */
  public void setFalseNorthing(double falseNorthing) {
      this.falseNorthing = falseNorthing;
  }
   /////////////////////////////////////////////////////////////////////

  /**
   * Get the label to be used in the gui for this type of projection
   *
   * @return Type label
   */
  public String getProjectionTypeLabel() {
    return "Lambert Azimuth Equal Area";
  }

  /**
   * Create a String of the parameters.
   *
   * @return a String of the parameters
   */
  public String paramsToString() {
    return toString();
  }

  @Override
  public String toString() {
    return "LambertAzimuthalEqualArea{" +
            "falseNorthing=" + falseNorthing +
            ", falseEasting=" + falseEasting +
            ", lon0=" + lon0 +
            ", lat0=" + lat0 +
            ", R=" + R +
            '}';
  }

  /**
   * This returns true when the line between pt1 and pt2 crosses the seam.
   * When the cone is flattened, the "seam" is lon0 +- 180.
   *
   * @param pt1 point 1
   * @param pt2 point 2
   * @return true when the line between pt1 and pt2 crosses the seam.
   */
  public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) {
    // either point is infinite
    if (ProjectionPointImpl.isInfinite(pt1)
            || ProjectionPointImpl.isInfinite(pt2)) {
      return true;
    }
    // opposite signed X values, larger then 5000 km
    return (pt1.getX() * pt2.getX() < 0)
            && (Math.abs(pt1.getX() - pt2.getX()) > 5000.0);
  }

  /*MACROBODY
    latLonToProj {} {
      fromLat = Math.toRadians(fromLat);
      double lonDiff =
          Math.toRadians(LatLonPointImpl.lonNormal(fromLon-lon0Degrees));
      double g =
      sinLat0*Math.sin(fromLat) + cosLat0*Math.cos(fromLat)*Math.cos(lonDiff);

      double kPrime = Math.sqrt(2/(1 + g));
      toX = R*kPrime*Math.cos(fromLat)*Math.sin(lonDiff) + falseEasting;
      toY = R*kPrime*(cosLat0*Math.sin(fromLat) - sinLat0*Math.cos(fromLat)*Math.cos(lonDiff)) + falseNorthing;
    }

    projToLatLon {} {

      fromX = fromX - falseEasting;
      fromY = fromY - falseNorthing;
      double rho = Math.sqrt(fromX*fromX + fromY*fromY);
      double c = 2*Math.asin(rho/(2*R));
      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c)*sinLat0 + (fromY*Math.sin(c)*cosLat0/rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) { // not 90 or -90
          temp = rho*cosLat0*Math.cos(c) - fromY*sinLat0*Math.sin(c);
          toLon = lon0 + Math.atan(fromX*Math.sin(c)/temp);
        } else if (lat0 == PI_OVER_4) {
          toLon = lon0 + Math.atan(fromX/-fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX/fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat= Math.toDegrees(toLat);
      toLon= Math.toDegrees(toLon);
      if (temp < 0) toLon += 180;
      toLon= LatLonPointImpl.lonNormal(toLon);
    }

  MACROBODY*/

  /*BEGINGENERATED*/

  /*
  Note this section has been generated using the convert.tcl script.
  This script, run as:
  tcl convert.tcl LambertAzimuthalEqualArea.java
  takes the actual projection conversion code defined in the MACROBODY
  section above and generates the following 6 methods
  */

  /**
   * Convert a LatLonPoint to projection coordinates
   *
   * @param latLon convert from these lat, lon coordinates
   * @param result the object to write to
   * @return the given result
   */
  public ProjectionPoint latLonToProj(LatLonPoint latLon,
                                      ProjectionPointImpl result) {
    double toX, toY;
    double fromLat = latLon.getLatitude();
    double fromLon = latLon.getLongitude();

    fromLat = Math.toRadians(fromLat);
    double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
            - lon0Degrees));
    double g = sinLat0 * Math.sin(fromLat)
            + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);

    double kPrime = Math.sqrt(2 / (1 + g));
    toX = R * kPrime * Math.cos(fromLat) * Math.sin(lonDiff)
            + falseEasting;
    toY = R * kPrime
            * (cosLat0 * Math.sin(fromLat)
            - sinLat0 * Math.cos(fromLat)
            * Math.cos(lonDiff)) + falseNorthing;

    result.setLocation(toX, toY);
    return result;
  }

  /**
   * Convert projection coordinates to a LatLonPoint
   * Note: a new object is not created on each call for the return value.
   *
   * @param world  convert from these projection coordinates
   * @param result the object to write to
   * @return LatLonPoint convert to these lat/lon coordinates
   */
  public LatLonPoint projToLatLon(ProjectionPoint world,
                                  LatLonPointImpl result) {
    double toLat, toLon;
    double fromX = world.getX();
    double fromY = world.getY();

    fromX = fromX - falseEasting;
    fromY = fromY - falseNorthing;
    double rho = Math.sqrt(fromX * fromX + fromY * fromY);
    double c = 2 * Math.asin(rho / (2 * R));
    toLon = lon0;
    double temp = 0;
    if (Math.abs(rho) > TOLERANCE) {
      toLat = Math.asin(Math.cos(c) * sinLat0
              + (fromY * Math.sin(c) * cosLat0 / rho));
      if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
        temp = rho * cosLat0 * Math.cos(c)
                - fromY * sinLat0 * Math.sin(c);
        toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
      } else if (Double.compare(lat0, PI_OVER_4) == 0) {
        toLon = lon0 + Math.atan(fromX / -fromY);
        temp = -fromY;
      } else {
        toLon = lon0 + Math.atan(fromX / fromY);
        temp = fromY;
      }
    } else {
      toLat = lat0;
    }
    toLat = Math.toDegrees(toLat);
    toLon = Math.toDegrees(toLon);
    if (temp < 0) {
      toLon += 180;
    }
    toLon = LatLonPointImpl.lonNormal(toLon);

    result.setLatitude(toLat);
    result.setLongitude(toLon);
    return result;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public float[][] latLonToProj(float[][] from, float[][] to, int latIndex,
                                int lonIndex) {
    int cnt = from[0].length;
    float[] fromLatA = from[latIndex];
    float[] fromLonA = from[lonIndex];
    float[] resultXA = to[INDEX_X];
    float[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double g = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);

      double kPrime = Math.sqrt(2 / (1 + g));
      toX = R * kPrime * Math.cos(fromLat) * Math.sin(lonDiff)
              + falseEasting;
      toY = R * kPrime
              * (cosLat0 * Math.sin(fromLat)
              - sinLat0 * Math.cos(fromLat)
              * Math.cos(lonDiff)) + falseNorthing;

      resultXA[i] = (float) toX;
      resultYA[i] = (float) toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public float[][] projToLatLon(float[][] from, float[][] to) {
    int cnt = from[0].length;
    float[] fromXA = from[INDEX_X];
    float[] fromYA = from[INDEX_Y];
    float[] toLatA = to[INDEX_LAT];
    float[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      fromX = fromX - falseEasting;
      fromY = fromY - falseNorthing;
      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double c = 2 * Math.asin(rho / (2 * R));
      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = (float) toLat;
      toLonA[i] = (float) toLon;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public double[][] latLonToProj(double[][] from, double[][] to,
                                 int latIndex, int lonIndex) {
    int cnt = from[0].length;
    double[] fromLatA = from[latIndex];
    double[] fromLonA = from[lonIndex];
    double[] resultXA = to[INDEX_X];
    double[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double g = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);

      double kPrime = Math.sqrt(2 / (1 + g));
      toX = R * kPrime * Math.cos(fromLat) * Math.sin(lonDiff)
              + falseEasting;
      toY = R * kPrime
              * (cosLat0 * Math.sin(fromLat)
              - sinLat0 * Math.cos(fromLat)
              * Math.cos(lonDiff)) + falseNorthing;

      resultXA[i] = toX;
      resultYA[i] = toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public double[][] projToLatLon(double[][] from, double[][] to) {
    int cnt = from[0].length;
    double[] fromXA = from[INDEX_X];
    double[] fromYA = from[INDEX_Y];
    double[] toLatA = to[INDEX_LAT];
    double[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      fromX = fromX - falseEasting;
      fromY = fromY - falseNorthing;
      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double c = 2 * Math.asin(rho / (2 * R));
      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = toLat;
      toLonA[i] = toLon;
    }
    return to;
  }

  /*ENDGENERATED*/

  /**
   * Test
   *
   * @param args not used
   */
  public static void main(String[] args) {
    LambertAzimuthalEqualArea a = new LambertAzimuthalEqualArea(40, -100);
    ProjectionPoint p = a.latLonToProj(-20, 100);
    System.out.println("proj point = " + p);
    LatLonPoint ll = a.projToLatLon(p);
    System.out.println("ll = " + ll);
  }
}


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

package ucar.nc2.iosp.cinrad;

import org.joda.time.DateTime;
import ucar.unidata.io.RandomAccessFile;
import ucar.ma2.IndexIterator;
import ucar.ma2.Range;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;

/**
 * This class reads one record (radial) in an CINRAD level II file.
 * File must be uncompressed.
 * Not handling messages yet, only data.
 * <p>
 * 10/16/05: Now returns data as a byte, so use scale and offset.
 *
 * Adapted with permission from the Java Iras software developed by David Priegnitz at NSSL.
 *
 * @author caron
 * @author David Priegnitz
 * @version $Revision:63 $ $Date:2006-07-12 21:50:51Z $
 */
public class Cinrad2Record {

  /** Reflectivity moment identifier */
  public static final int REFLECTIVITY = 1;

  /** Radial Velocity moment identifier */
  public static final int VELOCITY_HI = 2;

  /** Radial Velocity moment identifier */
  public static final int VELOCITY_LOW = 4;

  /** Sprectrum Width moment identifier */
  public static final int SPECTRUM_WIDTH = 3;

  /** Low doppler resolution code */
  public static final int DOPPLER_RESOLUTION_LOW_CODE = 4;

  /** High doppler resolution code */
  public static final int DOPPLER_RESOLUTION_HIGH_CODE = 2;

  /** Horizontal beam width */
  public static final float HORIZONTAL_BEAM_WIDTH = (float) 1.5;         // LOOK

  // Lookup Tables

  /** Initialization flag for lookup tables
  public static int data_lut_init_flag = 0;

  /** Reflectivity look up table
  public static float[] Reflectivity_LUT = new float[256];

  /** 1 km Velocity look up table
  public static float[] Velocity_1km_LUT = new float[256];

  /** 1/2 km Velocity look up table
  public static float[] Velocity_hkm_LUT = new float[256];

  static {
    Reflectivity_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
    Reflectivity_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;
    Velocity_1km_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
    Velocity_1km_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;
    Velocity_hkm_LUT[0] = 0.0f; // Float.NaN;  //(float) SIGNAL_BELOW_THRESHOLD;
    Velocity_hkm_LUT[1] = Float.NaN;  //(float) SIGNAL_OVERLAID;

    for (int i = 2; i < 256; i++) {
      Reflectivity_LUT[i] = (float) (i / 2.0 - 33.0);
      Velocity_1km_LUT[i] = (float) (i - 129.0);
      Velocity_hkm_LUT[i] = (float) (i / 2.0 - 64.5); // also spectrum width
    }
  } */

  public static byte MISSING_DATA = (byte) 1;
  public static final byte BELOW_THRESHOLD = (byte) 0;

  /** Size of the file header, aka title */
  static int FILE_HEADER_SIZE = 0;

  /** Size of the CTM record header */
  private static int CTM_HEADER_SIZE = 14;

  /** Size of the the message header, to start of the data message */
  private static final int MESSAGE_HEADER_SIZE = 28;

  /** Size of the entire message, if its a radar data message */
  private static int RADAR_DATA_SIZE = 2432;

  static public String getDatatypeName( int datatype) {
    switch (datatype) {
      case REFLECTIVITY : return "Reflectivity";
      case VELOCITY_HI :
      case VELOCITY_LOW : return "RadialVelocity";
      case SPECTRUM_WIDTH : return "SpectrumWidth";
      default : throw new IllegalArgumentException();
    }
  }

   static public String getDatatypeUnits(int datatype) {
     switch (datatype) {
       case REFLECTIVITY :
           return "dBz";

       case VELOCITY_HI :
       case VELOCITY_LOW :
       case SPECTRUM_WIDTH :
           return "m/s";
     }
     throw new IllegalArgumentException();
   }

  static public float getDatatypeScaleFactor(int datatype) {
    switch (datatype) {
      case REFLECTIVITY:
        if(Cinrad2IOServiceProvider.isCC)
          return 0.1f;
        if(Cinrad2IOServiceProvider.isCC20)
          return 0.5f;
        else
          return 0.5f;
      case VELOCITY_LOW :
        if(Cinrad2IOServiceProvider.isSC)
          return 0.3673f;
        else if(Cinrad2IOServiceProvider.isCC)
          return 0.1f;
        else
          return 1.0f;
      case VELOCITY_HI:
      case SPECTRUM_WIDTH :
        if(Cinrad2IOServiceProvider.isSC)
          return 0.1822f;
        else if(Cinrad2IOServiceProvider.isCC)
          return 0.1f;
        else if(Cinrad2IOServiceProvider.isCC20)
          return 1.0f;
        else
          return 0.5f;
      default : throw new IllegalArgumentException();
    }
  }

  static public float getDatatypeAddOffset(int datatype) {
    switch (datatype) {
      case REFLECTIVITY :
        if(Cinrad2IOServiceProvider.isSC)
          return -32.0f;
        else if (Cinrad2IOServiceProvider.isCC)
          return 0.0f;
        else if (Cinrad2IOServiceProvider.isCC20)
          return -32.0f;
        else
          return -33.0f;
      case VELOCITY_LOW :
        if(Cinrad2IOServiceProvider.isSC)
          return 0.0f;
        else if(Cinrad2IOServiceProvider.isCC)
          return 0.0f;
        else if (Cinrad2IOServiceProvider.isCC20)
          return 0.0f;
        else
          return -129.0f;
      case VELOCITY_HI:
      case SPECTRUM_WIDTH :
          if(Cinrad2IOServiceProvider.isSC)
            return 0.0f;
          else if(Cinrad2IOServiceProvider.isCC)
            return 0.0f;
          else if (Cinrad2IOServiceProvider.isCC20)
            return 0.0f;
          else
            return -64.5f;
      default : throw new IllegalArgumentException();
    }
  }

  static public String getMessageTypeName( int code) {
    switch (code) {
      case 1 : return "digital radar data";
      case 2 : return "RDA status data";
      case 3 : return "performance/maintainence data";
      case 4 : return "console message - RDA to RPG";
      case 5 : return "maintainence log data";
      case 6 : return "RDA control ocmmands";
      case 7 : return "volume coverage pattern";
      case 8 : return "clutter censor zones";
      case 9 : return "request for data";
      case 10 : return "console message - RPG to RDA";
      case 11 : return "loop back test - RDA to RPG";
      case 12 : return "loop back test - RPG to RDA";
      case 13 : return "clutter filter bypass map - RDA to RPG";
      case 14 : return "edited clutter filter bypass map - RDA to RPG";
      case 15: return "Notchwidth Map";
      case 18: return "RDA Adaptation data";
      default : return "unknown "+code;
    }
  }

  static public String getRadialStatusName( int code) {
    switch (code) {
      case 0 : return "start of new elevation";
      case 1 : return "intermediate radial";
      case 2 : return "end of elevation";
      case 3 : return "begin volume scan";
      case 4 : return "end volume scan";
      default : return "unknown "+code;
    }
  }

  static public String getVolumeCoveragePatternName( int code) {
    switch (code) {
      case 11 : return "16 elevation scans every 5 mins";
      case 12 : return "14 elevation scan every 4.1 mins";
      case 21 : return "11 elevation scans every 6 mins";
      case 31 : return "8 elevation scans every 10 mins";
      case 32 : return "7 elevation scans every 10 mins";
      case 121: return "9 elevations, 20 scans every 5 minutes";
      default : return "unknown "+code;
    }
  }

  static public java.util.Date getDate(int julianDays, int msecs) {
    long total = ((long) (julianDays - 1)) * 24 * 3600 * 1000 + msecs;
    return new Date( total);
  }

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

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

  int recno;        //  record number within the file
  long message_offset; // offset of start of message
  boolean hasReflectData, hasDopplerData;

  // message header
  short message_size = 0;
  byte id_channel = 0;
  public byte message_type = 0;
  short id_sequence = 0;
  short mess_julian_date = 0;
  int mess_msecs = 0;
  short seg_count = 0;
  short seg_number = 0;

  // radar data header
  int data_msecs = 0;
  short data_julian_date = 0;
  short unamb_range = 0;
  int azimuth_ang = 0;
  int azimuth_ang_end = 0;
  short radial_num = 0; // radial number within the elevation : starts with one
  short radial_status = 0;
  short elevation_ang = 0;
  short elevation_ang_end = 0;
  short elevation_num = 0;

  short reflect_first_gate = 0; // distance to first reflectivity gate (m)
  short reflect_gate_size = 0; //  reflectivity gate size (m)
  short reflect_gate_count = 0; //  number of reflectivity gates

  short doppler_first_gate = 0; // distance to first reflectivity gate (m)
  short doppler_gate_size = 0; //  reflectivity gate size (m)
  short doppler_gate_count = 0; //  number of reflectivity gates

  short cut = 0;
  float calibration = 0; // system gain calibration constant (db biased)
  short resolution = 0; // dopplar velocity resolution
  short vcp = 0;        // volume coverage pattern

  short nyquist_vel; // nyquist velocity
  short attenuation; // atmospheric attenuation factor
  short threshhold; // threshhold paramter for minimum difference

  private short reflect_offset; // reflectivity data pointer (byte number from start of message)
  private short velocity_offset; // velocity data pointer (byte number from start of message)
  private short spectWidth_offset; // spectrum-width data pointer (byte number from start of message)

  public DateTime dateTime0;
  public DateTime dateTimeE;
  public SweepInfo [] sweepInfo;
  public static Cinrad2Record factory(RandomAccessFile din, int record) throws IOException {
    long offset = (long)record * RADAR_DATA_SIZE + FILE_HEADER_SIZE;
    if (offset >= din.length())
      return null;
    else
      return new Cinrad2Record(din, record);
  }

  public short convertunsignedByte2Short(byte b) {
      return (short)((b<0)? (short)b + 256 : (short)b);
   }

  public void readSCHeader(RandomAccessFile din) throws IOException {

    message_offset = 0;
    din.seek(message_offset);
// site info 170
    din.skipBytes(90);
    byte[] b10 = new byte[10];
    // Message Header
    din.read(b10);
    String stationNId = new String(b10);

    din.skipBytes(52);

    // latlon
    int lon = din.readInt();
    int lat = din.readInt();
    int hhh = din.readInt();

    din.skipBytes(6);
    //
    message_type = 1;

    //PerformanceInfo
    din.skipBytes(31);

    //ObservationInfo
    vcp = convertunsignedByte2Short(din.readByte());
    short syear = (short)din.readUnsignedShort();
    short smm = convertunsignedByte2Short(din.readByte());
    short sdd = convertunsignedByte2Short(din.readByte());
    short shh = convertunsignedByte2Short(din.readByte());
    short smi = convertunsignedByte2Short(din.readByte());
    short sss = convertunsignedByte2Short(din.readByte());

    dateTime0 = new DateTime(syear,smm,sdd,shh,smi,sss);

    din.skipBytes(8);
    long offset = din.getFilePointer();
    sweepInfo = new SweepInfo[30];
    for(int i = 0; i < 30; i++){
      sweepInfo[i] = new SweepInfo(din, (int)offset);
      offset = offset + 21;
    }

    din.skipBytes(6);

    syear = (short)din.readUnsignedShort();
    smm = convertunsignedByte2Short(din.readByte());
    sdd = convertunsignedByte2Short(din.readByte());
    shh = convertunsignedByte2Short(din.readByte());
    smi = convertunsignedByte2Short(din.readByte());
    sss = convertunsignedByte2Short(din.readByte());

    dateTimeE = new DateTime(syear,smm,sdd,shh,smi,sss);

  }

  int sweepN = 1;
  int echoType;
  int [] elev;
  int [] recordNum;
  byte cDataForm;
  public void readCCHeader(RandomAccessFile din) throws IOException {

    message_offset = 0;
    din.seek(message_offset);
// site info 170
    din.skipBytes(66);

    // Message Header
    String stationId = din.readString(40);
    String stationNbr = din.readString(10);

    din.skipBytes(20);

    String clon = din.readString(16);
    String clat = din.readString(16);
    // latlon
    int lon = (int)din.readInt();
    int lat = (int)din.readInt();
    int hhh = (int)din.readInt();

    din.skipBytes(4);

    //ObservationInfo

    short syear1 = convertunsignedByte2Short(din.readByte());
    short syear2 = convertunsignedByte2Short(din.readByte());
    short syear = (short)(syear1 * 100 + syear2);
    short smm = convertunsignedByte2Short(din.readByte());
    short sdd = convertunsignedByte2Short(din.readByte());
    short shh = convertunsignedByte2Short(din.readByte());
    short smi = convertunsignedByte2Short(din.readByte());
    short sss = convertunsignedByte2Short(din.readByte());

    dateTime0 = new DateTime(syear,smm,sdd,shh,smi,sss);

    din.skipBytes(1);

    syear1 = convertunsignedByte2Short(din.readByte());
    syear2 = convertunsignedByte2Short(din.readByte());
    syear = (short)(syear1 * 100 + syear2);
    smm = convertunsignedByte2Short(din.readByte());
    sdd = convertunsignedByte2Short(din.readByte());
    shh = convertunsignedByte2Short(din.readByte());
    smi = convertunsignedByte2Short(din.readByte());
    sss = convertunsignedByte2Short(din.readByte());

    dateTimeE = new DateTime(syear,smm,sdd,shh,smi,sss);
    short scanMode = convertunsignedByte2Short(din.readByte());
    if(scanMode == 10) {
      sweepN = 1;
    } else if(scanMode >= 100){
      sweepN = scanMode - 100;
    } else {
      throw new IOException("Error reading CINRAD CC data: Unsupported product: RHI/FFT");
    }

    elev = new int[sweepN];
    din.skipBytes(4);
    short sRHIA= (short)din.readUnsignedShort();

    din.skipBytes(4);

    echoType = din.readUnsignedShort();
    if(echoType != 0x408a) //only support vppi at this moment
      throw new IOException("Error reading CINRAD CC data: Unsupported level 2 data");

    int prodCode = din.readUnsignedShort();

    if(prodCode != 0x8003) //only support vppi at this moment
      throw new IOException("Error reading CINRAD CC data: Unsupported product: RHI/FFT");
    din.skipBytes(4);

    //remain2[660]
    for(int i = 0; i < sweepN; i++) {
      int maxV =  din.readUnsignedShort();
      int maxL =  din.readUnsignedShort();
      int binWidth =  din.readUnsignedShort();
      int binNum =   din.readUnsignedShort();
      int recordTotalNum = din.readUnsignedShort();
      din.skipBytes(8);
      elev[i] = din.readUnsignedShort();
      din.skipBytes(2);
      //System.out.println("bin num: " + binNum + " maxL " + maxL + " totalRNumber " + recordTotalNum);
    }
    //din.seek(1020);
   // int doffset = din.readInt();
    //System.out.println(" Offset: " + doffset);
  }

  public void readCC20Header(RandomAccessFile din) throws IOException {

    message_offset = 0;
    din.seek(message_offset);
// site info 170
    din.skipBytes(62);

    // Message Header
    String stationId = din.readString(40);
    String stationNbr = din.readString(10);

    din.skipBytes(20);

    String clon = din.readString(16);
    String clat = din.readString(16);
    // latlon
    int lon = (int)din.readInt();
    int lat = (int)din.readInt();
    int hhh = (int)din.readInt();

    din.skipBytes(40);

    //ObservationInfo
    short scanMode = convertunsignedByte2Short(din.readByte());
    if(scanMode == 10) {
      sweepN = 1;
    } else if(scanMode >= 100){
      sweepN = scanMode - 100;
    } else {
      throw new IOException("Error reading CINRAD CC data: Unsupported product: RHI/FFT");
    }

    short syear = (short)din.readUnsignedShort();
    short smm = convertunsignedByte2Short(din.readByte());
    short sdd = convertunsignedByte2Short(din.readByte());
    short shh = convertunsignedByte2Short(din.readByte());
    short smi = convertunsignedByte2Short(din.readByte());
    short sss = convertunsignedByte2Short(din.readByte());

    dateTime0 = new DateTime(syear,smm,sdd,shh,smi,sss);

    din.skipBytes(14); // 14+ (35 - 21)

    //remain2[660]
    elev = new int[sweepN];
    recordNum = new int[sweepN];
    for(int i = 0; i < sweepN; i++) {
      din.skipBytes(14);
      int zbinWidth =  din.readUnsignedShort();
      int vbinWidth =  din.readUnsignedShort();
      int sbinWidth =  din.readUnsignedShort();
      int zbinNum =   din.readUnsignedShort();
      int vbinNum =   din.readUnsignedShort();
      int sbinNum =   din.readUnsignedShort();
      recordNum[i] = din.readUnsignedShort();
      //if(i > 0)
      //  recordNum[i] = recordNum[i] + recordNum[i-1];
      elev[i] = din.readShort();
      cDataForm = din.readByte();
      if(cDataForm != 22 && cDataForm != 23 && cDataForm != 24)
        throw new IOException("Unsupported CC data format");
      int dataP = din.readInt();
      //din.skipBytes(2);
      //System.out.println("zbin num: " + zbinNum + " vbin num: " + vbinNum + " sbin num: " + sbinNum + " dataForm " + cDataForm);
    }

    for(int i = sweepN; i < 32; i++) {
      din.skipBytes(35);
    }

    din.skipBytes(6);

    syear = (short)din.readUnsignedShort();
    smm = convertunsignedByte2Short(din.readByte());
    sdd = convertunsignedByte2Short(din.readByte());
    shh = convertunsignedByte2Short(din.readByte());
    smi = convertunsignedByte2Short(din.readByte());
    sss = convertunsignedByte2Short(din.readByte());

    dateTimeE = new DateTime(syear,smm,sdd,shh,smi,sss);

  }

  public Cinrad2Record(RandomAccessFile din, int record) throws IOException {
    if(!Cinrad2IOServiceProvider.isSC && !Cinrad2IOServiceProvider.isCC
            && !Cinrad2IOServiceProvider.isCC20) {
      //CINRAD SA/SB
      this.recno = record;
      message_offset = (long) record * RADAR_DATA_SIZE + FILE_HEADER_SIZE;

      din.seek(message_offset);

      din.skipBytes(CTM_HEADER_SIZE);
      //byte[] b2 = new byte[2];
      // Message Header
      //din.read(b2);
      //
      // b2[0] = din.readByte();
      //  b2[1] = din.readByte();
      //  message_size = (short)getUInt(b2, 2);
      //message_size  = (short)din.readShort(); // size in "halfwords" = 2 bytes

      //id_channel    = din.readByte(); // channel id
      message_type = din.readByte();
      //id_sequence   = din.readShort();
      //skip 2 byte
      din.skipBytes(13);
      //mess_msecs    = din.readInt();   // message generation time
      //mess_julian_date   = din.readShort(); // from 1/1/70; prob "message generation time"

      // seg_count     = din.readShort(); // number of message segments
      //seg_number    = din.readShort(); // this segment

      //dumpMessage(System.out, dd);
      if (message_type != 1) return;

      // data header
      byte[] b4 = din.readBytes(4);
      data_msecs = bytesToInt(b4, true); //din.readInt();   // collection time for this radial, msecs since midnight
      byte[] b2 = din.readBytes(2);
      data_julian_date = (short) bytesToShort(b2, true); //din.readShort(); // prob "collection time"
      unamb_range = din.readShort(); // unambiguous range
      azimuth_ang = din.readUnsignedShort(); // LOOK why unsigned ??
      radial_num = din.readShort(); // radial number within the elevation
      radial_status = din.readShort();
      elevation_ang = din.readShort();
      elevation_num = din.readShort(); // RDA elevation number
      reflect_first_gate = din.readShort(); // range to first gate of reflectivity (m) may be negetive
      doppler_first_gate = din.readShort(); // range to first gate of dopplar (m) may be negetive
      reflect_gate_size = din.readShort(); // reflectivity data gate size (m)
      doppler_gate_size = din.readShort(); // dopplar data gate size (m)
      reflect_gate_count = din.readShort(); // number of reflectivity gates
      doppler_gate_count = din.readShort(); // number of velocity or spectrum width gates
      if (record == 0) {
        if (reflect_gate_count == 1000 && doppler_gate_count == 1000)
          RADAR_DATA_SIZE = 3132;
        else if (reflect_gate_count == 800 || doppler_gate_count == 1600)
          RADAR_DATA_SIZE = 4132;
        else
          RADAR_DATA_SIZE = 2432;
      }
      din.skipBytes(6);
      //cut           = din.readShort(); // sector number within cut
      //calibration   = din.readFloat(); // system gain calibration constant (db biased)
      reflect_offset = din.readShort(); // reflectivity data pointer (byte number from start of message)
      velocity_offset = din.readShort(); // velocity data pointer (byte number from start of message)
      spectWidth_offset = din.readShort(); // spectrum-width data pointer (byte number from start of message)
      resolution = din.readShort(); // dopplar velocity resolution
      vcp = din.readShort(); // volume coverage pattern
      message_type = 1;
      din.skipBytes(14);

      nyquist_vel = din.readShort(); // nyquist velocity
      din.skipBytes(38);
      //attenuation   = din.readShort(); // atmospheric attenuation factor
      //threshhold    = din.readShort(); // threshhold paramter for minimum difference

      hasReflectData = (reflect_gate_count > 0);
      hasDopplerData = (doppler_gate_count > 0);
      //  dump(System.out);
    } else if(Cinrad2IOServiceProvider.isSC) {
      //CINRAD SC
      this.recno = record;
      // read header for every record
      readSCHeader(din);
      RADAR_DATA_SIZE = 4000;
        //FILE_HEADER_SIZE = 1024;

      message_offset = (long) record * RADAR_DATA_SIZE + 1024;
      if (message_offset >= din.length())
        return;
      din.seek(message_offset);
      //System.out.println("record = " + record);
      azimuth_ang = din.readUnsignedShort(); // LOOK why unsigned ??
      elevation_ang = (short)din.readUnsignedShort();
      azimuth_ang_end = din.readUnsignedShort(); // LOOK why unsigned ??
      elevation_ang_end = (short)din.readUnsignedShort();
      radial_num = (short)(record % 360 + 1); // radial number within the elevation

      elevation_num =(short)((record/360) + 1); // RDA elevation number
      reflect_first_gate = 300; // range to first gate of reflectivity (m) may be negetive
      doppler_first_gate = 300; // range to first gate of dopplar (m) may be negetive
      reflect_gate_size = 300; // reflectivity data gate size (m)
      doppler_gate_size = 300; // dopplar data gate size (m)
      reflect_gate_count = 998; // number of reflectivity gates
      doppler_gate_count = 998; // number of velocity or spectrum width gates
      //vcp = sweepInfo[0].svcp;
      //cut           = din.readShort(); // sector number within cut
      //calibration   = din.readFloat(); // system gain calibration constant (db biased)
      reflect_offset =   8; // reflectivity data pointer (byte number from start of message)
      velocity_offset =  8; // 9 velocity data pointer (byte number from start of message)
      spectWidth_offset = 8; // 11 spectrum-width data pointer (byte number from start of message)
      //resolution = din.readShort(); // dopplar velocity resolution

      hasReflectData = (reflect_gate_count > 0);
      hasDopplerData = (doppler_gate_count > 0);
    } else if(Cinrad2IOServiceProvider.isCC){
      //cinrad CC
      this.recno = record;
      // read header for every record
      //if(record == 0)
        readCCHeader(din);
      RADAR_DATA_SIZE = 3000;
      //FILE_HEADER_SIZE = 1021;
      message_type = 1;
      message_offset = (long) record * RADAR_DATA_SIZE + 1024;
      if (message_offset >= din.length())
        return;
      din.seek(message_offset);
      //System.out.println("record = " + record);
      radial_num = (short)(record % 512 + 1); // radial number within the elevation
      azimuth_ang = radial_num;

      elevation_num =(short)((record/512) + 1); // RDA elevation number
      //System.out.println("elevation num: " + elevation_num + " record " + record + " radial " + radial_num);
      if(elevation_num > sweepN)
        elevation_num = (short)sweepN;
      elevation_ang = (short)elev[elevation_num - 1];
      reflect_first_gate = 300; // range to first gate of reflectivity (m) may be negetive
      doppler_first_gate = 300; // range to first gate of dopplar (m) may be negetive
      reflect_gate_size = 150; // reflectivity data gate size (m)
      doppler_gate_size = 150; // dopplar data gate size (m)
      reflect_gate_count = 500; // number of reflectivity gates
      doppler_gate_count = 500; // number of velocity or spectrum width gates
      //vcp = sweepInfo[0].svcp;
      //cut           = din.readShort(); // sector number within cut
      //calibration   = din.readFloat(); // system gain calibration constant (db biased)
      reflect_offset =   0; // reflectivity data pointer (byte number from start of message)
      velocity_offset =  1000; // 9 velocity data pointer (byte number from start of message)
      spectWidth_offset = 2000; // 11 spectrum-width data pointer (byte number from start of message)
      //resolution = din.readShort(); // dopplar velocity resolution

      hasReflectData = (reflect_gate_count > 0);
      hasDopplerData = (doppler_gate_count > 0);
    } else if(Cinrad2IOServiceProvider.isCC20){
      //cinrad CC
      this.recno = record;
      // read header for every record
      // if(record == 0)
      readCC20Header(din);

      //if(record > recordNum[recordNum.length-1]-1)
      //  return;
      if(cDataForm == 24)
        RADAR_DATA_SIZE = 4011;
      else
        RADAR_DATA_SIZE = 3011;
      //FILE_HEADER_SIZE = 1021;
      message_type = 1;
      message_offset = (long) record * RADAR_DATA_SIZE + 2060;
      if (message_offset >= din.length())
        return;
      din.seek(message_offset);
      //System.out.println("record = " + record);
      elevation_ang =din.readShort(); // RDA elevation number
      azimuth_ang = din.readUnsignedShort();

      din.skipBytes(3);
      data_msecs = din.readInt();
     /* if (record < recordNum[0]) {
        radial_num = (short) (record +1);
        elevation_num = 0;
      } else {
        for (int i = 1; i < sweepN; i++) {
          if (record >= recordNum[i - 1] && record < recordNum[i]) {
            radial_num = (short) (record - recordNum[i - 1] + 1);
            elevation_num = (short) i;
            continue;
          }
        }
      }
      */
      //System.out.println(elevation_ang + " and " +azimuth_ang + " and " +record + " and " + data_msecs );
      reflect_first_gate = 0; // range to first gate of reflectivity (m) may be negetive
      doppler_first_gate = 0; // range to first gate of dopplar (m) may be negetive
      reflect_gate_size = 1500; // reflectivity data gate size (m)
      doppler_gate_size = 1500; // dopplar data gate size (m)
      reflect_gate_count = 1000; // number of reflectivity gates
      doppler_gate_count = 1000; // number of velocity or spectrum width gates
      //vcp = sweepInfo[0].svcp;
      //cut           = din.readShort(); // sector number within cut
      //calibration   = din.readFloat(); // system gain calibration constant (db biased)
      if(cDataForm == 24) {
        reflect_offset = 11; // reflectivity data pointer (byte number from start of message)
        velocity_offset = 2011; // 9 velocity data pointer (byte number from start of message)
        spectWidth_offset = 3011;
      } else {
        reflect_offset = 11; // reflectivity data pointer (byte number from start of message)
        velocity_offset = 1011; // 9 velocity data pointer (byte number from start of message)
        spectWidth_offset = 2011;
      }
      // 11 spectrum-width data pointer (byte number from start of message)
      //resolution = din.readShort(); // dopplar velocity resolution

      hasReflectData = (reflect_gate_count > 0);
      hasDopplerData = (doppler_gate_count > 0);
    }

  }

  public int findClosestIdx(int [] numbers, short myNumber){
    int distance = Math.abs(numbers[0] - myNumber);
    int idx = 0;
    for(int c = 1; c < numbers.length; c++){
      int cdistance = Math.abs(numbers[c] - myNumber);
      if(cdistance < distance){
        idx = c;
        distance = cdistance;
      }
    }
    return idx;
  }
    public static int bytesToInt(byte [] bytes, boolean swapBytes) {
       byte a = bytes[0];
       byte b = bytes[1];
       byte c = bytes[2];
       byte d = bytes[3];
       if (swapBytes) {
           return ((a & 0xff) ) +
               ((b & 0xff) << 8 ) +
               ((c & 0xff) << 16 ) +
               ((d & 0xff) << 24);
       } else {
           return ((a & 0xff) << 24 ) +
               ((b & 0xff) << 16 ) +
               ((c & 0xff) << 8 ) +
               ((d & 0xff) );
       }
    }

    public static int bytesToShort(byte [] bytes, boolean swapBytes) {
       byte a = bytes[0];
       byte b = bytes[1];

       if (swapBytes) {
           return ((a & 0xff) ) +
               ((b & 0xff) << 8 );

       } else {
           return ((a & 0xff) << 24 ) +
               ((b & 0xff) << 16 );

       }
    }

  public void dumpMessage(PrintStream out, java.util.Date d) {
    out.println(recno+" ---------------------");
    out.println(" message type = "+getMessageTypeName(message_type)+" ("+message_type+")");
    out.println(" message size = "+message_size+" segment="+seg_number+"/"+seg_count);
    out.println(" message date = "+ d.toString());
    out.println(" channel id = "+ id_channel );
  }

  public void dump(PrintStream out) {
    out.println(recno+" ------------------------------------------"+message_offset);
    out.println(" message type = "+getMessageTypeName(message_type));
    out.println(" data date = "+ getDate().toString());

    out.println(" elevation = "+getElevation()+" ("+elevation_num+")");
    out.println(" azimuth = "+getAzimuth());
    out.println(" radial = "+radial_num+" status= "+getRadialStatusName( radial_status)+
        " ratio = "+getAzimuth()/radial_num);
    out.println(" reflectivity first= "+reflect_first_gate+" size= "+reflect_gate_size+" count= "+reflect_gate_count);
    out.println(" doppler first= "+doppler_first_gate+" size= "+doppler_gate_size+" count= "+doppler_gate_count);
    out.println(" offset: reflect= "+reflect_offset+" velocity= "+velocity_offset+" spWidth= "+spectWidth_offset);
    out.println(" pattern = "+vcp );
  }

  public void dump2(PrintStream out) {
    out.println(recno+"= "+elevation_num+" size = "+message_size);
  }

  public boolean checkOk() {
    boolean ok = true;

    if (message_type != 1) return ok;

////    if ((seg_count != 1) || (seg_number != 1)) {
//      logger.warn("*** segment = "+seg_number+"/"+seg_count+who());
 //   }

    if ((reflect_offset < 0) || (reflect_offset > RADAR_DATA_SIZE)) {
      logger.warn("****"+recno+ " HAS bad reflect offset= "+reflect_offset+who());
      ok = false;
    }

    if ((velocity_offset < 0) || (velocity_offset > RADAR_DATA_SIZE)) {
      logger.warn("****"+recno+ " HAS bad velocity offset= "+velocity_offset+who());
      ok = false;
    }

    if ((spectWidth_offset < 0) || (spectWidth_offset > RADAR_DATA_SIZE)) {
      logger.warn("****"+recno+ " HAS bad spwidth offset= "+spectWidth_offset+who());
      ok = false;
    }

    if ((velocity_offset > 0) && (spectWidth_offset <= 0)) {
      logger.warn("****"+recno+ " HAS velocity NOT spectWidth!!"+who());
      ok = false;
    }

    if ((velocity_offset <= 0) && (spectWidth_offset > 0)) {
      logger.warn("****"+recno+ " HAS spectWidth AND NOT velocity!!"+who());
      ok = false;
    }

//    if (mess_julian_date != data_julian_date) {
//      logger.warn("*** message date = "+mess_julian_date+" : "+mess_msecs+who()+"\n"+
 //        " data date = "+data_julian_date+" : "+data_msecs);
//      ok = false;
//    }

    if (!hasReflectData && !hasDopplerData) {
      logger.info("*** no reflect or dopplar = "+who());
    }

    return ok;
  }

  private String who() { return " message("+recno +" "+ message_offset+")"; }

  /**
   * Get the azimuth in degrees
   *
   * @return   azimuth angle in degrees 0 = true north, 90 = east
   */
  public float getAzimuth() {
    if (message_type != 1) return -1.0f;

    if(Cinrad2IOServiceProvider.isSC)
      return 360.0f * azimuth_ang / 65536.0f;
    else if(Cinrad2IOServiceProvider.isCC)
      return 360.0f * azimuth_ang / 512.0f;
    else if(Cinrad2IOServiceProvider.isCC20)
      return  azimuth_ang * 0.01f;

    return 180.0f * azimuth_ang / 32768.0f;
  }

    /**
     * Get the elevation angle in degrees
     *
     * @return  elevation angle in degrees 0 = parellel to pedestal base, 90 = perpendicular
     */
    public float getElevation() {
      if (message_type != 1) return -1.0f;
      if(Cinrad2IOServiceProvider.isSC)
        return 120.0f * elevation_ang / 65536.0f;
      else if(Cinrad2IOServiceProvider.isCC)
        return elevation_ang * 0.01f;
      else if(Cinrad2IOServiceProvider.isCC20)
        return elevation_ang * 0.01f;

      return 180.0f * elevation_ang / 32768.0f;
    }

  /**
   * This method returns the gate size in meters
   */
  public int getGateSize(int datatype) {
    switch (datatype) {
      case REFLECTIVITY :
          return ((int) reflect_gate_size);

      case VELOCITY_HI :
      case VELOCITY_LOW :
      case SPECTRUM_WIDTH :
          return ((int) doppler_gate_size);
    }
    return -1;
  }

  /**
   * This method returns the starting gate in meters
   */
  public int getGateStart(int datatype) {
    switch (datatype) {
      case REFLECTIVITY :
        return ((int) reflect_first_gate);

      case VELOCITY_HI :
      case VELOCITY_LOW :
      case SPECTRUM_WIDTH :
          return ((int) doppler_first_gate);
    }
    return -1;
  }

  /**
   * This method returns the number of gates
   */
  public int getGateCount(int datatype) {
    switch (datatype) {
      case REFLECTIVITY :
          return ((int) reflect_gate_count);

      case VELOCITY_HI :
      case VELOCITY_LOW :
      case SPECTRUM_WIDTH :
          return ((int) doppler_gate_count);
    }
    return 0;
  }

  private short getDataOffset(int datatype) {
    switch (datatype) {
      case REFLECTIVITY : return reflect_offset;
      case VELOCITY_HI :
      case VELOCITY_LOW : return velocity_offset;
      case SPECTRUM_WIDTH : return spectWidth_offset;
    }
    return Short.MIN_VALUE;
  }

  public java.util.Date getDate() {
    if(Cinrad2IOServiceProvider.isSC || Cinrad2IOServiceProvider.isCC)
      return dateTime0.toDate();
    else if(Cinrad2IOServiceProvider.isCC20)
      return dateTime0.toDate();
    else
      return getDate( data_julian_date, data_msecs);
  }

  /**
   * Read data from this record.
   * @param raf read from this file
   * @param datatype which data type we want
   * @param gateRange handles the possible subset of data to return
   * @param ii put the data here
   * @throws IOException
   */
  public void readData(RandomAccessFile raf, int datatype, Range gateRange, IndexIterator ii) throws IOException {
    long offset = message_offset;
    offset += MESSAGE_HEADER_SIZE; // offset is from "start of digital radar data message header"
    offset += getDataOffset( datatype);
    raf.seek(offset);
    if (logger.isDebugEnabled()) {
      logger.debug("  read recno "+recno+" at offset "+offset+" count= "+getGateCount(datatype));
      logger.debug("   offset: reflect= "+reflect_offset+" velocity= "+velocity_offset+" spWidth= "+spectWidth_offset);
    }

    int dataCount = getGateCount( datatype);
    byte[] data = new byte[dataCount];
    raf.readFully(data);

    for (int gateIdx : gateRange) {
      if (gateIdx >= dataCount)
        ii.setByteNext(MISSING_DATA);
      else
        ii.setByteNext(data[gateIdx]);
    }

  }

  public void readData0(RandomAccessFile raf, int datatype, Range gateRange, IndexIterator ii) throws IOException {
    long offset = message_offset;
    offset += MESSAGE_HEADER_SIZE; // offset is from "start of digital radar data message header"
    offset += getDataOffset( datatype);
    raf.seek(offset);
    if (logger.isDebugEnabled()) {
      logger.debug("  read recno "+recno+" at offset "+offset+" count= "+getGateCount(datatype));
      logger.debug("   offset: reflect= "+reflect_offset+" velocity= "+velocity_offset+" spWidth= "+spectWidth_offset);
    }

    int dataCount = getGateCount( datatype);
    byte[] data = new byte[dataCount];
    byte[] b4 = new byte[4];
    int j = 0;
    if(datatype == REFLECTIVITY)
      j = 0;
    else if(datatype == VELOCITY_LOW)
      j = 1;
    else if(datatype == SPECTRUM_WIDTH)
      j = 3;

    //raf.readFully(data);

    for (int gateIdx : gateRange) {
      if (gateIdx >= dataCount)
        ii.setByteNext(MISSING_DATA);
      else {
        raf.read(b4);
        data[gateIdx] = b4[j];
        ii.setByteNext(data[gateIdx]);
      }
    }

  }

  public void readData1(RandomAccessFile raf, int datatype, Range gateRange, IndexIterator ii) throws IOException {
    long offset = message_offset;
    offset += MESSAGE_HEADER_SIZE; // offset is from "start of digital radar data message header"
    offset += getDataOffset( datatype);
    raf.seek(offset);
    if (logger.isDebugEnabled()) {
      logger.debug("  read recno "+recno+" at offset "+offset+" count= "+getGateCount(datatype));
      logger.debug("   offset: reflect= "+reflect_offset+" velocity= "+velocity_offset+" spWidth= "+spectWidth_offset);
    }

    int dataCount = getGateCount( datatype );
    short[] data = new short[dataCount];
    raf.readShort(data, 0, dataCount);

    for (int idx : gateRange) {
      if (idx >= dataCount)
        ii.setShortNext((short)-32768);
      else
        ii.setShortNext(data[idx]);
    }

  }
 /**
   * Instances which have same content are equal.
   *
  public boolean equals(Object oo) {
    if (this == oo) return true;
    if ( !(oo instanceof Cinrad2Record)) return false;
    return hashCode() == oo.hashCode();
  }
  /** Override Object.hashCode() to implement equals. *
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      result = 37*result + elevation_num;
      //result = 37*result + cut;
      //result = 37*result + datatype;
      hashCode = result;
    }
    return hashCode;
  }
  private volatile int hashCode = 0;  */

  public String toString() {
    return "elev= "+elevation_num+" radial_num = "+radial_num;
  }

  static class SweepInfo {
    byte amb;
    short arotate;
    short pref1;
    short pref2;
    short spulseW;
    short maxV;
    short maxL;
    short binWidth;
    short binnumber;
    short recordnumber;
    float elevationAngle;

    SweepInfo(RandomAccessFile din, int hoff ) throws IOException {
      din.seek(hoff);
      amb = din.readByte();
      arotate = (short)din.readUnsignedShort();
      pref1 = (short)din.readUnsignedShort();
      pref2 = (short)din.readUnsignedShort();
      spulseW = (short)din.readUnsignedShort();
      maxV = (short)din.readUnsignedShort();
      maxL = (short)din.readUnsignedShort();
      binWidth = (short)din.readUnsignedShort();
      binnumber = (short)din.readUnsignedShort();
      recordnumber = (short)din.readUnsignedShort();
      elevationAngle  = (short)din.readUnsignedShort()/100.0f;
    }
  }
}


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

package ucar.atd.dorade;

import ucar.nc2.constants.CDM;

import java.io.RandomAccessFile;
import java.util.Date;

class DoradeSWIB extends DoradeDescriptor {

  private String comment;
  private int sweepNumber;
  private int nRays;
  private float startAngle;
  private float endAngle;
  private float fixedAngle;
  private int filterFlag; // 0 = no filtering, 1 = filtered as described at
  // the beginning of the file

  private long[] rayDataOffsets;
  private DoradeRYIB[] myRYIBs;
  private DoradeASIB[] myASIBs;

  private float[] azimuths = null;
  private float[] elevations = null;

  public DoradeSWIB(RandomAccessFile file, boolean littleEndianData, DoradeVOLD vold) throws DescriptorException {
    byte[] data = readDescriptor(file, littleEndianData, "SWIB");

    //
    // unpack
    //
    comment = new String(data, 8, 8, CDM.utf8Charset).trim();
    sweepNumber = grabInt(data, 16);
    nRays = grabInt(data, 20);
    startAngle = grabFloat(data, 24);
    endAngle = grabFloat(data, 28);
    fixedAngle = grabFloat(data, 32);
    filterFlag = grabInt(data, 36);

    //
    // debugging output
    //
    if (verbose)
      System.out.println(this);

    //
    // Get the RYIBs and save offsets to the data for each ray
    //
    myRYIBs = new DoradeRYIB[nRays];
    myASIBs = new DoradeASIB[nRays];
    rayDataOffsets = new long[nRays];
    boolean haveASIBs = false;
    for (int i = 0; i < nRays; i++) {
      myRYIBs[i] = new DoradeRYIB(file, littleEndianData, vold);
      try {
        rayDataOffsets[i] = file.getFilePointer();
      } catch (java.io.IOException ex) {
        throw new DescriptorException(ex);
      }
      //
      // Look for an ASIB if this is the first ray or if we (based on
      // the results from the first ray) are expecting an ASIB for every
      // ray.
      //
      if (i == 0 || haveASIBs) try {
        myASIBs[i] = new DoradeASIB(file, littleEndianData);
        haveASIBs = true;
      } catch (DescriptorException ex) {
        //
        // We failed to find an ASIB.  If this is the first ray, just
        // assume we have no ASIBs and move on.  Otherwise, we're
        // missing an expected ASIB, and that's bad...
        //
        if (i == 0) {
          haveASIBs = false;
          myASIBs = null;
          //
          // We're at EOF.  Move back to where we started.
          //
          try {
            file.seek(rayDataOffsets[i]);
          } catch (java.io.IOException ioex) {
            throw new DescriptorException(ioex);
          }
        } else {
          throw new DescriptorException("not enough ASIBs");
        }
      }
    }
  }

  public String toString() {
    String s = "SWIB\n";
    s += "  sweep number: " + sweepNumber + "\n";
    s += "  number of rays: " + nRays + "\n";
    s += "  start angle: " + startAngle + "\n";
    s += "  end angle: " + endAngle + "\n";
    s += "  fixed angle: " + fixedAngle + "\n";
    s += "  filter flag: " + filterFlag + "\n";
    s += "  comment: " + comment;
    return s;
  }

  public float[] getRayData(DoradePARM parm, int ray) throws DescriptorException {
    return getRayData(parm, ray, null);
  }

  public float[] getRayData(DoradePARM parm, int ray, float[] workingArray) throws DescriptorException {
    try {
      //
      // position to the beginning of the ray
      //
      file.seek(rayDataOffsets[ray]);

      //
      // find the RDAT for the selected parameter
      //
      DoradeRDAT rdat = DoradeRDAT.getNextOf(parm, file,
              littleEndianData);
      return parm.getParamValues(rdat, workingArray);

    } catch (java.io.IOException ex) {
      throw new DescriptorException(ex);
    }
  }

  public int getNRays() {
    return nRays;
  }

  //  unidata added
  public Date[] getTimes() {
    if (myRYIBs == null)
      return null;
    Date[] times = new Date[nRays];
    for (int i = 0; i < nRays; i++)
      times[i] = myRYIBs[i].getRayTime();

    return times;
  }

  // unidata added
  public Date getRayTime(int ray) {
    return myRYIBs[ray].getRayTime();
  }

  /**
   * Get the array of per-ray latitudes.  If we do not have per-ray position
   * information, null is returned.
   *
   * @return an array of per-ray latitudes, or null if no per-ray position
   * information is available.
   */
  public float[] getLatitudes() {
    if (myASIBs == null)
      return null;
    float[] lats = new float[nRays];
    for (int i = 0; i < nRays; i++)
      lats[i] = myASIBs[i].getLatitude();
    return lats;
  }

  /**
   * Get the array of per-ray longitudes.  If we do not have per-ray position
   * information, null is returned.
   *
   * @return an array of per-ray longitudes, or null if no per-ray position
   * information is available.
   */
  public float[] getLongitudes() {
    if (myASIBs == null)
      return null;
    float[] lons = new float[nRays];
    for (int i = 0; i < nRays; i++)
      lons[i] = myASIBs[i].getLongitude();
    return lons;
  }

  /**
   * Get the array of per-ray altitudes.  If we do not have per-ray position
   * information, null is returned.
   *
   * @return an array of per-ray altitudes in km MSL, or null if no per-ray
   * position information is available.
   */
  public float[] getAltitudes() {
    if (myASIBs == null)
      return null;
    float[] alts = new float[nRays];
    for (int i = 0; i < nRays; i++)
      alts[i] = myASIBs[i].getAltitude();
    return alts;
  }

  /**
   * Get the array of azimuths for this sweep.
   *
   * @return the array of azimuths for this sweep
   * @see #getNRays()
   */
  public float[] getAzimuths() {
    if (azimuths == null) {
      azimuths = new float[nRays];
      for (int r = 0; r < nRays; r++) {
        azimuths[r] = myRYIBs[r].getAzimuth();
      }
    }
    return azimuths;
  }

  /**
   * Get the array of elevations for this sweep.
   *
   * @return the array of elevations for this sweep
   * @see #getNRays()
   */
  public float[] getElevations() {
    if (elevations == null) {
      elevations = new float[nRays];
      for (int r = 0; r < nRays; r++) {
        elevations[r] = myRYIBs[r].getElevation();
      }
    }
    return elevations;
  }

  /**
   * Get the fixed angle for this sweep.
   *
   * @return the fixed angle for this sweep
   */
  public float getFixedAngle() {
    return fixedAngle;
  }

  // unidata added
  public int getSweepNumber() {
    return sweepNumber;
  }
}


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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import ucar.ma2.Array;
import ucar.ma2.ArrayStructure;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.ma2.Section;
import ucar.ma2.StructureData;
import ucar.ma2.StructureDataIterator;
import ucar.ma2.StructureMembers;
import ucar.nc2.util.Indent;

/**
 * A Structure is a type of Variable that contains other Variables, like a struct in C.
 *  A Structure can be scalar or multidimensional.
 *

 * A call to structure.read() will read all of the data in a Structure,
 * including nested structures, and returns an Array of StructureData, with all of the data in memory.
 * If there is a nested sequence, the sequence data may be read into memory all at once, or it may be
 * read in increments as the iteration proceeds.
 *

 * Generally, the programmer can assume that the data in one Structure are stored together,
 *  so that it is efficient to read an entire Structure, and then access the Variable data through the
 *  Arrays in the StructureData.
 *
 * @author caron
 */

public class Structure extends Variable {
  static protected org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Structure.class);
  static protected int defaultBufferSize = 500 * 1000; // 500K bytes

  protected List<Variable> members;
  protected HashMap<String, Variable> memberHash;
  protected boolean isSubset;

   /* Create a Structure "from scratch". Also must call setDimensions().
   *
   * @param ncfile    the containing NetcdfFile.
   * @param group     the containing group; if null, use rootGroup
   * @param parent    parent Structure, may be null
   * @param shortName variable shortName, must be unique within the Group
   */
  public Structure(NetcdfFile ncfile, Group group, Structure parent, String shortName) {
    super(ncfile, group, parent, shortName);
    setDataType(DataType.STRUCTURE);
    this.elementSize = -1; // gotta wait before calculating
    members = new ArrayList<>();
    memberHash = new HashMap<>();
  }

  /**
   * Create a Structure "from scratch".
   * @param ncfile    the containing NetcdfFile.
   * @param group     the containing group; if null, use rootGroup
   * @param parent    parent Structure, may be null
   * @param shortName variable shortName, must be unique within the Group
   * @param dimList   list of Dimensions
     */
    public Structure(NetcdfFile ncfile, Group group, Structure parent,
                     String shortName, List<Dimension> dimList) {
      this(ncfile, group, parent, shortName);
      setDimensions(dimList);
   }

  /** Copy constructor.
   * @param from  copy from this
   */
  protected Structure(Structure from) { // , boolean reparent) {
    super(from);

    members = new ArrayList<>(from.members);
    memberHash = new HashMap<>(from.memberHash);
    isSubset = from.isSubset();

    /* if (reparent) {
      for (Variable v : members) {
        v.setParentStructure(this);
      }
    } */
  }

  /**
   * Create a subset of the Structure consisting only of the given member variables
   * @param memberNames list of Variable names, already a member
   * @return Structure containing just those members
   */
  public Structure select( List<String> memberNames) {
    Structure result = (Structure) copy();
    List<Variable> members = new ArrayList<>();
    for (String name : memberNames) {
      Variable m = findVariable(name);
      if (null != m) members.add(m);
    }
    result.setMemberVariables(members);
    result.isSubset = true;
    return result;
  }

  /**
   * Create a subset of the Structure consisting only of the one member variable
   * @param varName name of member Variable
   * @return containing just that member
   */
  public Structure select( String varName) {
    List<String> memberNames = new ArrayList<>(1);
    memberNames.add(varName);
    return  select(memberNames);
  }

  /**
   * Find if this was created from a subset() method.
   * @return true if this is a subset
   */
  public boolean isSubset() { return isSubset; }

  // for section and slice
  @Override
  protected Variable copy() {
    return new Structure(this);
  }

  protected int calcStructureSize() {
    int structureSize = 0;
    for (Variable member : members) {
      structureSize += member.getSize() * member.getElementSize();
    }
    return structureSize;
  }

  /** Caching is not allowed */
  @Override
  public boolean isCaching() {
    return false;
  }

  /** Caching is not allowed */
  @Override
  public void setCaching(boolean caching) {
    this.cache.isCaching = false;
    this.cache.cachingSet = true;
  }

  /** Add a member variable
   * @param v add this variable as a member of this structure
   * @return the added variable
   */
  public Variable addMemberVariable( Variable v) {
    if (isImmutable()) throw new IllegalStateException("Cant modify");
    members.add( v);
    memberHash.put( v.getShortName(), v);
    v.setParentStructure( this);
    return v;
  }

  /** Set the list of member variables.
   * @param vars this is the list of member variables
   */
  public void setMemberVariables( List<Variable> vars) {
    if (isImmutable()) throw new IllegalStateException("Cant modify");
    members = new ArrayList<>();
    memberHash = new HashMap<>(2*vars.size());
    for (Variable v : vars) {
      addMemberVariable(v);
    }
  }

  /** Remove a Variable : uses the Variable name to find it.
   * @param v remove this variable as a member of this structure
   * @return true if was found and removed
   */
  public boolean removeMemberVariable( Variable v) {
    if (isImmutable()) throw new IllegalStateException("Cant modify");
    if (v == null) return false;
    //smembers = null;
    java.util.Iterator<Variable> iter = members.iterator();
    while (iter.hasNext()) {
      Variable mv =  iter.next();
      if (mv.getShortName().equals(v.getShortName())) {
        iter.remove();
        memberHash.remove( v.getShortName());
        return true;
      }
    }
    return false;
  }

  /** Replace a Variable with another that has the same name : uses the variable name to find it.
   * If old Var is not found, just add the new one
   * @param newVar add this variable as a member of this structure
   * @return true if was found and replaced */
  public boolean replaceMemberVariable( Variable newVar) {
    if (isImmutable()) throw new IllegalStateException("Cant modify");
    //smembers = null;
    boolean found = false;
    for (int i = 0; i < members.size(); i++) {
      Variable v =  members.get(i);
      if (v.getShortName() == null)
          System.out.println("BAD null short name"); // E:/work/ghansham/iasi_20110513_045057_metopa_23676_eps_o.l1_bufr
      if (v.getShortName().equals( newVar.getShortName())) {
        members.set( i, newVar);
        found = true;
      }
    }

    if (!found)
      members.add( newVar);
    return found;
  }

  /** Set the parent group of this Structure, and all member variables. */
  @Override
  public void setParentGroup(Group group) {
    if (isImmutable())
        throw new IllegalStateException("Cant modify");
    super.setParentGroup(group);
    if(members != null)
    {
        for (Variable v : members) {
          v.setParentGroup(group);
        }
    }
  }

  @Override
  public Variable setImmutable() {
    members = Collections.unmodifiableList(members);
    for (Variable m : members)
      m.setImmutable();

    super.setImmutable();
    return this;
  }

  /** Get the variables contained directly in this Structure.
   * @return List of type Variable.
   */
  public java.util.List<Variable> getVariables() {
    return isImmutable() ?  members : new ArrayList<>(members);
  }

  /** Get the number of variables contained directly in this Structure.
   * @return number of member variables
   */
  public int getNumberOfMemberVariables() {
    return members.size();
  }

  /** Get the (short) names of the variables contained directly in this Structure.
   * @return List of type String.
   */
  public java.util.List<String> getVariableNames() {
    return new ArrayList<>(memberHash.keySet());
  }

  /**
   * Find the Variable member with the specified (short) name.
   * @param shortName name of the member variable.
   * @return the Variable member with the specified (short) name, or null if not found.
   */
  public Variable findVariable(String shortName) {
    if (shortName == null) return null;
    return memberHash.get(shortName);
  }

  /**
   * Create a StructureMembers object that describes this Structure.
   * CAUTION: Do not use for iterating over a StructureData or ArrayStructure - get the StructureMembers object
   * directly from the StructureData or ArrayStructure.
   *
   * @return a StructureMembers object that describes this Structure.
   */
  public StructureMembers makeStructureMembers() {
    StructureMembers smembers = new StructureMembers( getShortName());
    for (Variable v2 : getVariables()) {
      StructureMembers.Member m = smembers.addMember( v2.getShortName(), v2.getDescription(),
          v2.getUnitsString(), v2.getDataType(), v2.getShape());
      if (v2 instanceof Structure)
        m.setStructureMembers( ((Structure)v2).makeStructureMembers());
    }
    return smembers;
  }
  //protected StructureMembers smembers = null;

  /**
   * Get the size of one element of the Structure.
   * @return size (in bytes)
   */
  @Override
  public int getElementSize() {
    if (elementSize == -1) calcElementSize();
    return elementSize;
  }

  /**
   * Force recalculation of size of one element of this structure - equals the sum of sizes of its members.
   * This is used only by low level classes like IOSPs.
   */
  public void calcElementSize() {
    int total = 0;
    for (Variable v : members) {
      total += v.getElementSize() * v.getSize();
    }
    elementSize = total;
  }

  /**
   * Use this when this is a scalar Structure. Its the same as read(), but it extracts the single
   * StructureData out of the Array.
   * @return StructureData for a scalar
   * @throws java.io.IOException on read error
   */
  public StructureData readStructure() throws IOException {
    if (getRank() != 0) throw new java.lang.UnsupportedOperationException("not a scalar structure");
    Array dataArray = read();
    ArrayStructure data = (ArrayStructure) dataArray;
    return data.getStructureData(0);
  }

  /**
   * Use this when this is a one dimensional array of Structures, or you are doing the index calculation yourself for
   * a multidimension array. This will read only the ith structure, and return the data as a StructureData object.
   * @param index index into 1D array
   * @return ith StructureData
   * @throws java.io.IOException on read error
   * @throws ucar.ma2.InvalidRangeException if index out of range
   */
  public StructureData readStructure(int index) throws IOException, ucar.ma2.InvalidRangeException {
    Section section = null; // works for scalars i think

    if (getRank() == 1) {
      section = new Section().appendRange(index,index);

    } else if (getRank() > 1) {
      Index ii = Index.factory(shape); // convert to nD index
      ii.setCurrentCounter(index);
      int[] origin = ii.getCurrentCounter();
      section = new Section();
      for (int anOrigin : origin)
        section.appendRange(anOrigin, anOrigin);
    }

    Array dataArray = read(section);
    ArrayStructure data = (ArrayStructure) dataArray;
    return data.getStructureData(0);
  }

  /**
   * For rank 1 array of Structures, read count Structures and return the data as an ArrayStructure.
   * Use only when this is a one dimensional array of Structures.
   * @param start start at this index
   * @param count return this many StructureData
   * @return the StructureData recordsfrom start to start+count-1
   * @throws java.io.IOException on read error
   * @throws ucar.ma2.InvalidRangeException if start, count out of range
   */
  public ArrayStructure readStructure(int start, int count) throws IOException, ucar.ma2.InvalidRangeException {
    if (getRank() != 1) throw new java.lang.UnsupportedOperationException("not a vector structure");
    int[] origin = new int[] {start};
    int[] shape = new int[] {count};
    if (NetcdfFile.debugStructureIterator)
      System.out.println("readStructure "+start+" "+count);
    return (ArrayStructure) read(origin, shape);
  }

  /** Iterator over all the data in a Structure.
   *

   *  StructureDataIterator ii = structVariable.getStructureIterator();
   *  while (ii.hasNext()) {
        StructureData sdata = ii.next();
      }

   * @return StructureDataIterator over type StructureData
   * @throws java.io.IOException on read error
   * @see #getStructureIterator(int bufferSize)
   */
  public StructureDataIterator getStructureIterator()  throws java.io.IOException {
    return getStructureIterator(defaultBufferSize);
  }

  /**
   * Get an efficient iterator over all the data in the Structure.
   *
   * This is the efficient way to get all the data, it can be much faster than reading one record at a time,
   *   and is optimized for large datasets.
   * This is accomplished by buffering bufferSize amount of data at once.
   *
   *
Example:
   *
   *  StructureDataIterator ii = structVariable.getStructureIterator(100 * 1000);
   *  while (ii.hasNext()) {
   *    StructureData sdata = ii.next();
   *  }
   *

   *  @param bufferSize size in bytes to buffer, set < 0 to use default size
   *  @return StructureDataIterator over type StructureData
   * @throws java.io.IOException on read error
   */
  public StructureDataIterator getStructureIterator(int bufferSize) throws java.io.IOException {
    return (getRank() < 2) ? new Structure.IteratorRank1(bufferSize) : new Structure.Iterator(bufferSize);
  }

  /**
   * Iterator over type StructureData, rank 1 (common) case
   */
  private class IteratorRank1 implements StructureDataIterator {
    private int count = 0;
    private int recnum = (int) getSize();
    private int readStart = 0;
    private int readCount = 0;
    private int readAtaTime;
    private ArrayStructure as = null;

    protected IteratorRank1(int bufferSize) {
      setBufferSize( bufferSize);
    }

    @Override
    public boolean hasNext() { return count < recnum; }

    @Override
    public StructureDataIterator reset() {
      count = 0;
      readStart = 0;
      readCount = 0;
      return this;
    }

    @Override
    public StructureData next() throws IOException {
      if (count >= readStart) {
        readNext();
      }

      count++;
      return as.getStructureData( readCount++);
    }

    @Override
    public int getCurrentRecno() {
      return count-1;
    }

    private void readNext() throws IOException {
      int left = Math.min(recnum, readStart+readAtaTime); // dont go over recnum
      int need = left - readStart; // how many to read this time
      try {
        // System.out.println(" read start= "+readStart+" count= "+need);
        as = readStructure( readStart, need);
        if (NetcdfFile.debugStructureIterator)
          System.out.println("readNext "+count+" "+readStart);

      } catch (InvalidRangeException e) {
        log.error("Structure.IteratorRank1.readNext() ",e);
        throw new IllegalStateException("Structure.Iterator.readNext() ",e);
      } // cant happen
      readStart += need;
      readCount = 0;
    }

    @Override
    public void setBufferSize(int bytes) {
      if (count > 0) return; // too late
      int structureSize = calcStructureSize();
      if (structureSize <= 0)
        structureSize = 1; // no members in the psuedo-structure LOOK is this ok?
      if (bytes <= 0)
        bytes = defaultBufferSize;
      readAtaTime = Math.max( 10, bytes / structureSize);
      if (NetcdfFile.debugStructureIterator)
        System.out.println("Iterator structureSize= "+structureSize+" readAtaTime= "+readAtaTime);
    }

  }

  /**
   * Iterator over type StructureData, general case
   */
  private class Iterator implements StructureDataIterator {
    private int count; // done so far
    private int total; // total to do
    private int readStart; // current buffer starts at
    private int readCount; // count within the current buffer [0,readAtaTime)
    private int outerCount;  // over the outer Dimension
    private ArrayStructure as = null;

    protected Iterator(int bufferSize) {
      reset();
    }

    @Override
    public boolean hasNext() {
      return count < total;
    }

    @Override
    public StructureDataIterator reset() {
      count = 0;
      total = (int) getSize();
      readStart = 0;
      readCount = 0;
      outerCount = 0;
      return this;
    }

    @Override
    public int getCurrentRecno() {
      return count-1;
    }

    @Override
    public StructureData next() throws IOException {
      if (count >= readStart)
        readNextGeneralRank();

      count++;
      return as.getStructureData( readCount++);
    }

    private void readNextGeneralRank() throws IOException {

      try {
        Section section = new Section(shape);
        section.setRange( 0, new Range(outerCount, outerCount));

        as = (ArrayStructure) read( section);

        if (NetcdfFile.debugStructureIterator)
          System.out.println("readNext inner="+outerCount+" total="+outerCount);

        outerCount++;

      } catch (InvalidRangeException e) {
        log.error("Structure.Iterator.readNext() ",e);
        throw new IllegalStateException("Structure.Iterator.readNext() ",e);
      } // cant happen

      readStart += as.getSize();
      readCount = 0;
    }

  }

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

  /** Get String with name and attributes. Used in short descriptions like tooltips.
   * @return  name and attributes String
   */
  public String getNameAndAttributes() {
    Formatter sbuff = new Formatter();
    sbuff.format("Structure ");
    getNameAndDimensions(sbuff, false, true);
    sbuff.format("%n");
    for (Attribute att : attributes.getAttributes()) {
      sbuff.format("  %s:%s;%n",getShortName(), att.toString());
    }
    return sbuff.toString();
  }

  @Override
  protected void writeCDL(Formatter buf, Indent indent, boolean useFullName, boolean strict) {
    buf.format("%n%s%s {%n", indent, dataType);

    indent.incr();
    for (Variable v : members)
      v.writeCDL(buf, indent, useFullName, strict);
    indent.decr();

    buf.format("%s} ", indent);
    getNameAndDimensions(buf, useFullName, strict);
    buf.format(";%s%n", extraInfo());

    for (Attribute att : getAttributes()) {
      if(Attribute.isspecial(att)) continue;
      buf.format("  ", indent);
      att.writeCDL(buf, strict, getShortName());
      buf.format(";");
      if(!strict && (att.getDataType() != DataType.STRING))
        buf.format(" // %s", att.getDataType());
      buf.format("%n");
    }
    buf.format("%n");
  }

}


/*
 * 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.input.SAXBuilder;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Read standard WMO Table A (data categories)
 *
 * @author John
 * @since 8/12/11
 */
public class TableA {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableA.class);
  static private final String TABLEA_FILENAME = "wmo/BUFR_25_0_0_TableA_en.xml";
  private static Map<Integer, String> tableA = null;

  /*
  <BUFR_19_1_1_TableA_en>
    <No>27</No>
    <CodeFigure>28</CodeFigure>
    <Meaning_en>Precision orbit (satellite)</Meaning_en>
    <Status>Operational</Status>
  </BUFR_19_1_1_TableA_en>

   <Exp_BUFRTableA_E>
    <No>4</No>
    <CodeFigure>3</CodeFigure>
    <Meaning_E>Vertical soundings (satellite)</Meaning_E>
    <Status>Operational</Status>
  </Exp_BUFRTableA_E>
  */
  static private void init() {
    String filename = BufrTables.RESOURCE_PATH + TABLEA_FILENAME;
    try (InputStream is = CodeFlagTables.class.getResourceAsStream(filename)) {

      HashMap<Integer, String> map = new HashMap<>(100);
      SAXBuilder builder = new SAXBuilder();
      org.jdom2.Document tdoc = builder.build(is);
      org.jdom2.Element root = tdoc.getRootElement();

      List<Element> elems = root.getChildren();
      for (Element elem : elems) {
        String line = elem.getChildText("No");
        String codeS = elem.getChildText("CodeFigure");
        String desc = elem.getChildText("Meaning_en");

        try {
          int code = Integer.parseInt(codeS);
          map.put(code,  desc);
        } catch (NumberFormatException e) {
          log.debug("NumberFormatException on line " + line + " in " + codeS);
        }

      }
      tableA = map;

    } catch (Exception e) {
      log.error("Can't read BUFR code table " + filename, e);
    }
  }

  /**
   * data category name, from table A
   *
   * @param cat data category
   * @return category name, or null if not found
   */
  static public String getDataCategory(int cat) {
    if (tableA == null) init();
    String result = tableA.get(cat);
    return result != null ? result : "Unknown category=" + cat;
  }

}