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

/**
 * Renders collections of stations. package private.
 *
 * @author caron
 */

import ucar.nc2.ui.gis.SpatialGrid;
import ucar.nc2.ui.util.Renderer;
import ucar.nc2.ui.widget.FontUtil;
import ucar.unidata.geoloc.*;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.util.List;

class StationRenderer implements Renderer {
  private List<StationUI> stations = new ArrayList<StationUI>(); // StationUI objects
  private HashMap<String, StationUI> stationHash = new HashMap<String, StationUI>();
  private SpatialGrid stationGrid;              // for "decluttering" and closest point
  private ProjectionImpl project = null;        // display projection
  private AffineTransform world2Normal;

  // drawing parameters
  private Color color = Color.black;
  private int circleRadius = 5;
  private Rectangle2D circleBB = new Rectangle2D.Double(-circleRadius, -circleRadius, 2*circleRadius, 2*circleRadius);
  private Rectangle2D typicalBB = circleBB;
  private FontUtil.StandardFont textFont;

    // pick stuff
  private StationUI selected = null;

  // working objects to minimize excessive gc
  private Point2D.Double ptN = new Point2D.Double();

  // misc state
  private boolean declutter = true;
  private boolean posWasCalc = false;
  private boolean debug = false;

    /** constructor */
  public StationRenderer () {
    stationGrid = new SpatialGrid(50, 30);
    stationGrid.setOverlap(10);
    textFont = FontUtil.getStandardFont(10);
  }

  public void incrFontSize() { textFont.incrFontSize(); };
  public void decrFontSize() { textFont.decrFontSize(); };

  public void setColor(java.awt.Color color){ this.color = color; }
  public java.awt.Color getColor() { return color; }

  public LatLonRect getPreferredArea() {
    return null;
  }

  /**
   * Set the list of stations.
   * @param stns: list of DDStation objects
   */
  public void setStations(java.util.List<ucar.unidata.geoloc.Station> stns) {
    stations = new ArrayList<StationUI>( stns.size());
    stationHash.clear();
    for (int i = 0; i < stns.size(); i++) {
      ucar.unidata.geoloc.Station s = (ucar.unidata.geoloc.Station) stns.get(i);
      StationUI sui = new StationUI( s); // wrap in a StationUI
      stations.add(sui); // wrap in a StationUI
      stationHash.put( s.getName(), sui);
    }
    posWasCalc = false;
    calcWorldPos();
  }

  // set selected station based on the sttion id.
  public void setSelectedStation( String name) {
    StationUI sui = (StationUI) stationHash.get( name);
    if (sui != null) {
      setSelectedStation( sui);
    }
  }

  private void setSelectedStation( StationUI sui) {
    if (selected != null) {
      selected.selected = false;
      selected = null;
    }

    if (sui != null) {
      selected = sui;
      selected.selected = true;
    }
  }

  public void setDeclutter(boolean declut) { declutter = declut; }
  public boolean getDeclutter() { return declutter; }

  public void setProjection(ProjectionImpl project) {
    this.project = project;
    calcWorldPos();
  }

  private void calcWorldPos() {
    if (project == null) return;
    for (int i = 0; i < stations.size(); i++) {
      StationUI s = (StationUI) stations.get(i);
      s.worldPos.setLocation( project.latLonToProj(s.latlonPos));
    }
    posWasCalc = true;
  }

    ///////////////// pickable stuff

  /**
   * Find station that contains this point. If it exists, make it the
   *   "selected" station.
   * @param pickPt: world coordinates
   * @return station that contains this point, or null if none.
   */
  public ucar.unidata.geoloc.Station pick(Point2D pickPt) {
    if (world2Normal == null || pickPt == null || stations.isEmpty()) return null;

    world2Normal.transform(pickPt, ptN); // work in normalized coordinate space
    StationUI closest = (StationUI) stationGrid.findIntersection(ptN);
    setSelectedStation( closest);

    return getSelectedStation();
  }

  /**
   * Find station closest to this point. Make it the "selected" station.
   * @param pickPt: world coordinates
   * @return station that contains this point, or null if none.
   */
  public ucar.unidata.geoloc.Station pickClosest(Point2D pickPt) {
    if (world2Normal == null || pickPt == null || stations.isEmpty()) return null;

    world2Normal.transform(pickPt, ptN); // work in normalized coordinate space
    StationUI closest = (StationUI) stationGrid.findClosest(ptN);

    if (debug) System.out.println("closest= " +closest);
    setSelectedStation( closest);

    return getSelectedStation();
  }

  /**
   * Get the selected station.
   * @return the selected station, or null if none selected
   */
  public ucar.unidata.geoloc.Station getSelectedStation() {
    return (selected != null) ? selected.ddStation : null;
  }

  /**
   * Is this point contained in a Station bounding box?
   * @param p: normalized coords.
   * @return Station containing pt, or null.
   */
  public StationUI isOnStation( Point p) {
    return (StationUI) stationGrid.findIntersection (p);
  }

  public void draw(java.awt.Graphics2D g, java.awt.geom.AffineTransform normal2Device) {
    if ((project == null) || !posWasCalc) return;

    // use world coordinates for position, but draw in screen coordinates
    // so that the symbols stay the same size
    AffineTransform world2Device = g.getTransform();
    g.setTransform(normal2Device);        //  identity transform for screen coords

    // transform World to Normal coords:
    //    world2Normal = pixelAT-1 * world2Device
    // cache for pick closest
    try {
      world2Normal = normal2Device.createInverse();
      world2Normal.concatenate( world2Device);
    } catch ( java.awt.geom.NoninvertibleTransformException e) {
      System.out.println(
            " RendSurfObs: NoninvertibleTransformException on " +
            normal2Device);
      return;
    }

    // we want aliasing; but save previous state to restore at end
    Object saveHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g.setStroke(new java.awt.BasicStroke(1.0f));

    // set up the grid
    Rectangle2D bbox = (Rectangle2D) g.getClip(); // clipping area in normal coords
    // clear the grid = "no stations are drawn"
    stationGrid.clear();
    // set the grid size based on typical bounding box
    stationGrid.setGrid(bbox, typicalBB.getWidth(), typicalBB.getHeight());

    g.setFont( textFont.getFont());
    g.setColor(color);

    // always draw selected
    if (selected != null) {
      selected.calcPos( world2Normal);
      stationGrid.markIfClear( selected.getBB(), selected);
      selected.draw(g);
    }

    for (int i=0; i < stations.size(); i++) {
      StationUI s = (StationUI) stations.get(i);
      s.calcPos( world2Normal);
      if (stationGrid.markIfClear( s.getBB(), s) || !declutter) {
        s.draw(g);
      }
    }

    // restore
    g.setTransform(world2Device);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, saveHint);
  }

  public class StationUI {
    private ucar.unidata.geoloc.Station ddStation;
    private String id;

    private LatLonPointImpl latlonPos = new LatLonPointImpl();       // latlon pos
    private ProjectionPointImpl worldPos = new ProjectionPointImpl();// world pos
    private Point2D.Double screenPos = new Point2D.Double(); // normalized screen pos

    private Rectangle2D bb;       // bounding box, in normalized screen coords, loc = 0, 0
    private Rectangle2D bbPos = new Rectangle2D.Double();    // bounding box, translated to current drawing pos
    private boolean selected = false;

    StationUI(ucar.unidata.geoloc.Station stn) {
      ddStation = stn;
      latlonPos.setLatitude(stn.getLatitude());
      latlonPos.setLongitude(stn.getLongitude());

      id = stn.getName();

      // text bb
      Dimension t = textFont.getBoundingBox(id);
      bb = new Rectangle2D.Double(-circleRadius, -circleRadius-t.getHeight(), t.getWidth(), t.getHeight());
      // add circle bb
      bb.add( circleBB);
      typicalBB = bb;
    }

    public String getID() { return id; }
    public ucar.unidata.geoloc.Station getStation() { return ddStation; }
    public LatLonPoint getLatLon() { return latlonPos; }
    public ProjectionPointImpl getLocation() { return worldPos; }
    public boolean isSelected() { return selected; }
    public void setIsSelected(boolean selected) { this.selected = selected; }
    public Rectangle2D getBB() { return bbPos; }

    boolean contains( Point p) {
      return bbPos.contains( p);
    }

    void calcPos( AffineTransform w2n) {
      Point2D.Double p2d = new Point2D.Double(worldPos.getX(), worldPos.getY());
      w2n.transform( p2d, screenPos);  // work in normalized coordinate space
      bbPos.setRect( screenPos.getX() + bb.getX(), screenPos.getY() + bb.getY(),
          bb.getWidth(), bb.getHeight());
    }

    void draw(Graphics2D g) {
      g.setColor( selected ? Color.red : Color.black);
      drawCircle(g, screenPos);
      drawText(g, screenPos, id);
    }

    /** draw the symbol at the specified location
     * @param g Graphics2D object
     * @param loc the reference point (center of the CloudSymbol icon offset from here)
     */
    private void drawCircle( Graphics2D g, Point2D loc) {
      int x = (int) (loc.getX()-circleRadius);
      int y = (int) (loc.getY()-circleRadius);
      g.drawOval( x, y, 2*circleRadius, 2*circleRadius);
    }

    private void drawText( Graphics2D g, Point2D loc, String text) {
      int x = (int) (loc.getX() - circleRadius);
      int y = (int) (loc.getY() - circleRadius);
      g.drawString( text, x, y);
    }

    public String toString() {
      return id+": "+ddStation.getName();
    }

  } // end inner class StationUI

}


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

package ucar.nc2.iosp.bufr;

import org.jdom2.Element;
import thredds.client.catalog.Catalog;
import ucar.ma2.*;
import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Sequence;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
import ucar.nc2.ft.point.bufr.BufrField;
import ucar.nc2.ft.point.bufr.StandardFields;
import ucar.nc2.ncml.NcMLReader;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.util.Indent;
import ucar.unidata.geoloc.StationImpl;
import ucar.unidata.io.RandomAccessFile;

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

/**
 * Configuration for converting BUFR files to CDM
 * DataDescriptor tree becomes FieldConverter tree with annotations.
 * @author caron
 * @since 8/8/13
 */
public class BufrConfig {
  static private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrConfig.class);

  static public BufrConfig scanEntireFile(RandomAccessFile raf) throws IOException {
    return new BufrConfig( raf);
  }

  static public BufrConfig openFromMessage(RandomAccessFile raf, Message m, Element iospParam) throws IOException {
    BufrConfig config = new BufrConfig( raf, m);
    if (iospParam != null)
      config.merge(iospParam);
    return config;
  }

  private String filename;
  private StandardFields.StandardFieldsFromMessage standardFields;
  private FieldConverter rootConverter;
  private int messHash = 0;
  private FeatureType featureType;
  private Map<String, BufrStation> map;
  private long start = Long.MAX_VALUE;
  private long end = Long.MIN_VALUE;
  private boolean debug = false;

  /*
   * Open file as a stream of BUFR messages, create config file.
   *
   * Examine length of sequences, annotate
   *
   * @param bufrFilename open this file
   * @throws java.io.IOException on IO error
   *
  private BufrConfig(String bufrFilename, boolean read) throws IOException {
    this.filename =  bufrFilename;
    try {
      scanBufrFile(new RandomAccessFile(bufrFilename, "r"), read);
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e.getMessage());
    }
  } */

  private BufrConfig(RandomAccessFile raf) throws IOException {
    this.filename =  raf.getLocation();
    try {
      scanBufrFile(raf);
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e.getMessage());
    }
  }

  private BufrConfig(RandomAccessFile raf, Message m) throws IOException {
    this.filename =  raf.getLocation();
    this.messHash = m.hashCode();
    this.rootConverter = new FieldConverter(m.ids.getCenterId(), m.getRootDataDescriptor());
    standardFields = StandardFields.extract(m);
  }

  public String getFilename() {
    return filename;
  }

  public FieldConverter getRootConverter() {
    return rootConverter;
  }

  public Map<String,BufrStation> getStationMap() {
    return map;
  }

  public int getMessHash() {
    return messHash;
  }

  public FeatureType getFeatureType() {
    return featureType;
  }

  public FieldConverter getStandardField(BufrCdmIndexProto.FldType want) {
    for (FieldConverter fld : rootConverter.flds)
      if (fld.type == want) return fld;
    return null;
  }

  public long getStart() {
    return start;
  }

  public long getEnd() {
    return end;
  }

  public long getNobs() {
    return countObs;
  }

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

  private void merge(Element iospParam) {
    assert iospParam.getName().equals("iospParam");
    Element bufr2nc = iospParam.getChild("bufr2nc", Catalog.ncmlNS);
    if (bufr2nc == null) return;
    for (Element child : bufr2nc.getChildren("fld", Catalog.ncmlNS))
      merge(child, rootConverter);
  }

  private void merge(Element jdom, FieldConverter parent) {
    if (jdom == null || parent == null) return;

    FieldConverter fld = null;

    // find the corresponding field
    String idxName = jdom.getAttributeValue("idx");
    if (idxName != null) {
      try {
        int idx = Integer.parseInt(idxName);
        fld = parent.getChild(idx);
      } catch (NumberFormatException ne) {
        log.info("BufrConfig cant find Child member index={} for file = {}", idxName, filename);
      }
    }

    if (fld == null) {
      String fxyName = jdom.getAttributeValue("fxy");
      if (fxyName != null) {
        fld = parent.findChildByFxyName(fxyName);
        if (fld == null) {
          log.info("BufrConfig cant find Child member fxy={} for file = {}", fxyName, filename);
        }
      }
    }

    if (fld == null) {
      String name = jdom.getAttributeValue("name");
      if (name != null) {
        fld = parent.findChild(name);
        if (fld == null) {
          log.info("BufrConfig cant find Child member name={} for file = {}", name, filename);
        }
      }
    }

    if (fld == null) {
      log.info("BufrConfig must have idx, name or fxy attribute = {} for file = {}", jdom, filename);
      return;
    }

    String action = jdom.getAttributeValue("action");
    if (action != null && !action.isEmpty())
      fld.setAction(action);

    if (jdom.getChildren("fld") != null) {
      for (Element child : jdom.getChildren("fld", Catalog.ncmlNS)) {
        merge(child, fld);
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  private StandardFields.StandardFieldsFromStructure extract;
  private boolean hasStations = false;
  private boolean hasDate = false;
  private int countObs = 0;
  private void scanBufrFile(RandomAccessFile raf) throws Exception {
    NetcdfFile ncd = null;
    countObs = 0;

    try {
      MessageScanner scanner = new MessageScanner(raf);
      Message protoMessage = scanner.getFirstDataMessage();
      if (protoMessage == null) throw new IOException("No message found!");

      messHash = protoMessage.hashCode();
      standardFields = StandardFields.extract(protoMessage);
      rootConverter = new FieldConverter(protoMessage.ids.getCenterId(), protoMessage.getRootDataDescriptor());

       if (standardFields.hasStation()) {
         hasStations = true;
         map = new HashMap<>(1000);
       }
      featureType = guessFeatureType(standardFields);
      hasDate = standardFields.hasTime();

      //ncd = NetcdfDataset.openDataset(raf.getLocation(), BufrIosp2.enhance, -1, null, null); // LOOK opening another raf
      ncd = NetcdfFile.open(raf.getLocation()); // LOOK opening another raf
      Attribute centerAtt = ncd.findGlobalAttribute(BufrIosp2.centerId);
      int center = (centerAtt == null) ? 0 : centerAtt.getNumericValue().intValue();

      Sequence seq = (Sequence) ncd.findVariable(null, BufrIosp2.obsRecord);
      extract = new StandardFields.StandardFieldsFromStructure(center, seq);

      StructureDataIterator iter = seq.getStructureIterator();
      processSeq( iter, rootConverter, true);

      setStandardActions(rootConverter);

    } finally {
      if (ncd != null) ncd.close();
    }
    System.out.printf("nobs = %d%n", countObs);
  }

  private FeatureType guessFeatureType(StandardFields.StandardFieldsFromMessage standardFields) {
    if (standardFields.hasStation()) return FeatureType.STATION;
    if (standardFields.hasTime()) return FeatureType.POINT;
    return FeatureType.ANY;
  }

  private void setStandardActions(FieldConverter fld) {
    fld.setAction(fld.makeAction());
    if (fld.flds == null) return;
    for (FieldConverter child : fld.flds)
      setStandardActions(child);
  }

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

  private CalendarDate today = CalendarDate.present();
  private void processSeq(StructureDataIterator sdataIter, FieldConverter parent, boolean isTop) throws IOException {
     try {
       while (sdataIter.hasNext()) {
         StructureData sdata = sdataIter.next();

         if (isTop) {
           countObs++;
           if (debug && countObs % 100 == 0) System.out.printf("%d ", countObs);

           if (hasStations) processStations(parent, sdata);
           if (hasDate) {
             extract.extract(sdata);
             CalendarDate date = extract.makeCalendarDate();
             if (Math.abs(date.getDifferenceInMsecs(today)) > 1000L * 3600 * 24 * 100) {
               extract.makeCalendarDate();
             }
             long msecs = date.getMillis();
             if (this.start > msecs) {
               this.start = msecs;
               //System.out.printf("new start %s%n", date);
             }
             if (this.end < msecs) {
               this.end = msecs;
               //System.out.printf("new end %s%n", date);
             }
           }
         }

         int count = 0;
         for (StructureMembers.Member m : sdata.getMembers()) {
           if (m.getDataType() == DataType.SEQUENCE) {
             FieldConverter fld = parent.getChild(count);
             ArraySequence data = (ArraySequence) sdata.getArray(m);
             int n = data.getStructureDataCount();
             fld.trackSeqCounts(n);
             processSeq(data.getStructureDataIterator(), fld, false);
           }
           count++;
         }
       }
     } finally {
       sdataIter.close();
     }
   }

   private void processStations(FieldConverter parent, StructureData sdata) {
     BufrStation station = new BufrStation();
     station.read(parent, sdata);

     if (station.getName() == null) {
       log.warn("bad station name: "+station);
       return;
     }

     BufrStation check = map.get(station.getName());
     if (check == null)
       map.put(station.getName(), station);
     else {
       check.count++;
       if (!station.equals(check))
         log.warn("bad station doesnt equal "+station+" != "+check);
     }
   }

  public class BufrStation extends StationImpl {
    public int count = 1;

    void read(FieldConverter parent, StructureData sdata) {
      extract.extract(sdata);

      setName( extract.getStationId());
      setLatitude( extract.getFieldValueD( BufrCdmIndexProto.FldType.lat));
      setLongitude( extract.getFieldValueD( BufrCdmIndexProto.FldType.lon));
      if (extract.hasField(BufrCdmIndexProto.FldType.stationDesc))
        setDescription( extract.getFieldValueS( BufrCdmIndexProto.FldType.stationDesc));
      if (extract.hasField(BufrCdmIndexProto.FldType.wmoId))
        setWmoId( extract.getFieldValueS( BufrCdmIndexProto.FldType.wmoId));
      if (extract.hasField(BufrCdmIndexProto.FldType.heightOfStation))
        setAltitude( extract.getFieldValueD( BufrCdmIndexProto.FldType.heightOfStation));
    }

    /* void read(FieldConverter parent, StructureData sdata) {
      int count = 0;
      List<FieldConverter> flds = parent.getChildren(); // asssume these track exactly the members
      for (StructureMembers.Member m : sdata.getMembers()) {
        FieldConverter fld = flds.get(count++);
        if (fld.getType() == null) continue;

        switch (fld.getType()) {
          case stationId:
            setName( readString(sdata, m));
            break;
          case stationDesc:
            setDescription(sdata.getScalarString(m));
            break;
          case wmoId:
            setWmoId(readString(sdata, m));
            break;
          case lat:
            setLatitude(sdata.convertScalarDouble(m));
            break;
          case lon:
            setLongitude(sdata.convertScalarDouble(m));
            break;
          case height:
            setAltitude(sdata.convertScalarDouble(m));
            break;
          case heightOfStation:
            setAltitude(sdata.convertScalarDouble(m));
            break;
        }
      }
    }

    String readString(StructureData sdata, StructureMembers.Member m) {
      if (m.getDataType().isString())
        return sdata.getScalarString(m);
      else if (m.getDataType().isIntegral())
        return Integer.toString(sdata.convertScalarInt(m));
      else if (m.getDataType().isNumeric())
        return Double.toString(sdata.convertScalarDouble(m));
      else return "type "+ m.getDataType();
    }  */

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

      BufrStation that = (BufrStation) o;

      if (Double.compare(that.alt, alt) != 0) return false;
      if (Double.compare(that.lat, lat) != 0) return false;
      if (Double.compare(that.lon, lon) != 0) return false;
      if (desc != null ? !desc.equals(that.desc) : that.desc != null) return false;
      if (!name.equals(that.name)) return false;
      if (wmoId != null ? !wmoId.equals(that.wmoId) : that.wmoId != null) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result;
      long temp;
      temp = Double.doubleToLongBits(lat);
      result = (int) (temp ^ (temp >>> 32));
      temp = Double.doubleToLongBits(lon);
      result = 31 * result + (int) (temp ^ (temp >>> 32));
      temp = Double.doubleToLongBits(alt);
      result = 31 * result + (int) (temp ^ (temp >>> 32));
      result = 31 * result + name.hashCode();
      result = 31 * result + (desc != null ? desc.hashCode() : 0);
      result = 31 * result + (wmoId != null ? wmoId.hashCode() : 0);
      return result;
    }

  }

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

  public class FieldConverter implements BufrField {
    DataDescriptor dds;
    List<FieldConverter> flds;
    BufrCdmIndexProto.FldType type;
    BufrCdmIndexProto.FldAction action;
    int min = Integer.MAX_VALUE;
    int max = 0;
    boolean isSeq;

    private FieldConverter(int center, DataDescriptor dds) {
      this.dds = dds;
      this.type = StandardFields.findField(center, dds.getFxyName());

      if (dds.getSubKeys() != null) {
        this.flds = new ArrayList<>(dds.getSubKeys().size());
        for (DataDescriptor subdds : dds.getSubKeys()) {
          FieldConverter subfld = new FieldConverter(center, subdds);
          flds.add(subfld);
        }
      }
    }

    public String getName() {
      return dds.getName();
    }

    public String getDesc() {
      return dds.getDesc();
    }

    public String getUnits() {
      return dds.getUnits();
    }

    public short getFxy() {
      return dds.getFxy();
    }

    public String getFxyName() {
      return dds.getFxyName();
    }

    public BufrCdmIndexProto.FldAction getAction() {
      return action;
    }

    public BufrCdmIndexProto.FldType getType() {
      return type;
    }

    public List<FieldConverter> getChildren() {
      return flds;
    }

    public boolean isSeq() {
      return isSeq;
    }

    public int getMin() {
      return min;
    }

    public int getMax() {
      return max;
    }

    public int getScale() {
      return dds.getScale();
    }

    public int getReference() {
      return dds.getRefVal();
    }

    public int getBitWidth() {
      return dds.getBitWidth();
    }

    public void setAction(String action) {
      BufrCdmIndexProto.FldAction act = BufrCdmIndexProto.FldAction.valueOf(action);
      if (act != null)
        this.action = act;
    }

    public void setAction(BufrCdmIndexProto.FldAction action) {
      this.action = action;
    }

    FieldConverter findChild(String want) {
      for (FieldConverter child : flds) {
        String name = child.dds.getName();
        if (name != null && name.equals(want))
          return child;
      }
      return null;
    }

    FieldConverter findChildByFxyName(String fxyName) {
      for (FieldConverter child : flds) {
        String name = child.dds.getFxyName();
        if (name != null && name.equals(fxyName))
          return child;
      }
      return null;
    }

    public FieldConverter getChild(int i) {
      return flds.get(i);
    }

    void trackSeqCounts(int n) {
      isSeq = true;
      if (n > max) max = n;
      if (n < min) min = n;
    }

    void showRange(Formatter f) {
      if (!isSeq) return;
      if (max == min) f.format(" isConstant='%d'", max);
      else if (max < 2) f.format(" isBinary='true'");
      else f.format(" range='[%d,%d]'", min, max);
    }

    BufrCdmIndexProto.FldAction makeAction() {
      if (!isSeq) return null;
      if (max == 0) return BufrCdmIndexProto.FldAction.remove;
      if (max < 2) return BufrCdmIndexProto.FldAction.asMissing;
      else return BufrCdmIndexProto.FldAction.asArray;
    }

    void show(Formatter f, Indent indent, int index) {
      boolean hasContent = false;
      if (isSeq)
        f.format("%s<fld idx='%d' name='%s'", indent, index, dds.getName());
      else
        f.format("%s<fld idx='%d' fxy='%s' name='%s' desc='%s' units='%s' bits='%d'", indent, index,
                dds.getFxyName(), dds.getName(), dds.getDesc(), dds.getUnits(), dds.getBitWidth());

      if (type != null) f.format(" type='%s'", type);
      showRange(f);
      f.format(" action='%s'", makeAction());

      /* if (type != null) {
        f.format(">%n");
        indent.incr();
        f.format("%s<type>%s</type>%n", indent, type);
        indent.decr();
        hasContent = true;
      } */

      if (flds != null) {
        f.format(">%n");
        indent.incr();
        int subidx = 0;
        for (FieldConverter cc : flds) {
          cc.show(f, indent, subidx++);
        }
        indent.decr();
        hasContent = true;
      }

      if (hasContent)
        f.format("%s</fld>%n", indent);
      else
        f.format(" />%n");
    }
  }

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

  public void show(Formatter out) {
    if (standardFields != null)
      out.format("Standard Fields%n%s%n%n", standardFields);

    Indent indent = new Indent(2);
    out.format("<bufr2nc location='%s' hash='%s' featureType='%s'>%n", filename, Integer.toHexString(messHash), featureType);
    indent.incr();
    int index = 0;
    for (FieldConverter fld : rootConverter.flds) {
      fld.show(out, indent, index++);
    }
    indent.decr();
    out.format("</bufr2nc>%n");
}

  public static void main(String[] args) throws IOException {

    String filename = "G:/work/manross/split/872d794d.bufr";
    //String filename = "Q:/cdmUnitTest/formats/bufr/US058MCUS-BUFtdp.SPOUT_00011_buoy_20091101021700.bufr";
    try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
      BufrConfig config = BufrConfig.scanEntireFile(raf);
      Formatter out = new Formatter();
      config.show(out);
      System.out.printf("%s%n", out);
    }
  }
}


/* Copyright */
package thredds.server.catalog.tracker;

import thredds.server.catalog.DataRoot;

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

/**
 * Externalized DataRoot.
 *
 * @author caron
 * @since 6/16/2015
 */
public class DataRootExt implements Comparable<DataRootExt> {
  static public int total_count = 0;
  static public long total_nbytes = 0;
  static private final boolean debug = false;

  private String path;
  private String dirLocation;
  private DataRoot.Type type;
  private String catLocation;
  private String name;
  private String restrict;

  private DataRoot dataRoot;

  public DataRootExt() {
  }

  public DataRootExt(DataRoot dataRoot, String catLocation) {
    this.path = dataRoot.getPath();
    this.dataRoot = dataRoot;
    this.dirLocation = dataRoot.getDirLocation();
    this.type = dataRoot.getType();
    this.name = dataRoot.getName();
    this.catLocation = catLocation;
    this.restrict = dataRoot.getRestrict();
  }

  public String getPath() {
    return path;
  }

  public DataRoot getDataRoot() {
    if (dataRoot != null) return dataRoot;
    if (type == DataRoot.Type.datasetRoot) {
      dataRoot = new DataRoot(path, dirLocation, restrict);
    }
    return dataRoot;
  }

  public void setDataRoot(DataRoot dataRoot) {
    this.dataRoot = dataRoot;
    this.restrict = dataRoot.getRestrict();
  }

  public String getCatLocation() {
    return catLocation;
  }

  public String getDirLocation() {
    return dirLocation;
  }

  public DataRoot.Type getType() {
    return type;
  }

  public String getName() {
    return name;
  }

  public String getRestrict() {
    return restrict;
  }

  /* message DataRoot {
        required string urlPath = 1;
        required string dirLocation = 2;
        required DataRootType type = 3;
        optional string catLocation = 4;    // omit for simple dataset root
      } */
  public void writeExternal(DataOutputStream out) throws IOException {
    ConfigCatalogExtProto.DataRoot.Builder builder = ConfigCatalogExtProto.DataRoot.newBuilder();
    builder.setUrlPath(path);
    builder.setDirLocation(dirLocation);
    builder.setType(convertDataRootType(type));
    if (type != DataRoot.Type.datasetRoot) {
      builder.setCatLocation(catLocation);
      builder.setName(name);
    }
    if (restrict != null)
      builder.setRestrict(restrict);

    ConfigCatalogExtProto.DataRoot index = builder.build();
    byte[] b = index.toByteArray();
    out.writeInt(b.length);
    out.write(b);

    total_count++;
    total_nbytes += b.length + 4;
    if (debug) System.out.printf(" %d: DataRootExt.writeExternal len=%d total=%d%n", total_count, b.length, total_nbytes);
  }

  private static int nrecordsRead = 0;

  public void readExternal(DataInputStream in) throws IOException {
    int avail = in.available();
    int len = in.readInt();
    if (debug) System.out.printf(" %d: DataRootExt.readExternal len=%d%n", nrecordsRead++, len);

    byte[] b = new byte[len];
    int n = in.read(b);
    //System.out.printf(" read size = %d%n", b.length);

    //try {
    if (n != len)
      throw new RuntimeException("DataRootExt.readExternal failed read size=" + len + " in.available=" + avail);

    ConfigCatalogExtProto.DataRoot dsp = ConfigCatalogExtProto.DataRoot.parseFrom(b);
    this.path = dsp.getUrlPath();
    this.dirLocation = dsp.getDirLocation();
    this.type = convertDataRootType( dsp.getType());
    if (dsp.getCatLocation().length() > 0)
      catLocation = dsp.getCatLocation();
    if (dsp.getName().length() > 0)
      name = dsp.getName();
    if (dsp.getRestrict().length() > 0)
      restrict = dsp.getRestrict();
  }

  ////////////////////////////
    //  Type {datasetRoot, datasetScan, catalogScan, featureCollection}

  static public DataRoot.Type convertDataRootType(ConfigCatalogExtProto.DataRootType type) {
    switch (type) {
      case datasetRoot:
        return DataRoot.Type.datasetRoot;
      case datasetScan:
        return DataRoot.Type.datasetScan;
      case catalogScan:
        return DataRoot.Type.catalogScan;
      case featureCollection:
        return DataRoot.Type.featureCollection;
    }
    throw new IllegalStateException("illegal DataRootType " + type);
  }

  static public ConfigCatalogExtProto.DataRootType convertDataRootType(DataRoot.Type type) {
     switch (type) {
       case datasetRoot:
         return ConfigCatalogExtProto.DataRootType.datasetRoot;
       case datasetScan:
         return ConfigCatalogExtProto.DataRootType.datasetScan;
       case catalogScan:
         return ConfigCatalogExtProto.DataRootType.catalogScan;
       case featureCollection:
         return ConfigCatalogExtProto.DataRootType.featureCollection;
     }
     throw new IllegalStateException("illegal DataRootType " + type);
   }

  @Override
  public int compareTo(DataRootExt o) {
    return path.compareTo( o.getPath());
  }

  @Override
  public String toString() {
    Formatter f = new Formatter();
    f.format("%s -> %s; type=%s", path, dirLocation, type);
    if (restrict != null)
      f.format("; restrict=%s", restrict);
    return f.toString();
  }
}


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

package ucar.nc2.iosp.mcidas;

import edu.wisc.ssec.mcidas.*;
import ucar.nc2.iosp.grid.*;

/**
 * Class to hold the grid navigation information.
 *
 * @author dmurray
 */
public class McGridDefRecord extends GridDefRecord {

  /**
   * raw values
   */
  int[] vals = null;

  /**
   * projection type
   */
  //private String proj;

  /**
   * Navigation type for pseudo-mercator grids
   */
  private static final int PSEUDO_MERCATOR = 1;

  /**
   * Navigation type for polar stero or lambert conformal conic grids
   */
  private static final int PS_OR_LAMBERT_CONIC = 2;

  /**
   * Navigation type for equidistant grids
   */
  private static final int EQUIDISTANT = 3;

  /**
   * Navigation type for pseudo-mercator (general case) grids
   */
  private static final int PSEUDO_MERCATOR_GENERAL = 4;

  /**
   * no navigation
   */
  private static final int NO_NAV = 5;

  /**
   * Navigation type for lambert conformal tangent grids
   */
  private static final int LAMBERT_CONFORMAL_TANGENT = 6;

  /**
   * Earth radius
   */
 // private static final double EARTH_RADIUS = 6371.23;

  /**
   * Create a new grid nav block
   */
  public McGridDefRecord() {
  }

  /**
   * Create a new grid nav block with the values
   *
   * @param words analysis block values
   */
  public McGridDefRecord(int[] words) {
    setValues(words);
  }

  /**
   * Set the grid nav block values
   *
   * @param values the raw values
   */
  public void setValues(int[] values) {
    vals = values;
    setParams();
    addParam(GDS_KEY, this.toString());
  }

  /**
   * Print out a string representation of this
   *
   * @return a String representation of this.
   */
  public String toString() {
    // TODO: make this more unique
    return getParam(PROJ) + " X:" + getParam(NX) + " " + "Y:" + getParam(NY);
  }

  /**
   * Check for equality
   *
   * @param o object to compare
   * @return true if equal
   */
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof McGridDefRecord)) {
      return false;
    }
    return this.toString().equals(o.toString());
  }

  /**
   * Get the hashcode
   *
   * @return the hashcode
   */
  public int hashCode() {
    return toString().hashCode();
  }

    /* common calculation variables
    private int navType;
    private double xnr;             // number of rows
    private double xnc;             // number of columns
    private double xnrow;           // number of rows for calculations
    private double xncol;           // number of columns for calculations
    private boolean wierd = false;  // JTYP = 1, navType > 10
    */

    /* Merc and pseudo_merc parameters
    private double glamx;           // max latitude
    private double glomx;           // max longitude
    private double ginct;           // grid increment in latitude
    private double gincn;           // grid increment in longitude
    */

    /* PS and CONF projection parameters
    private double xrowi;           // row # of North Pole*10000
    private double xcoli;           // column # of North Pole* 10000
    private double xqlon;           // longitude parallel to columns
    private double xspace;          // column spacing at standard latitude
    private double xh;              //
    private double xfac;            //
    private double xblat;           //
    */

    /* Equidistant params
    private double xrot;            // rotation angle
    private double yspace;
    private double xblon;
    */

  /** static debug count */
  // private static int cnt = 0;

  /**
   * Set the parameters for the GDS
   */
  private void setParams() {

        /* PS and CONF projection parameters */
    double xrowi;   // row # of North Pole*10000
    double xcoli;   // column # of North Pole* 10000
    double xqlon;   // longitude parallel to columns
    double xspace;  // column spacing at standard latitude
    double yspace;  // row spacing at standard latitude
    double xt1;     // standard latitude
    double xt2;     // 2nd standard latitude
    double xh;      //
    double xfac;    //
    double xblat;   //
        /* Merc and pseudo_merc parameters */
    double glamx;  // max latitude
    double glomx;  // max longitude
    double glamn;  // min latitude
    double glomn;  // min longitude
    double ginct;  // grid increment in latitude
    double gincn;  // grid increment in longitude

    try {
      GRIDnav nav = new GRIDnav(vals);

      int gridType = vals[33];
      int navType = gridType % 10;
      addParam("Proj", String.valueOf(navType));
      boolean wierd = gridType / 10 == 1;
      int ny = vals[1];
      int nx = vals[2];
      addParam(PROJ, getProjName(navType));
      addParam(NX, String.valueOf(nx));
      addParam(NY, String.valueOf(ny));
      double[][] input;
      if (nav.isFlippedRowCoordinates()) {
        input = new double[][]{{1, nx}, {1, ny}};
      } else {
        input = new double[][]{{1, nx}, {ny, 1}};
      }
      double[][] llur = nav.toLatLon(input);
            /*
            if (cnt == 0 && navType == 6) {
                System.out.println("Proj = " + getProjName(navType) + ":" + navType);
                System.out.println("isFlipped = " + nav.isFlippedRowCoordinates());
                ucar.unidata.util.Misc.printArray("input x", input[0]);
                ucar.unidata.util.Misc.printArray("input y", input[1]);
                ucar.unidata.util.Misc.printArray("lats", llur[0]);
                ucar.unidata.util.Misc.printArray("lons", llur[1]);
                cnt++;
            }
            */
      addParam(LA1, String.valueOf(llur[0][0]));
      addParam(LO1, String.valueOf(llur[1][0]));
      addParam(LA2, String.valueOf(llur[0][1]));
      addParam(LO2, String.valueOf(llur[1][1]));

      switch (navType) {

        case PSEUDO_MERCATOR:
        case PSEUDO_MERCATOR_GENERAL:
          glamx = vals[34] / 10000.;
          glomx = -vals[35] / 10000.;
          glamn = vals[34] / 10000.;
          glomn = -vals[35] / 10000.;
          ginct = vals[38] / 10000.;
          gincn = (navType == PSEUDO_MERCATOR_GENERAL)
                  ? vals[39] / 10000.
                  : ginct;
          addParam("Latin", String.valueOf(20));
          //addParam(DX, String.valueOf(gincn));
          //addParam(DY, String.valueOf(ginct));
                  /*
                  if (wierd) {
                    double x = xnr;
                    xnr = xnc;
                    xnc = x;
                  }
                  */
          break;

        case PS_OR_LAMBERT_CONIC:
          xrowi = vals[34] / 10000.;  // row # of the North pole*10000
          xcoli = vals[35] / 10000.;  // col # of the North pole*10000
          xspace = vals[36];  // column spacing at standard lat (m)
          xqlon = -vals[37] / 10000.;  // lon parallel to cols (deg*10000)
          xt1 = vals[38] / 10000.;  // first standard lat
          xt2 = vals[39] / 10000.;  // second standard lat
          addParam(LATIN1, String.valueOf(xt1));
          addParam(LOV, String.valueOf(xqlon));
          addParam(LATIN2, String.valueOf(xt2));
          addParam(DX, String.valueOf(xspace));
          addParam(DY, String.valueOf(xspace));
                  /*
                  xh = (xt1 >= 0) ? 1. : -1.;
                  xt1 =(90.-xh*xt1)*xrad;
                  xt2 =(90.-xh*xt2)*xrad;
                  xfac =1.0;
                  if (xt1 != xt2)
                     xfac = (Math.log(Math.sin(xt1))-Math.log(Math.sin(xt2)))/
                            (Math.log(Math.tan(.5*xt1))-Math.log(Math.tan(.5*xt2)));
                  xfac = 1.0/xfac;
                  xblat = 6370. * Math.sin(xt1)/
                           (xspace*xfac*(Math.pow(Math.tan(xt1*.5),xfac)));
                  if (wierd) {
                     double x=xnr;
                     xnr=xnc;
                     xnc=x;
                     x=xcoli;
                     xcoli=xrowi;
                     xrowi=xnr-x+1.0;
                     xqlon=xqlon+90.;
                  }
                  */

          break;

        case EQUIDISTANT:
          xrowi = 1.;
          xcoli = 1.;
          glamx = vals[34] / 10000.;   // lat of (1,1) degrees*10000
          glomx = -vals[35] / 10000.;  // lon of (1,1) degrees*10000
          //xrot  = -xrad*vals[36]/10000.; // clockwise rotation of col 1
          xspace = vals[37] / 1000.;   // column spacing
          yspace = vals[38] / 1000.;   // row spacing
          addParam(LA1, String.valueOf(glamx));
          addParam(LO1, String.valueOf(glomx));
          addParam(DX, String.valueOf(xspace));
          addParam(DY, String.valueOf(yspace));
                  /*
                  xblat = EARTH_RADIUS*xrad/yspace;
                  xblon = EARTH_RADIUS*xrad/xspace;

                  if (wierd) {
                    double x = xnr;
                    xnr = xnc;
                    xnc = x;
                  }
                  */

          break;

        case LAMBERT_CONFORMAL_TANGENT:
          xrowi = vals[34] / 10000.;  // lat at (1,1)
          xcoli = vals[35] / 10000.;  // lon at (1,1)
          xspace = vals[36];  // column spacing at standard lat (m)
          xqlon = -vals[37] / 10000.;  // lon parallel to cols (deg*10000)
          xt1 = vals[38] / 10000.;  // standard lat 1
          xt2 = vals[39] / 10000.;  // standard lat 2
          if (Double.compare(xt2, (double) McIDASUtil.MCMISSING) == 0 || (xt2 == 0)) { // LOOK suspicious floating point point compare
            xt2 = xt1;
          }
          addParam(LATIN1, String.valueOf(xt1));
          addParam(LOV, String.valueOf(xqlon));
          addParam(LATIN2, String.valueOf(xt2));
          addParam(DX, String.valueOf(xspace));
          addParam(DY, String.valueOf(xspace));
                  /*
                  xh = (xt1 >= 0) ? 1. : -1.;
                  xt1 = (90. - xh * xt1) * xrad;
                  xfac = Math.cos(xt1);
                  xblat = EARTH_RADIUS * Math.tan(xt1) /
                             (xspace * Math.pow(Math.tan(xt1*.5), xfac));
                             */

          break;

        default:
          break;
      }
    } catch (McIDASException me) {
      System.out.println("couldn't set nav");
    }

  }

  /**
   * Get a short name for this GDSKey for the netCDF group.
   * Subclasses should implement as a short description
   *
   * @return short name
   */
  public String getGroupName() {
    return getParam(PROJ) + "_" + getParam(NX) + "x" + getParam(NY);
  }

  /**
   * Get the name for the projection type
   *
   * @param type the projection type
   * @return a String name for the type
   */
  public String getProjName(int type) {
    String projName;
    switch (type) {

      case PSEUDO_MERCATOR:
      case PSEUDO_MERCATOR_GENERAL:
        projName = "MERC";
        break;

      case PS_OR_LAMBERT_CONIC:
        projName = (vals[38] == vals[39])
                ? "PS"
                : "CONF";
        break;

      case EQUIDISTANT:
        projName = "EQUI";
        break;

      case LAMBERT_CONFORMAL_TANGENT:
        projName = "CONF";
        break;

      default:
        projName = "NAV" + type;
    }
    return projName;

  }

}


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

import ucar.httpservices.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.util.EscapeStrings;
import ucar.nc2.util.IO;
import ucar.nc2.util.ListenerManager;

import java.net.*;
import java.io.*;

/**
 * Starts up a server socket on the given port, and listens for messages sent to it.
 * Sends the contents of the message to anyone who is registered as a listener.
 */
public final class SocketMessage {
  static private final boolean debug = false, throwAway = false;
  static private boolean raw = false;

  private ServerSocket server;
  private boolean isAlreadyRunning = false;
  private ListenerManager lm;

  /**
   * Try to start a listener on the given port. If that port is already used, send the given message to it.
   * @param port listen on this server port.
   * @param message send this message if port in use
   */
  public SocketMessage(int port, String message) {

    try {
      server = new ServerSocket(port, 1);
      if (debug) System.out.println("SocketMessage started on port " + server.getLocalPort());
      Thread listen = new ListenThread();
      listen.start();

      // manage Event Listener's
      lm = new ListenerManager(
        "ucar.nc2.ui.util.SocketMessage$EventListener",
        "ucar.nc2.ui.util.SocketMessage$Event",
        "setMessage");

    } catch (java.net.BindException e) {
      if (message != null)
        sendMessage( port, message);
      isAlreadyRunning = true;

    } catch (java.net.SocketException e) {
      if (message != null)
        sendMessage( port, message);
      isAlreadyRunning = true;

    } catch (IOException e) {
      System.out.println("SocketMessage IOException= " + e);
      e.printStackTrace();
    }
  }

  /**
   * Was the port already in use?
   * @return true if the port was already in use
   */
  public boolean isAlreadyRunning() { return isAlreadyRunning; }

  /**
   * Add a EventListener
   * @param l the listener
   */
  public void addEventListener( EventListener l) {
    lm.addListener( l);
  }

  /**
   * Remove an EventListener.
   * @param l the listener
   */
  public void removeEventListener( EventListener l) {
    lm.removeListener(l);
  }

  // public void exit() { listen.exit(); }

  private void sendMessage(int port, String message) {
    Socket connection = null;
    try {
      connection = new Socket("localhost", port);
      IO.writeContents(message, connection.getOutputStream());
      if (debug) System.out.println(" sent message " + message);

    }  catch (IOException e) {
      System.err.println(e);
      e.printStackTrace();

    } finally {
      try { if (connection != null) connection.close(); }
      catch (IOException e) {}
    }

  }

  private class ListenThread extends Thread {
    String message;

    public void run()  {

      while (true) {
        Socket connection;
        if (debug) System.out.println("Listening for connections on port " + server.getLocalPort());
        try {
          connection = server.accept();

        } catch (IOException e) {
          System.out.println("SocketMessage accept= " + e);
          e.printStackTrace();
          return;
        }

        try {
          if (debug) System.out.println("SocketMessage Connection established with " + connection);
          if (throwAway) {
            IO.copy2null(connection.getInputStream(), -1);
            /* long count = IO.writeToFile(connection.getInputStream(), "C:/temp/save");
            if (debug) System.out.println("SocketMessage had length " + count/(1000*1000)+" Mb"); */
          } else if (raw) {
            InputStream in = connection.getInputStream();
            byte[] buffer = new byte[8000];
            int bytesRead = in.read(buffer);
            System.out.printf("%s == %s%n", bytesRead, new String(buffer,0,bytesRead, CDM.utf8Charset));
          } else {
            message = IO.readContents(connection.getInputStream());
            if (debug) System.out.println(" SocketMessage got message= "+message);
            lm.sendEvent(new Event( message));
          }

        } catch (IOException e) {
          System.out.println("SocketMessage IOException reading= " + e);
          e.printStackTrace();

        } finally {
          try {
            if (connection != null) connection.close();
            if (debug) System.out.println("connection done ");
          }
          catch (IOException e) { } // client closed first
        }

      } // loop
    } // run
  } // ListenThread

  public class Event extends java.util.EventObject {
    private String message;

    Event(String message) {
      super(SocketMessage.this);
      this.message = message;
    }

    public String getMessage() { return message; }
  }

  public static interface EventListener {
     public void setMessage(SocketMessage.Event event);
  }

  //////////////////////////////////////////////
  public static void main(String[] args) throws IOException {
    if (false) {
      new SocketMessage( 9999, "startNewServer");
      raw = true;

    } else {
      String url = "http://localhost:8080/thredds/test/it" // + EscapeStrings.escapeOGC("yabba/bad[0]/good")
               +"?"+EscapeStrings.escapeOGC("quuery[1]");
      System.out.printf("send '%s'%n", url);
      try (HTTPMethod method = HTTPFactory.Head(url)) {
          method.execute();
          int status = method.getStatusCode();
          System.out.printf("%d%n", status);
      } // close method, close method internal session
    }
  }
}


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

package ucar.nc2.ui.geoloc;

import java.awt.*;
import java.awt.geom.*;

/** Rectangle Rubberbanding.
 * @author David M. Geary
 * @author John Caron
 */
public class RubberbandRectangleHandles extends Rubberband {
  static public double handleSizePixels = 6.0;

  private Rectangle current;

  public RubberbandRectangleHandles(Component c, boolean listen) {
    super( c, listen);
  }

  public void setRectangle(Rectangle current) {
    this.current = current;
  }

  /**
   * Erase the last rectangle and shift the rectangle and redraw
   * deltax, deltay: position from original position
   */
  public void move(int deltax, int deltay) {
    lastPt.x      = stretchedPt.x;
    lastPt.y      = stretchedPt.y;

    Graphics2D g = (Graphics2D) component.getGraphics();
    if(g != null) {
     try {
       g.setXORMode(component.getBackground());
       if(firstStretch == true)
         firstStretch = false;
       else
         drawLast(g);

       anchorPt.x = current.x + deltax;
       anchorPt.y = current.y + deltay;
       stretchedPt.x = current.x + current.width + deltax;
       stretchedPt.y = current.y + current.height + deltay;
       drawNext(g);
     }
     finally {
       g.dispose();
     } // try
    } // if
  }

  public boolean anchor(Point p) {
    if (current == null) return false;

    // have to decide which handle is closest, and use opposite corner
    minDiff = Integer.MAX_VALUE;
    testDiff( p, current.x, current.y, current.x + current.width, current.y + current.height);
    testDiff( p, current.x + current.width, current.y, current.x, current.y + current.height);
    testDiff( p, current.x, current.y + current.height, current.x + current.width, current.y);
    testDiff( p, current.x + current.width, current.y + current.height, current.x, current.y);

    // System.out.println(" anchor diff = "+minDiff);
    if (minDiff > 100) return false;

    stretchedPt.x = lastPt.x = p.x;
    stretchedPt.y = lastPt.y = p.y;

    firstStretch = true;
    return true;
  }

  private int minDiff = 0;
  private void testDiff( Point p, int x, int y, int anchor_x, int anchor_y) {
    int dx = p.x - x;
    int dy = p.y - y;
    int diff = dx*dx + dy*dy;
    if (diff < minDiff) {
      minDiff = diff;
      anchorPt.x = anchor_x;
      anchorPt.y = anchor_y;
    }
  }

  public void drawLast(Graphics2D graphics) {
    drawHandledRect( graphics, lastBounds(), handleSizePixels);
    //System.out.println("==RBRH last= "+lastBounds());
  }

  public void drawNext(Graphics2D graphics) {
    drawHandledRect( graphics, getBounds(), handleSizePixels);
    //System.out.println("==RBRH draw= "+getBounds());
  }

  static public void drawHandledRect(Graphics2D graphics, Rectangle2D rect, double handleSize) {
    double x = rect.getX();
    double y = rect.getY();
    double w = rect.getWidth();
    double h = rect.getHeight();

    graphics.draw( rect);
    Rectangle2D hr = new Rectangle2D.Double();

    // corners
    hr.setRect(x - handleSize/2, y - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x + w - handleSize/2, y - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x - handleSize/2, y + h - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x + w - handleSize/2, y + h - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);

    /* sides
    hr.setRect(x + w/2 - handleSize/2, y - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x - handleSize/2, y + h/2 - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x + w - handleSize/2, y + h/2 - handleSize/2, handleSize, handleSize);
    graphics.fill( hr);
    hr.setRect(x + w/2 - handleSize/2, y + h - handleSize/2, handleSize, handleSize);
    graphics.fill( hr); */
  }

}


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

package ucar.nc2.ui.op;

import ucar.ma2.StructureData;
import ucar.ma2.StructureDataIterator;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFileSubclass;
import ucar.nc2.Structure;
import ucar.nc2.Variable;
import ucar.nc2.constants.CDM;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.SequenceDS;
import ucar.nc2.ft.point.bufr.BufrCdmIndex;
import ucar.nc2.ft.point.bufr.BufrCdmIndexProto;
import ucar.nc2.ft.point.bufr.StandardFields;
import ucar.nc2.iosp.bufr.*;
import ucar.nc2.iosp.bufr.writer.Bufr2Xml;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.ui.widget.*;
import ucar.nc2.ui.widget.PopupMenu;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.BeanTable;

import ucar.nc2.ui.StructureTable;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import javax.swing.*;

//import ucar.util.GoogleDiff;

/**
 * ToolsUI/Iosp/Bufr
 *
 * @author caron
 * @since Aug 15, 2008
 */
public class BufrMessageViewer extends JPanel {

  private PreferencesExt prefs;

  private BeanTable messageTable, obsTable, ddsTable;
  private JSplitPane split, split2;

  private TextHistoryPane infoTA, infoTA2;
  private IndependentWindow infoWindow, infoWindow2;

  private StructureTable dataTable;
  private IndependentWindow dataWindow;
  private FileManager fileChooser;
  private boolean seperateWindow = false;

  public BufrMessageViewer(final PreferencesExt prefs, JPanel buttPanel) {
    this.prefs = prefs;

    AbstractButton tableButt = BAMutil.makeButtcon("Structure", "Data Table", false);
    tableButt.addActionListener(e -> {
        try {
          NetcdfFile ncd = makeBufrDataset() ;
          Variable v = ncd.findVariable(BufrIosp2.obsRecord);
          if ((v != null) && (v instanceof Structure)) {
            if (dataTable == null) makeDataTable();
            dataTable.setStructure((Structure) v);
            dataWindow.show();
          }
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(BufrMessageViewer.this, ex.getMessage());
          ex.printStackTrace();
        }
    });
    buttPanel.add(tableButt);

    AbstractButton showButt = BAMutil.makeButtcon("GetAll", "Read All Data", false);
    showButt.addActionListener(e -> {
        Formatter f = new Formatter();
        readData(f);
        infoTA2.setText(f.toString());
        infoTA2.gotoTop();
        infoWindow2.show();
    });
    buttPanel.add(showButt);

    AbstractAction seperateWindowAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        seperateWindow = (Boolean) getValue(BAMutil.STATE);
      }
    };
    BAMutil.setActionProperties(seperateWindowAction, "DrawVert", "seperate DDS window", true, 'C', -1);
    seperateWindowAction.putValue(BAMutil.STATE, Boolean.valueOf(seperateWindow));
    BAMutil.addActionToContainer(buttPanel, seperateWindowAction);

    AbstractButton distinctDdsButt = BAMutil.makeButtcon("dd", "Dump distinct DDS", false);
    distinctDdsButt.addActionListener(e -> {
        dumpDDS();
    });
    buttPanel.add(distinctDdsButt);

    AbstractButton distinctMessButt = BAMutil.makeButtcon("Import", "Write distinct messages", false);
    distinctMessButt.addActionListener(e -> {
        writeAll();
    });
    buttPanel.add(distinctMessButt);

    AbstractButton configButt = BAMutil.makeButtcon("Dump", "Make BufrConfig", false);
    configButt.addActionListener(e -> {
        if (raf == null) return;
        try {
          BufrConfig config = BufrConfig.scanEntireFile(raf);
          Formatter out = new Formatter();
          config.show(out);
          infoTA2.setText(out.toString());

        } catch (Exception ex) {
          ex.printStackTrace();
          StringWriter sw = new StringWriter(10000);
          ex.printStackTrace(new PrintWriter(sw));
          infoTA2.setText(sw.toString());
        }
        infoTA2.gotoTop();
        infoWindow2.show();
    });
    buttPanel.add(configButt);

    AbstractButton writeButton = BAMutil.makeButtcon("V3", "Write index", false);
    writeButton.addActionListener(e -> {
        Formatter f = new Formatter();
        try {
          if (writeIndex(f)) {
            f.format("Index written");
            infoTA2.setText(f.toString());
          }

        } catch (Exception ex) {
          ex.printStackTrace();
          StringWriter sw = new StringWriter(10000);
          ex.printStackTrace(new PrintWriter(sw));
          infoTA2.setText(sw.toString());
        }
        infoTA2.gotoTop();
        infoWindow2.show();
    });
    buttPanel.add(writeButton);

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

    messageTable = new BeanTable(MessageBean.class, (PreferencesExt) prefs.node("GridRecordBean"), false);
    messageTable.addListSelectionListener(e -> {
        ddsTable.setBeans(new ArrayList());
        obsTable.setBeans(new ArrayList());

        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        List<DdsBean> beanList = new ArrayList<>();
        try {
          setDataDescriptors(beanList, mb.m.getRootDataDescriptor(), 0);
          setObs(mb.m);
        } catch (Exception e1) {
          JOptionPane.showMessageDialog(BufrMessageViewer.this, e1.getMessage());
          e1.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        ddsTable.setBeans(beanList);
    });

    obsTable = new BeanTable(ObsBean.class, (PreferencesExt) prefs.node("ObsBean"), false);
    /* obsTable.addListSelectionListener(e -> {
        obsTable.getSelectedBean();
    }); */

    ddsTable = new BeanTable(DdsBean.class, (PreferencesExt) prefs.node("DdsBean"), false);
    /* ddsTable.addListSelectionListener(e -> {
        ddsTable.getSelectedBean();
    });  */

    PopupMenu varPopup = new PopupMenu(messageTable.getJTable(), "Options");
    varPopup.addAction("Show DDS", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean vb = (MessageBean) messageTable.getSelectedBean();
        if (vb == null) return;
        if (!seperateWindow) infoTA.clear();
        Formatter f = new Formatter();
        try {
          if (!vb.m.isTablesComplete()) {
            f.format(" MISSING DATA DESCRIPTORS= ");
            vb.m.showMissingFields(f);
            f.format("%n%n");
          }

          vb.m.dump(f);
        } catch (IOException e1) {
          JOptionPane.showMessageDialog(BufrMessageViewer.this, e1.getMessage());
          e1.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        if (seperateWindow) {
          TextHistoryPane ta = new TextHistoryPane();
          IndependentWindow info = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), ta);
          info.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));
          ta.appendLine(f.toString());
          ta.gotoTop();
          info.show();

        } else {
          infoTA.appendLine(f.toString());
          infoTA.gotoTop();
          infoWindow.show();
        }
      }
    });

    varPopup.addAction("Data Table", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        try {
          NetcdfFile ncd = makeBufrMessageAsDataset(mb.m);
          Variable v = ncd.findVariable(BufrIosp2.obsRecord);
          if ((v != null) && (v instanceof Structure)) {
            if (dataTable == null) makeDataTable();
            dataTable.setStructure((Structure) v);
            dataWindow.show();
            mb.setReadOk(true);
          }
        } catch (Exception ex) {
          mb.setReadOk(false);
          JOptionPane.showMessageDialog(BufrMessageViewer.this, ex.getMessage());
          ex.printStackTrace();
        }
      }
    });

    varPopup.addAction("BitCount", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        mb.checkBits();
      }
    });

    varPopup.addAction("Bit Count Details", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        Message m = mb.m;

        Formatter out = new Formatter();
        try {
          infoTA2.clear();
          if (!m.dds.isCompressed()) {
            MessageUncompressedDataReader reader = new MessageUncompressedDataReader();
            reader.readData(null, m, raf, null, false, out);
          } else {
            MessageCompressedDataReader reader = new MessageCompressedDataReader();
            reader.readData(null, m, raf, null, out);
          }
          int nbitsGiven = 8 * (m.dataSection.getDataLength() - 4);
          DataDescriptor root = m.getRootDataDescriptor();
          out.format("Message nobs=%d compressed=%s vlen=%s countBits= %d givenBits=%d %n",
                  m.getNumberDatasets(), m.dds.isCompressed(), root.isVarLength(),
                  m.getCountedDataBits(), nbitsGiven);
          out.format(" countBits= %d givenBits=%d %n", m.getCountedDataBits(), nbitsGiven);
          out.format(" countBytes= %d dataSize=%d %n", m.getCountedDataBytes(), m.dataSection.getDataLength());
          out.format("%n");
          infoTA2.appendLine(out.toString());

        } catch (Exception ex) {
          StringWriter sw = new StringWriter(5000);
          ex.printStackTrace(new PrintWriter(sw));
          infoTA2.appendLine(out.toString());
          infoTA2.appendLine(sw.toString());
        }

        infoTA2.gotoTop();
        infoWindow2.show();
      }
    });

    varPopup.addAction("Read", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        mb.read();
      }
    });

    varPopup.addAction("Write Message", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        try {
          String defloc;
          String header = mb.m.getHeader();
          if (header != null) {
            header = header.split(" ")[0];
          }
          if (header == null) {
            defloc = (raf.getLocation() == null) ? "." : raf.getLocation();
            int pos = defloc.lastIndexOf(".");
            if (pos > 0)
              defloc = defloc.substring(0, pos);
          } else
            defloc = header;

          makeFileChooser();
          String filename = fileChooser.chooseFilenameToSave(defloc + ".bufr");
          if (filename == null) return;

          File file = new File(filename);
          FileOutputStream fos = new FileOutputStream(file);
          WritableByteChannel wbc = fos.getChannel();
          String headerS = mb.m.getHeader();
          if (headerS != null)
            wbc.write(ByteBuffer.wrap(headerS.getBytes(CDM.utf8Charset)));

          byte[] raw = scan.getMessageBytes(mb.m);
          wbc.write(ByteBuffer.wrap(raw));
          wbc.close();
          JOptionPane.showMessageDialog(BufrMessageViewer.this, filename + " successfully written");

        } catch (Exception ex) {
          JOptionPane.showMessageDialog(BufrMessageViewer.this, "ERROR: " + ex.getMessage());
          ex.printStackTrace();
        }
      }
    });

    varPopup.addAction("Show XML", new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        MessageBean mb = (MessageBean) messageTable.getSelectedBean();
        if (mb == null) return;
        Message m = mb.m;

        ByteArrayOutputStream out = new ByteArrayOutputStream(1000 * 100);
        try {
          infoTA.clear();

          NetcdfFile ncd = makeBufrMessageAsDataset(mb.m);
          new Bufr2Xml(m, ncd, out, true);
          infoTA.setText(out.toString(CDM.UTF8));

        } catch (Exception ex) {
          StringWriter sw = new StringWriter();
          ex.printStackTrace(new PrintWriter(sw));
          try {
            infoTA.appendLine(out.toString(CDM.UTF8));
          } catch (UnsupportedEncodingException uee) {
            uee.printStackTrace();
          }
          infoTA.appendLine(sw.toString());
        }

        infoTA.gotoTop();
        infoWindow.show();
      }
    });

      varPopup.addAction("Compare DDS", new AbstractAction() {
         public void actionPerformed(ActionEvent e) {
           List list = messageTable.getSelectedBeans();
           for (Object beano : list) {
             MessageBean bean = (MessageBean) beano;
             showDDS(bean.m);
           }
         }
       });

    // the info window
    infoTA = new TextHistoryPane();
    infoWindow = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), infoTA);
    infoWindow.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));

    // the info window 2
    infoTA2 = new TextHistoryPane();
    infoWindow2 = new IndependentWindow("Extra Information-2", BAMutil.getImage("netcdfUI"), infoTA2);
    infoWindow2.setBounds((Rectangle) prefs.getBean("InfoWindowBounds2", new Rectangle(300, 300, 500, 300)));

    split2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, ddsTable, obsTable);
    split2.setDividerLocation(prefs.getInt("splitPos2", 800));

    split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, messageTable, split2);
    split.setDividerLocation(prefs.getInt("splitPos", 500));

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

  private void makeFileChooser() {
    if (fileChooser == null)
      fileChooser = new FileManager(null, null, null, (PreferencesExt) prefs.node("FileManager"));
  }

  public boolean writeIndex(Formatter f) throws IOException {
    //MFileCollectionManager dcm = scanCollection(spec, f);

    File bufrFile = new File(raf.getLocation());
    String name = bufrFile.getName();
    int pos = name.lastIndexOf('/');
    if (pos < 0) pos = name.lastIndexOf('\\');
    if (pos > 0) name = name.substring(pos + 1);
    File def = new File(bufrFile.getParent(), name + BufrCdmIndex.NCX_IDX);

    makeFileChooser();
    String filename = fileChooser.chooseFilename(def);
    if (filename == null) return false;
    if (!filename.endsWith(BufrCdmIndex.NCX_IDX))
      filename += BufrCdmIndex.NCX_IDX;
    File idxFile = new File(filename);

    BufrConfig config = BufrConfig.scanEntireFile(raf);
    return BufrCdmIndex.writeIndex(raf.getLocation(), config, idxFile);
  }

  private void makeDataTable() {
    // the data Table
    dataTable = new StructureTable((PreferencesExt) prefs.node("structTable"));
    dataWindow = new IndependentWindow("Data Table", BAMutil.getImage("netcdfUI"), dataTable);
    dataWindow.setBounds((Rectangle) prefs.getBean("dataWindow", new Rectangle(50, 300, 1000, 600)));
  }

  private void writeAll() {
    List<MessageBean> beans = messageTable.getBeans();
    HashMap<Integer, Message> map = new HashMap<>(2 * beans.size());

    for (MessageBean mb : beans) {
      map.put(mb.m.hashCode(), mb.m);
    }

    makeFileChooser();
    String defloc = (raf.getLocation() == null) ? "." : raf.getLocation();
    String dirName = fileChooser.chooseDirectory(defloc);
    if (dirName == null) return;

    try {
      int count = 0;
      for (Message m : map.values()) {
        String header = m.getHeader();
        if (header != null) {
          header = header.split(" ")[0];
        } else {
          // header is the non-negative hash code of m. Note that using Math.abs() for this operation is a bug:
          // http://findbugs.blogspot.com/2006/09/is-mathabs-broken.html
          header = Integer.toString(m.hashCode() & Integer.MAX_VALUE);
        }

        File file = new File(dirName + "/" + header + ".bufr");
        try (FileOutputStream fos = new FileOutputStream(file);
             WritableByteChannel wbc = fos.getChannel()) {
          String headerS = m.getHeader();
          if (headerS != null)
            wbc.write(ByteBuffer.wrap(headerS.getBytes(CDM.utf8Charset)));
          byte[] raw = scan.getMessageBytes(m);
          wbc.write(ByteBuffer.wrap(raw));
        }
        count++;
      }
      JOptionPane.showMessageDialog(BufrMessageViewer.this, count + " successfully written to " + dirName);

    } catch (IOException e1) {
      JOptionPane.showMessageDialog(BufrMessageViewer.this, e1.getMessage());
      e1.printStackTrace();
    }
  }

  private void readData(Formatter f) {
    List<MessageBean> beans = messageTable.getBeans();
    int count = 0;
    try {
      for (MessageBean bean : beans) {
        bean.read();
        count++;
      }
      f.format("Read %d messages", count);

    } catch (Exception e) {
       StringWriter sw = new StringWriter(10000);
       e.printStackTrace(new PrintWriter(sw));
       f.format("%s", sw.toString());
    }
  }

  private void dumpDDS() {
    List<MessageBean> beans = messageTable.getBeans();
    HashMap<Integer, Message> map = new HashMap<>(2 * beans.size());

    // unique DDS
    for (MessageBean mb : beans) {
      map.put(mb.m.hashCode(), mb.m);
    }

    makeFileChooser();
    String defloc = (raf.getLocation() == null) ? "." : raf.getLocation();
    int pos = defloc.lastIndexOf(".");
    if (pos > 0)
      defloc = defloc.substring(0, pos);
    String filename = fileChooser.chooseFilenameToSave(defloc + ".txt");
    if (filename == null) return;

    try (FileOutputStream out = new FileOutputStream(filename)) {
      OutputStreamWriter fout = new OutputStreamWriter(out, CDM.utf8Charset);
      BufferedWriter bw = new BufferedWriter(fout);
      Formatter f = new Formatter(bw);

      int count = 0;
      for (Message m : map.values()) {
        m.dump(f);
        f.flush();
        count++;
      }
      JOptionPane.showMessageDialog(BufrMessageViewer.this, count + " successfully written to " + filename);

    } catch (IOException e1) {
      JOptionPane.showMessageDialog(BufrMessageViewer.this, e1.getMessage());
      e1.printStackTrace();
    }
  }

 /*  private void compare(Message m1, Message m2, Formatter f) {
    Formatter f1 = new Formatter();
    Formatter f2 = new Formatter();
    m1.dump(f1);
    m1.dump(f2);

    TextHistoryPane ta = new TextHistoryPane();
    IndependentWindow info = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), ta);
    info.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));
    ta.appendLine(f.toString());
    ta.gotoTop();
    info.show();
  }  */

  private void showDDS(Message m1) {
    Formatter f1 = new Formatter();
    m1.dump(f1);
    TextHistoryPane ta = new TextHistoryPane();
    IndependentWindow info = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), ta);
    info.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));
    ta.appendLine(f1.toString());
    ta.gotoTop();
    info.show();
  }

  /* private void compare2(Message m1, Message m2, Formatter f) {
    Formatter f1 = new Formatter();
    Formatter f2 = new Formatter();
      m1.dump(f1);
      m1.dump(f2);
    GoogleDiff diff = new GoogleDiff();
    List<GoogleDiff.Diff> result = diff.diff_main(f1.toString(), f2.toString());
    for (GoogleDiff.Diff d : result)
      f.format("%s%n", d);
    //DataDescriptor root1 = m1.getRootDataDescriptor();
    //DataDescriptor root2 = m1.getRootDataDescriptor();
    //compare(root1.getSubKeys(), root2.getSubKeys(), f);
  }

  private void compare(List<DataDescriptor> dds1, List<DataDescriptor> dds2, Formatter f) throws IOException {

    int count = 0;
    for (DataDescriptor sub1 : dds1) {
      DataDescriptor sub2 = dds2.get(count);

    }
  } */

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

  public void save() {
    messageTable.saveState(false);
    ddsTable.saveState(false);
    obsTable.saveState(false);
    prefs.putBeanObject("InfoWindowBounds", infoWindow.getBounds());
    prefs.putBeanObject("InfoWindowBounds2", infoWindow2.getBounds());
    prefs.putInt("splitPos", split.getDividerLocation());
    prefs.putInt("splitPos2", split2.getDividerLocation());
    if (fileChooser != null) fileChooser.save();
  }

  private RandomAccessFile raf;
  private MessageScanner scan;
  int center;

  public void setBufrFile(RandomAccessFile raf) throws IOException {
    this.raf = raf;
    List<MessageBean> beanList = new ArrayList<>();
    center = -1;

    scan = new MessageScanner(raf, 0, true);
    while (scan.hasNext()) {
      Message m = scan.next();
      if (m == null) continue;
      if (center == -1) center = m.ids.getCenterId();

      beanList.add(new MessageBean(m));
    }

    messageTable.setBeans(beanList);
    obsTable.setBeans(new ArrayList());
    ddsTable.setBeans(new ArrayList());
  }

  private NetcdfFile makeBufrMessageAsDataset(Message m) throws IOException {
    BufrIosp2 iosp = new BufrIosp2();
    NetcdfFileSubclass ncfile = new NetcdfFileSubclass(iosp, raf.getLocation());
    iosp.open(raf, ncfile, m);

    // Wrap in a NetcdfDataset so that we can get "*DS" objects. But don't enhance.
    return new NetcdfDataset(ncfile, false);
  }

  private NetcdfFile makeBufrDataset() throws IOException {
    BufrIosp2 iosp = new BufrIosp2();
    NetcdfFileSubclass ncfile = new NetcdfFileSubclass(iosp, raf.getLocation());
    iosp.open(raf, ncfile, (CancelTask) null);
    return ncfile;
  }

  private int setDataDescriptors(List<DdsBean> beanList, DataDescriptor dds, int seqno) {
    for (DataDescriptor key : dds.getSubKeys()) {
      beanList.add(new DdsBean(key, seqno++));
      if (key.getSubKeys() != null)
        seqno = setDataDescriptors(beanList, key, seqno);
    }
    return seqno;
  }

  private void setObs(Message m) {

    List<ObsBean> beanList = new ArrayList<>();
    try {
      NetcdfFile ncd = makeBufrMessageAsDataset(m);
      Variable v = ncd.findVariable(BufrIosp2.obsRecord);
      if ((v != null) && (v instanceof Structure)) {
        Structure obs = (Structure) v;
        StandardFields.StandardFieldsFromStructure extract = new StandardFields.StandardFieldsFromStructure(center, obs);
        try (StructureDataIterator iter = obs.getStructureIterator()) {
          while (iter.hasNext()) {
            beanList.add(new ObsBean(extract, iter.next()));
          }
        }
      }
    } catch (Exception ex) {
      JOptionPane.showMessageDialog(BufrMessageViewer.this, ex.getMessage());
      ex.printStackTrace();
    }
    obsTable.setBeans(beanList);
  }

  public class MessageBean {
    Message m;
    int readOk = 0;
    int bitsOk = 0;

    // no-arg constructor

    public MessageBean() {
    }

    // create from a dataset

    public MessageBean(Message m) {
      this.m = m;
    }

    public String getCategory() throws IOException {
      return m.getLookup().getCategoryFullName();
    }

    public String getCenter() {
      return m.getLookup().getCenterName();
    }

    public String getTable() {
      return m.getLookup().getTableName();
    }

    public String getHeader() {
      return m.getHeader();
    }

    public int getEdition() {
      return m.is.getBufrEdition();
    }

    public int getNobs() {
      return m.getNumberDatasets();
    }

    public long getSize() {
      return m.getMessageSize();
    }

    public String getHash() {
      return Integer.toHexString(m.hashCode());
    }

    public String getDdsHash() {
      return Integer.toHexString(m.dds.getDataDescriptors().hashCode());
    }

    public String getCompress() {
      return m.dds.isCompressed() ? "true" : "false";
    }

    public String getDate() {
      return m.getReferenceTime().toString();
    }

    public String getComplete() {
      try {
        return m.isTablesComplete() ? "true" : "false";
      } catch (IOException e) {
        return "exception";
      }
    }

    public String getBitsOk() {
      if (bitsOk == 0) checkBits();
      switch (bitsOk) {
        default : return "N/A";
        case 1 : return "true";
        case 2 : return "false";
        case 3 : return "fail";
      }
    }

    void checkBits() {
      if (getNobs() == 0) return;
      try {
        boolean ok = m.isBitCountOk();
        setBitsOk(ok);
      } catch (Exception e) {
        bitsOk = 3;
      }
    }

    public String getLocal() {
      try {
        return m.usesLocalTable() ? "true" : "false";
      } catch (Exception e) {
        return "exception";
      }
    }

    public String getReadOk() {
      switch (readOk) {
        default : return "N/A";
        case 1 : return "true";
        case 2 : return "false";
      }
    }

    void setBitsOk(boolean ok) {
      bitsOk = ok ? 1 : 2;
    }

    void setReadOk(boolean ok) {
      readOk = ok ? 1 : 2;
    }

    private void read() {
      try {
        NetcdfFile ncd = makeBufrMessageAsDataset(m);
        SequenceDS v = (SequenceDS) ncd.findVariable(BufrIosp2.obsRecord);
        try (StructureDataIterator iter = v.getStructureIterator(-1)) {
          while (iter.hasNext())
            iter.next();

        }
        setReadOk(true);
      } catch (IOException e) {
        setReadOk(false);
      }
    }

  }

  public class DdsBean {
    DataDescriptor dds;
    int seq;

    // create from a dataset

    public DdsBean(DataDescriptor dds, int seq) {
      this.dds = dds;
      this.seq = seq;
    }

    public String getFxy() {
      return dds.getFxyName();
    }

    public String getName() {
      return dds.getName();
    }

    public String getUnits() {
      return dds.getUnits();
    }

    public String getDesc() {
      return dds.getDesc();
    }

    public int getBitWidth() {
      return dds.getBitWidth();
    }

    public int getScale() {
      return dds.getScale();
    }

    public int getReference() {
      return dds.getRefVal();
    }

    public String getSource() {
      return dds.getSource();
    }

    public int getSeq() {
      return seq;
    }

    public String getLocal() {
      if (dds.isLocalOverride()) return "override";
      return dds.isLocal() ? "true" : "false";
    }

  }

  public class ObsBean {
    double lat = Double.NaN, lon = Double.NaN, height = Double.NaN, heightOfStation = Double.NaN;
    int year = -1, month = -1, day = -1, hour = -1, minute = -1, sec = -1, doy = -1;
    CalendarDate date;
    int wmo_block = -1, wmo_id = -1;
    String stn = null;

    // no-arg constructor

    public ObsBean() {
    }

    // create from a dataset

    public ObsBean(StandardFields.StandardFieldsFromStructure extract, StructureData sdata) {
      extract.extract(sdata);
      this.stn = extract.getStationId();
      this.date = extract.makeCalendarDate();
      this.lat = extract.getFieldValueD(BufrCdmIndexProto.FldType.lat);
      this.lon = extract.getFieldValueD(BufrCdmIndexProto.FldType.lon);
      this.height = extract.getFieldValueD(BufrCdmIndexProto.FldType.height);
      this.heightOfStation = extract.getFieldValueD(BufrCdmIndexProto.FldType.heightOfStation);
      this.year = extract.getFieldValue(BufrCdmIndexProto.FldType.year);
      this.month = extract.getFieldValue(BufrCdmIndexProto.FldType.month);
      this.day = extract.getFieldValue(BufrCdmIndexProto.FldType.day);
      this.hour = extract.getFieldValue(BufrCdmIndexProto.FldType.hour);
      this.minute = extract.getFieldValue(BufrCdmIndexProto.FldType.minute);
      this.sec = extract.getFieldValue(BufrCdmIndexProto.FldType.sec);
      this.doy = extract.getFieldValue(BufrCdmIndexProto.FldType.doy);
      this.wmo_block = extract.getFieldValue(BufrCdmIndexProto.FldType.wmoBlock);
      this.wmo_id = extract.getFieldValue(BufrCdmIndexProto.FldType.wmoId);
    }

    public double getLat() {
      return lat;
    }

    public double getLon() {
      return lon;
    }

    public double getHeight() {
      return height;
    }

    public double getHeightOfStation() {
       return heightOfStation;
     }

     public int getYear() {
      return year;
    }

    public int getMonth() {
      return month;
    }

    public int getDay() {
      return day;
    }

    public int getHour() {
      return hour;
    }

    public int getMinute() {
      return minute;
    }

    public int getSec() {
      return sec;
    }

    public int getDoy() {
      return doy;
    }

    public String getWmoId() {
      return wmo_block + "/" + wmo_id;
    }

    public String getStation() {
      return stn;
    }

    public String getDate() {
      return date.toString();
    }

  }

 /* public static void main(String[] args) throws IOException {
    RandomAccessFile raf1 = new RandomAccessFile("G:/ldm/distinct/8.bufr", "r");
    RandomAccessFile raf2 = new RandomAccessFile("G:/ldm/distinct/9.bufr", "r");
    MessageScanner scan1 = new MessageScanner(raf1);
    MessageScanner scan2 = new MessageScanner(raf2);
    Message m1 = scan1.getFirstDataMessage();
    Message m2 = scan2.getFirstDataMessage();
    raf1.close();
    raf2.close();

    Formatter f1 = new Formatter();
    Formatter f2 = new Formatter();

    m1.dump(f1);
    m2.dump(f2);

    System.out.printf("%s%n", f1);
    System.out.printf("==========================%n");
    System.out.printf("%s%n", f2);
    System.out.printf("==========================%n");

    GoogleDiff diff = new GoogleDiff();
    LinkedList<GoogleDiff.Diff> result = diff.diff_main(f1.toString(), f2.toString());
    diff.diff_cleanupSemantic(result);
    for (GoogleDiff.Diff d : result)
      System.out.printf("%s%n", d);
  } */

}


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

import ucar.nc2.dt.*;
import ucar.nc2.units.DateFormatter;
import ucar.nc2.constants.FeatureType;
import ucar.ma2.StructureData;

import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.Iterator;
import java.io.IOException;
import java.text.ParseException;

/**
 * A Collection of StationDatasets
 * @deprecated use ucar.nc2.ft.point
 */
public class StationDatasetCollection {
  private boolean debug = false;

  private ArrayList sobsList = new ArrayList();
  private StationObsDataset typical = null;
  private StringBuilder log = new StringBuilder();

  public void add(String location) throws IOException {
    StationObsDataset sobs = (StationObsDataset) TypedDatasetFactory.open(FeatureType.STATION, location, null, log);
    if (typical == null)
      typical = sobs;

    sobsList.add(sobs);
  }

  /**
   * Get all the Stations in the collection.
   *
   * @return List of Station
   * @throws java.io.IOException I/O error
   */
  public List getStations() throws IOException {
    return typical.getStations();
  }

  /**
   * Get all the Stations within a bounding box.
   *
   * @param boundingBox within this bounding box
   * @return List of Station
   * @throws java.io.IOException I/O error
   */
  public List getStations(ucar.unidata.geoloc.LatLonRect boundingBox) throws IOException {
    return typical.getStations(boundingBox);
  }

  /**
   * Find a Station by name
   *
   * @param name name of the Station
   * @return Station with that name, or null if not found
   */
  public ucar.unidata.geoloc.Station getStation(String name) {
    return typical.getStation(name);
  }

  /**
   * Get all data for this Station.
   *
   * @param s Station
   * @return iterator over type getDataClass()
   * @throws java.io.IOException I/O error
   */
  public DataIterator getDataIterator(ucar.unidata.geoloc.Station s) throws IOException {
    return new StationDataIterator(s);
  }

  /**
   * Get data for this Station within the specified date range.
   *
   * @param s     Station
   * @param start starting Date
   * @param end   ending Date
   * @return Iterator over type getDataClass()
   * @throws java.io.IOException I/O error
   */
  public DataIterator getDataIterator(ucar.unidata.geoloc.Station s, Date start, Date end) throws IOException {
    return new StationDateDataIterator(s, start, end);
  }

  private class StationDataIterator implements DataIterator {
    String stationName;
    Iterator iterSobs;
    DataIterator dataIter;

    StationDataIterator(ucar.unidata.geoloc.Station s) throws IOException {
      this.stationName = s.getName();
      iterSobs = sobsList.iterator();
    }

    public boolean hasNext() {
      if (dataIter == null)
        dataIter = getNextDataIterator();
      if (dataIter == null)
        return false;

      if (dataIter.hasNext())
        return true;

      dataIter = getNextDataIterator();
      if (dataIter == null)
        return false;

      return hasNext();
    }

    public Object nextData() throws IOException {
      // System.out.println("nextData dataIter =" + dataIter);
      return dataIter.nextData();
    }

    public Object next() {
      return dataIter.next();
    }

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

    private DataIterator getNextDataIterator() {
      if (!iterSobs.hasNext())
        return null;
      StationObsDataset sobs = (StationObsDataset) iterSobs.next();
      DataIterator dataIter = makeDataIterator(sobs);
      if (debug && dataIter != null) System.out.println("next sobs =" + sobs.getLocationURI());
      return dataIter == null ? getNextDataIterator() : dataIter;
    }

    protected DataIterator makeDataIterator(StationObsDataset sobs) {
      ucar.unidata.geoloc.Station s = sobs.getStation(stationName);
      if (s == null) return null;
      return sobs.getDataIterator(s);
    }
  }

  private class StationDateDataIterator extends StationDataIterator {
    private Date want_start, want_end;

    StationDateDataIterator(ucar.unidata.geoloc.Station s, Date start, Date end) throws IOException {
      super(s);
      this.want_start = start;
      this.want_end = end;
    }

    protected DataIterator makeDataIterator(StationObsDataset sobs) {
      Date start = sobs.getStartDate();
      if (start.after(want_end))
        return null;
      Date end = sobs.getEndDate();
      if (end.before(want_start))
        return null;
      ucar.unidata.geoloc.Station s = sobs.getStation(stationName);
      if (s == null) return null;
      return sobs.getDataIterator(s, start, end);
    }
  }

  public static void main(String args[]) throws IOException, ParseException {
    StationDatasetCollection sdc = new StationDatasetCollection();
    DateFormatter format = new DateFormatter();

    sdc.add("C:/data/metars/Surface_METAR_20070326_0000.nc");
    sdc.add("C:/data/metars/Surface_METAR_20070329_0000.nc");
    sdc.add("C:/data/metars/Surface_METAR_20070330_0000.nc");
    sdc.add("C:/data/metars/Surface_METAR_20070331_0000.nc");

    ucar.unidata.geoloc.Station s = sdc.getStation("ACK");
    DataIterator iter = sdc.getDataIterator(s);
    while (iter.hasNext()) {
      Object o = iter.nextData();
      assert (o instanceof StationObsDatatype);
      StationObsDatatype sod = (StationObsDatatype) o;
      ucar.unidata.geoloc.Station ss = sod.getStation();
      assert (ss.getName().equals(s.getName()));

      System.out.println(ss.getName() + " " + format.toDateTimeStringISO( sod.getObservationTimeAsDate()));

      StructureData sdata = sod.getData();
      assert sdata != null;
    }

    System.out.println("------------------\n");
    Date start = format.getISODate("2007-03-27T09:18:56Z");
    Date end = format.getISODate("2007-03-30T10:52:48Z");

    iter = sdc.getDataIterator(s, start, end);
    while (iter.hasNext()) {
      Object o = iter.nextData();
      assert (o instanceof StationObsDatatype);
      StationObsDatatype sod = (StationObsDatatype) o;
      ucar.unidata.geoloc.Station ss = sod.getStation();
      assert (ss.getName().equals(s.getName()));

      System.out.println(ss.getName() + " " + format.toDateTimeStringISO( sod.getObservationTimeAsDate()));

      StructureData sdata = sod.getData();
      assert sdata != null;
    }

  }
}