package nxm.sys.prim;

import java.awt.Color;
import java.util.concurrent.atomic.AtomicInteger;

import nxm.sys.inc.Installation;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.XGeoPlotIF;
import nxm.sys.lib.Args;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.MaskValue;
import nxm.sys.lib.Message;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Position;
import nxm.sys.lib.StateVector;
import nxm.sys.lib.Table;
import nxm.sys.lib.Util;
import nxm.sys.lib.XmUtil;
import nxm.sys.libg.Feature;  // For MARKER message
import nxm.sys.libg.Layer;
import nxm.sys.libg.LayerGeo; // For StateVector files and event messages
import nxm.sys.libg.LayerT5;  // For T5REC, T5TEXT,... messages
import nxm.sys.libg.MColor;

/**
  A Java replacement of X-Midas XGEOPLOT

  @author Jay Hallam
*/
public class xgeoplot extends plot implements XGeoPlotIF {
  // JC4 2009-01-15: Note that if you want to modify messages on their way out, just override
  // the sendMessage(..) method which is used by PLOT.


  /** T5REC MODMASK enum  <pre>
  ALT    - Altitude
  LAT    - Latitude
  LON    - Longitude
  NAME   - Name
  AZIM   - Azimuth   (R>1), or Altitude  2 (R=-1), or ellipse orientation (R=0)
  ELEV   - Elevation (R>1), or Latitude  2 (R=-1)
  ROLL   - Roll      (R>1), or Longitude 2 (R=-1)
  R      - Height of conic (R>1)
  A      - Major half angle for cone (R>1), Ellipse major radius (R=0)
  B      - Minor half angle for cone (R>1), Ellipse minor radius (R=0)
  COLOR  - X-Midas color ID number {@link nxm.sys.libg.MColor#T5_INDEX_DARK_GREEN}, {@link nxm.sys.libg.MColor#T5_INDEX_RED},
           {@link nxm.sys.libg.MColor#T5_INDEX_DARK_YELLOW}, {@link nxm.sys.libg.MColor#T5_GREEN},
           {@link nxm.sys.libg.MColor#T5_GRAY}
  SYMBOL - X-Midas symbol ID number
  SIZE   - size of symbol
  CONE   = -9: uses XGEOPLOTs current/default CONELEV
           -1: map cone to intersection with geosphere
            0: off - no conic
            N: number of sections (rings) along cone centerline
  INVERT = the cone is inverted, ie. the base of the cone and the vertex are swapped.
           (saved, but not used at this time)
  </pre>
  @since NeXtMidas 3.7.0
*/
  private static enum T5REC_MODMASK_BITS { ALT, LAT, LON, NAME, AZIM, ELEV, ROLL, R, A, B,
                                      COLOR, SYMBOL, SIZE, CONE, INVERT};

  /** T5TEXT MODMASK enum  <pre>
  ALT    - Altitude
  LAT    - Latitude
  LON    - Longitude
  NAME   - Name
  TEXT   - Note to dispplayed along with point (saved, but not displayed at this time)
  ELEV   - Elevation (R>1), or Latitude  2 (R=-1)
  COLOR  - X-Midas color ID number {@link nxm.sys.libg.MColor#T5_INDEX_DARK_GREEN}, {@link nxm.sys.libg.MColor#T5_INDEX_RED},
           {@link nxm.sys.libg.MColor#T5_INDEX_DARK_YELLOW}, {@link nxm.sys.libg.MColor#T5_GREEN},
           {@link nxm.sys.libg.MColor#T5_GRAY}
  SYMBOL - X-Midas symbol ID number
  SIZE   - size of symbol
  INVERT = the cone is inverted, ie. the base of the cone and the vertex are swapped.
           (saved, but not used at this time)
  </pre>
  @since NeXtMidas 3.7.0
*/
  private static enum T5TEXT_MODMASK_BITS { ALT, LAT, LON, NAME, TEXT, COLOR, SYMBOL, SIZE, INVERT};

  private boolean debug = false;
  private String resName_XCLICK=null;
  private String resName_YCLICK=null;
  private LayerGeo[] geoLayers = null;       // These correspond to XGEOPLOT events
  private StateVector[] stateVectors = null; // These are loaded via files or events
  private String markerName = null;          // For MARKER message: The name of the marker feature
  private double referenceTime = 0;

  // SETVIEW Message INFO field indicators
  private static final int XGEO_SETVIEW_LATLON = 1;
  private static final int XGEO_SETVIEW_PERSPECTIVE = 2;
  private static final int XGEO_SETVIEW_SVA = 3;
  private static final int XGEO_SETVIEW_SVB = 4;

  /** incrementing number use to get unique T5 layer names for layers created with T5LAYER CREATE messages*/
  private static AtomicInteger t5AutoNameCounter = new AtomicInteger(1); // since 3.6.0

  /** default constructor */
  public xgeoplot () {}

  @Override
  public int open () {
    // Use the plot open method first...
    int status = super.open();

    // ... then override with anything specific to XGEOPLOT
    debug = MA.getState("/DEBUG", debug);
    String xmTitle = MA.getCS("/XN","XGEOPLOT");
    String mtag = MA.getS("/MTAG",null);
    parseMTAG(mtag);
    MW.setTitle(xmTitle);
    info("mtag "+mtag+" res names X -> "+resName_XCLICK+", Y -> "+resName_YCLICK);

    String siteFile = MA.getS("SITE","SITE");
    Position sitePos = new Position(M, siteFile);
    info("SITE FILE = "+siteFile+", pos "+sitePos);

    String svString = MA.getRaw("SV");
    if (svString == null) svString ="";
    Args.SplitArg splitSVs = MA.getSplitArg(svString,'|');
    int numSVs = splitSVs.getFields();
    info("State Vectors "+svString+" # = "+numSVs);
    if (numSVs > 0) {
      stateVectors = new StateVector[numSVs];
      for (int i=0; i < splitSVs.getFields(); i++) {
        stateVectors[i] = new StateVector(M,splitSVs.getField(i));
        debug("SV["+i+"] = "+stateVectors[i]);
      }
    }
    referenceTime = MA.getD("REFTIME",referenceTime);  // Initial reference time
    initGeoLayers(sitePos);
    return status;
  }

  @Override
  public int processMessage (Message msg) {
    boolean fromMe = (msg.from == this);//getMessageID());
    boolean fromMPlot = (msg.from instanceof nxm.sys.libg.MPlot);
    int retStatus = NORMAL;
    String msgName = msg.getName();
    if (debug                    && !"POINTER".equals(msgName) &&
        !"FOCUS".equals(msgName) && !"REFRESH".equals(msgName) &&
        !"DRAG".equals(msgName)  && !"RESIZE".equals(msgName)  &&
        !"PAINT".equals(msgName) ) {
      debug("got message " + msgName + " from "+Util.getClassName(msg.from) + " fid "+msg.getFromID()
           + " info " + msg.info + " data " + msg.data);
      debug("  fromMe? "+fromMe+" from MPlot "+fromMPlot);
    }
    if (fromMe) {
      // These are messages from me (or plot) that are sent because this primitive
      // is the msgid object in plot
      //print("------------------------ from me!!!");
      // Process the message so I can send it to X-Midas
      if (!processMessageFromMe(msg)) {
        retStatus = super.processMessage(msg);
      }
    } else if (fromMPlot) {
      retStatus = super.processMessage(msg);
    } else {
      /* These are special XGEOPLOT messages I have to process here */
      if (!processExternalMessage(msg)) {
        retStatus = super.processMessage(msg);
      }
    }

    return retStatus;
  }

  private boolean processMessageFromMe(Message msg) {
    boolean processed = true;
    if (msg.getName().equals("MARK")) {
      Position pos = (Position)msg.data;
      MR.put("XM."+resName_XCLICK, pos.getLon());
      MR.put("XM."+resName_YCLICK, pos.getLat());
    } else {
      // tbd
      processed = false;
    }
    return processed;
  }

  private boolean processExternalMessage(Message msg) {
    boolean processed = true;
    try {
      if (msg.getName().equals("T5REC") || msg.getName().equals("T5TEXT")) {
        processT5TEXTorT5REC(msg);
      } else if (msg.getName().equals("T5LAYER")) {
        processT5LAYER(msg);
      } else if (msg.getName().equals("TRACE")) {
        processTRACE(msg);
      } else if (msg.getName().equals("MARKER")) {
        processMARKER(msg);
      } else if (msg.getName().equals("SETD")) {
        processSETD(msg);
      } else if (msg.getName().equals("NEWFILE")) {
        processNEWFILE(msg);
      } else if (msg.getName().equals("REREAD")) {
        processREREAD(msg);
      } else if (msg.getName().equals("SETVIEW")) {
        processSETVIEW(msg);
      } else if (msg.getName().equals("ZOOM")) {
        processZOOM(msg);
      } else if (msg.getName().equals("OVERLAY")) {
        openFileWithLimits(msg);
      } else {
        processed = false;
      }
    } catch (Exception e) {
      e.printStackTrace();
      warn("Could not process "+msg.getName()+" message - exception "+e);
    }
    return processed;
  }

  /* Process the REREAD message */
  private int processREREAD(Message msg) {
/*
FORTRAN
  if (type.eq.P_LAYER .or. type.eq.0) then  ! layers
    do n = Gx.nlayer,1,-1
      if (n.eq.index .or. index.eq.0) then
        ply = Gx.lloc(n)
        if (ly.lhcb .eq. 0) then  ! if not pipe
    if (BTEST(ly.enable,PM_BS)) Gx.bsmask = 0
    call M$FREE (ly.bufloc)
    call XGEO_GET_DATA (ly,ly.name,1)
        endif
      endif
    enddo
  endif
*/

    int status = 0;
    if (msg.info == 0) {
      // Reread all layers
      for (int n=1; n<MP.layers.getSize(); n++) {
        Layer layer = (Layer)MP.layers.get(n);
        print("REREAD - > Layer = "+layer+", file "+layer.getFile().getURL());

        Table layerTable = getLayerTable(layer, ""+msg.data);
        layer.close(); // close existing file before opening new one with same name (Bug 2406)
        setFile(layer.getFile().getURL()+layerTable.toString());
      }
    } else {
      Layer layer = getLayer(msg.info);
      if (layer != null) {
        print("REREAD - > Layer = "+layer+", file='"+layer.getFile().getURL()+"'");

        // Get any qualifiers given like... /REREAD/ myfile{LAYER={COLOR=RED}}
        Table layerTable = getLayerTable(layer, ""+msg.data);
        layer.close(); // close existing file before opening new one with same name (Bug 2406)
        setFile(layer.getFile().getURL()+layerTable.toString());
        //MP.removeLayer(layer.name); // Added for bug 2406  - removed for bug 2102
      }
      else {
        warn("REREAD Could not find layer number"+msg.info);
      }
    }
    return status;
  }

  /* Gets the Layer table given */
  private Table getLayerTable (Layer layer, String fileStr) {
    FileName fname = new FileName(fileStr);
    Table qualTable = fname.getQualifierTable();

    // Get current color and form {LAYER={COLOR=<>}} table
    Color prevColor = layer.getLine().getColor();
    Table tempTab = new Table();
    tempTab.put("COLOR", MColor.toString(prevColor));
    Table layerTable = new Table();
    layerTable.put("LAYER", tempTab);

    // Now merge the tables together.
    if (qualTable != null) {
      layerTable.merge(qualTable);
    }
    return layerTable;
  }

  /* Process the SETD message */
  private int processSETD(Message msg) {
    Table dataTab = Convert.o2t(msg.data);
    String key  = dataTab.getS("OBJECT");
    double dval = dataTab.getD("VALUE");
    debug("Processing "+msg.getName()+" info "+msg.info+" key "+key+" dval "+dval);

    int modMask  = (int)dval;
    int status = 0;
    if (key.equals("LAYER") && msg.info >= 0) {
      status = processSETD_LAYER(msg.info, modMask);

    } else if (key.equals("EVENT") && msg.info > 0) {
      status = processSETD_EVENT(msg.info, modMask);

    } else {
      warn("Unrecognized key: "+key+" for SETD message");
    }

    return status;
  }

  private int processSETD_LAYER(int layNum, int modMask) {
/*
    lval = dval
    ply = Gx.lloc(info)
    if (lval.eq.0) ly.enable = JIBCLR(ly.enable,PM_GLOBAL)
    if (lval.eq.1) ly.enable = JIBSET(ly.enable,PM_GLOBAL)
    if (lval.gt.1) ly.enable = lval
*/
    Layer layer = getLayer(layNum);
    if (layer != null) {
      debug("SETD_LAYER for layer '"+layer.getClass()+"'");
      if (modMask == 0) layer.setEnable("-GLOBAL");
      if (modMask == 1) layer.setEnable("+GLOBAL");
      if (modMask > 1) {
        StringBuffer sb = new StringBuffer("|");  // Empty mask
        if (btest(modMask, PM_GLOBAL)) { sb.append("GLOBAL|"); }
        if (btest(modMask, PM_LABEL))  { sb.append("LABELS|"); }
        if (btest(modMask, PM_BS))     { sb.append("BSTORE|"); }
        if (btest(modMask, PM_BASE))   { sb.append("BASE|"); }
        if (btest(modMask, PM_ZOOM))   { sb.append("ZOOM|"); }
        if (btest(modMask, PM_FBASE))  { sb.append("FASTBASE|"); }
        if (btest(modMask, PM_FZOOM))  { sb.append("FASTZOOM|"); }
        debug("Enables string '"+sb+"'");
        layer.setEnable(sb.toString());
      }
    } else {
      warn("Could not find layer number "+layNum+", # layers = "+MP.getLayers().size());
    }
    return 0;
  }
  private int processSETD_EVENT(int eventNum, int modMask) {
/*
    lval = dval
    pev = Gx.eloc(info)
    if (lval.eq.0) ev.enable = JIBCLR(ev.enable,PM_GLOBAL)
    if (lval.eq.1) ev.enable = JIBSET(ev.enable,PM_GLOBAL)
    if (lval.gt.1) ev.enable = lval
*/
    int glNum = eventNum-1;
    if (glNum <= geoLayers.length-1) {
      LayerGeo geoLay = geoLayers[glNum];
      if (modMask == 0) geoLay.setEnable("-GLOBAL");
      if (modMask == 1) geoLay.setEnable("+GLOBAL");
      if (modMask > 1) {
        StringBuffer sb = new StringBuffer("|");  // Empty mask
        if (btest(modMask, PM_GLOBAL)) { sb.append("GLOBAL|"); }
        if (btest(modMask, PM_LABEL))  { sb.append("LABELS|"); }
        if (btest(modMask, PM_BS))     { sb.append("BSTORE|"); }
        if (btest(modMask, PM_BASE))   { sb.append("BASE|"); }
        if (btest(modMask, PM_ZOOM))   { sb.append("ZOOM|"); }
        if (btest(modMask, PM_FBASE))  { sb.append("FASTBASE|"); }
        if (btest(modMask, PM_FZOOM))  { sb.append("FASTZOOM|"); }
        debug("Enables string '"+sb+"'");
        geoLay.setEnable(sb.toString());
      }
    } else {
      warn("SETD_EVENT - Invalid event number "+eventNum
          +" num events = "+geoLayers.length);
    }

    return 0;
  }
  /* Process the NEWFILE message */
  private int processNEWFILE(Message msg) {
/*
 /NEWFILE/ filename(s)
         info = 1-site 2-statevectors 3-layers 4-events 5-altdb
         ( clears out old files first )
*/
    int retStatus = 0;
    Table dataTab = Convert.o2t(msg.data);
    int fileTypes = msg.info;
    // This could be file or file1|file2|file3 ...
    String filenames = dataTab.getS("FILENAME");
    // XGEOPLOT processes NEWFILE this way, 0 indicates layers
    if (fileTypes == 0) fileTypes = P_LAYER; // 3
    switch(fileTypes) {
      case P_SITE: // Open site file
        break;
      case P_SV:
        retStatus = processNEWFILE_STATEVECTOR(filenames);
        break;
      case P_LAYER:
        break;
      case P_EVENT:
        retStatus = processNEWFILE_EVENT(filenames);
        break;
      case P_ALTDB:
        break;
      default:
    }

    return 0;
  }

  /** Process the NEWFILE message of type EVENT
    @param filenames A bar separated file name list
  */
  private int processNEWFILE_EVENT(String filenames) {
    debug("Processing NEWFILE event "+filenames);
    Args.SplitArg sa = MA.getSplitArg(filenames,'|');
    int numEvents = sa.getFields();
    if (numEvents > 0) {
      for (int i=0; i < numEvents; i++) {
        String name = sa.getField(i);
        debug("Event["+i+"] = "+name);
        loadEventFile(name);
      }
    }
    return 0;
  }
  /** Process the NEWFILE message of type STATE VECTOR
    @param filenames A bar separated file name list
  */
  private int processNEWFILE_STATEVECTOR(String filenames) {
    debug("Processing NEWFILE state vector "+filenames);
warn("------------> TBD -- how to process new state vectors...");
    Args.SplitArg sa = MA.getSplitArg(filenames,'|');
    int numSVs = sa.getFields();
    if (numSVs > 0) {
      stateVectors = new StateVector[numSVs];
      for (int i=0; i < numSVs; i++) {
        String name = sa.getField(i);
        debug("SV["+i+"] = "+name);
      }
    } else {
      warn("No files found in string "+filenames);
    }
    return 0;
  }
  /* Process the MARKER message */
  private int processMARKER(Message msg) {
/*
  MARKER lat lon alt [a:name] [s48:text]
  info = color (if not zero)
  set position of the marker - origin for delta measurements and
  starting point for geolocation iterations
*/
    int colorNum = msg.info;
    Table dataTab = Convert.o2t(msg.data);
    String oldMarkerName = markerName;
    Feature marker = null;
    if (markerName == null) {
debug("MARKER - creating feature for PLOT ...");
      markerName = new String("MARKER");
      marker = new Feature();
      marker.setType("OSYMBOL");
      MP.addFeature(markerName, marker);
    } else {
debug("MARKER - getting '"+markerName+"' feature from PLOT ...");
      marker = (Feature)MP.getFeatures().get(markerName);
    }
debug("MARKER - marker name '"+markerName+"' marker "+marker);
    if (dataTab != null) {
      //NOTE: This is a non-configured message so I have to use ARGx syntax
debug("MARKER - setting"
     +" X = "+dataTab.getD("VAL2")
     +" Y = "+dataTab.getD("VAL1")
     +" Z = "+dataTab.getD("VAL3"));
      double lat = dataTab.getD("VAL1", -999.0);
      double lon = dataTab.getD("VAL2", -999.0);
      double alt = dataTab.getD("VAL3");
      String newMarkerName = dataTab.getS("VAL4", "");
      String text = dataTab.getS("VAL5");
      if (lon != -999.0) marker.setX(lon);
      if (lat != -999.0) marker.setY(lat);
      if (dataTab.containsKey("VAL3")) marker.setZ(alt);
      if (!newMarkerName.equals("")) {
debug("MARKER --------==============> newMarkerName '"+newMarkerName+"'");
        markerName = newMarkerName;
        marker.setName(markerName);
      }
      if (dataTab.containsKey("VAL5")) marker.setText(text);
      debug("Lat "+dataTab.getD("VAL1")+" lon "+dataTab.getD("VAL2")+" alt "+dataTab.getD("VAL3")
           +" markerName "+markerName+" colornum "+colorNum+" text '"+dataTab.getS("VAL5")+"'");
    }
    if (colorNum > 0) {
      marker.getLine().setColor(colorNum);
    } else {
      marker.getLine().setColor("RED");
    }
    return 0;
  }


  /* Processes the X-Midas T5REC and T5TEXT messages */
  private int processT5TEXTorT5REC(Message msg) {
/* This processes the X-Midas message T5TEXT of the form:
     name    t5text
     infoalias       modmask ! bitmask of fields to modify
     L:layer         ! layer number
     L:index         ! point index
     D:alt           ! geodetic altitude
     D:lat           ! geodetic latitude
     D:lon           ! geodetic longitude
     A:name          ! name label
     S[48]:text      ! text label
     L:color         ! color
     B:symbol        ! symbol code
     B:size          ! size of symbol
     B:cone          ! cone mode or sections
     B:invert        ! inverted cone

or the T5REC message of the form:

   name    t5rec
   infoalias       modmask ! bitmask of fields to modify
   L:layer         ! layer number
   L:index         ! point index
   D:alt           ! geodetic altitude
   D:lat           ! geodetic latitude
   D:lon           ! geodetic longitude
   A:name          ! name label
   D:azim          ! conic azimuth
   D:elev          ! conic elevation
   D:roll          ! conic roll
   D:r             ! conic radius
   D:a             ! cone angle A (in degrees)
   D:b             ! cone angle B (in degrees)
   L:color         ! color
   B:symbol        ! symbol code
   B:size          ! size of symbol
   B:cone          ! cone mode or sections
   B:invert        ! inverted cone
*/
    Table dataTab = Convert.o2t(msg.data);
    int layerNum  = dataTab.getL("LAYER");

    int modMask   = msg.info;
    // When there is a String MODMASK entry in the Data table,
    // use that instead of the msg.info - since 3.7.0 for bug 2965
    String maskStr = dataTab.getS("MODMASK");
    if ((maskStr != null) && (maskStr.length() != 0)) {
      if (msg.getName().equals("T5REC")) {
        MaskValue<T5REC_MODMASK_BITS> maskVal = MaskValue.fromString(maskStr, T5REC_MODMASK_BITS.class);
        modMask = maskVal.getHexValue() * 2;
      }
      else {
        MaskValue<T5TEXT_MODMASK_BITS> maskVal = MaskValue.fromString(maskStr, T5TEXT_MODMASK_BITS.class);
        modMask = maskVal.getHexValue() * 2;
      }
    }

    int index = dataTab.getL("INDEX");
    int offset = index - 1;
    boolean mergeAllPoints = (index==0); //T5REC with index=0 means merge ALL points
    debug(msg.getName()+" - Layer number "+layerNum+" index "+index+" allpts? "+mergeAllPoints+
        " mod mask "+modMask+" DATA: "+dataTab);

    // This is the actual table used to set/add a point
    Table rowTab = new Table();

    // Sub table postion
    Table posTab = new Table();
    if (btest(modMask,1)) posTab.put("ALT",  dataTab.getD("ALT"));
    if (btest(modMask,2)) {posTab.put("LAT",  dataTab.getD("LAT"));}
    if (btest(modMask,3)) {posTab.put("LON",  dataTab.getD("LON"));}
    if (btest(modMask,4)) {rowTab.put("NAME", dataTab.getS("NAME"));}

    if (msg.getName().equals("T5REC")) {

      // Get value of R, which is needed to interpret the rest of the Data table values
      double range = dataTab.getD("R");

      // Create tables that will be populated with 2nd position, cone, & error ellipse information
      Table posPTab = new Table();
      Table elpsTab = new Table();
      Table coneInfoTab = new Table();

      // Form tables based on value of R (Range) - since 3.7.0 for bug 2965
      if ( range == -1 ) { // Cone via second position specified as Alt, Lat, Lon
        if (btest(modMask,5)) posPTab.put("ALT", dataTab.getD("AZIM"));
        if (btest(modMask,6)) posPTab.put("LAT", dataTab.getD("ELEV"));
        if (btest(modMask,7)) posPTab.put("LON", dataTab.getD("ROLL"));
        //warn("T5REC: POSP conversion to Az El Roll is not implemented yet"); //Current implementation does not need conversion
      }
      else if (range == 0) {  // Error Ellipse case
        if (btest(modMask,9))  elpsTab.put("SEMIMAJOR", dataTab.getD("A"));
        if (btest(modMask,10)) elpsTab.put("SEMIMINOR", dataTab.getD("B"));
        if (btest(modMask,5))  elpsTab.put("TILT", dataTab.getD("AZIM"));
      }
      else { // Cone via a second position specified with Azim, Elev, Roll, & Range
        warn("T5REC: POSP conversion from Az El Roll Range(R) SemiMajor(A) SemiMinor(B) is not implemented yet");
        // TODO: POSP could be calculated from Azim, Elev, Roll, & Range, but there is no code
        // in place to do this. The method probably would be put in GeodeticUtil.
        // Using semimajor and semiminor would add more complexity, as currently we use a single cone angle
        if (btest(modMask,5)) coneInfoTab.put("AZIM", dataTab.getD("AZIM"));
        if (btest(modMask,6)) coneInfoTab.put("ELEV", dataTab.getD("ELEV"));
        if (btest(modMask,7)) coneInfoTab.put("ROLL", dataTab.getD("ROLL"));
        coneInfoTab.put("HEIGHT", range);
        if (btest(modMask,9))  coneInfoTab.put("SEMIMAJOR", dataTab.getD("A"));
        if (btest(modMask,10)) coneInfoTab.put("SEMIMINOR", dataTab.getD("B"));
      }

      // Create flag table
      Table flagTab = new Table();
      if (btest(modMask,11)) flagTab.put("COLOR",      dataTab.getB("COLOR"));
      if (btest(modMask,12)) flagTab.put("SYMBOLID",   dataTab.getB("SYMBOL"));
      if (btest(modMask,13)) flagTab.put("SYMBOLSIZE", dataTab.getB("SIZE"));
      if (btest(modMask,14)) flagTab.put("CONE",       dataTab.getB("CONE"));
      if (btest(modMask,15)) flagTab.put("INVERT",     dataTab.getB("INVERT"));

      // Add non-empty tables to actual table used to set/add a point
      if (posTab.getSize()  > 0)  rowTab.put("POS", posTab);
      if (posPTab.getSize() > 0)  rowTab.put("POSP", posPTab);
      if (elpsTab.getSize() > 0)  rowTab.put("ELPS", elpsTab);
      if (flagTab.getSize() > 0)  rowTab.put("FLAG", flagTab);

    } else {
debug(msg.getName()+" SYMBOL"
     +" id "+dataTab.getB("SYMBOL")
     +" name "+XmUtil.getXmSymbolName((int)dataTab.getB("SYMBOL")));
      // Sub table flag
      Table flagTab = new Table();
debug(msg.getName()+" data table "+dataTab);
      if (btest(modMask,5))  rowTab.put("NOTE",       dataTab.getS("TEXT"));
      if (btest(modMask,6)) flagTab.put("COLOR",      dataTab.getB("COLOR"));
      if (btest(modMask,7)) flagTab.put("SYMBOLID",   dataTab.getB("SYMBOL"));
      if (btest(modMask,8)) flagTab.put("SYMBOLSIZE", dataTab.getB("SIZE"));
      // XGEOPLOT put P_TEXT=-99 into this, do not think we need to do anything
      // (btest(modMask,9)) for CONE not needed here
      if (btest(modMask,10)) flagTab.put("INVERT",    dataTab.getB("INVERT"));

      if (posTab.getSize() > 0) rowTab.put("POS", posTab);
      if (flagTab.getSize() > 0)rowTab.put("FLAG", flagTab);
    }
debug(msg.getName()+" ROW "+rowTab+" layer at "+layerNum+" "+getLayer(layerNum));
    Layer layer = getLayer(layerNum);
    if (layer instanceof LayerT5) {
      LayerT5 layt5 = (LayerT5)layer;
      // Since 3.5.1 (bug 2823) default behavior is MERGE Pnt information rather than override
      if (!Installation.ApplyBugFix.XGEOPLOT_T5REC_MSG_MERGE_PROPERTIES) {
        layt5.setPoint(offset, rowTab);
      }
      else if (mergeAllPoints){
        layt5.mergeAllPoints(rowTab);
      }
      else {
        layt5.mergePoint(offset, rowTab);
      }
    } else {
      M.warning("The layer I got, "+layer.getName()+", is not the right type, LayerT5");
    }
    return 0;
  }

  /* */
  private int processT5LAYER(Message msg) {
/*
name  t5layer
infoalias  layer  ! layer number
A:action  ! action to perform
L:p1    ! parameter 1
L:p2    ! parameter 2
L:p3    ! parameter 3
L:p4    ! parameter 4
*/
    Table dataTab = Convert.o2t(msg.data);
    String action = dataTab.getS("ACTION");
    int layerNum = msg.info;

    if        (action.equals("CREATE")) { // supported since 3.7.0 for bug 2917
      int nelem = dataTab.getL("P1");
      LayerT5 createdT5 = new LayerT5(nelem);
      // TODO: check if another layer by this name exists before setting name
      createdT5.setName("T5_"+t5AutoNameCounter.getAndIncrement());
      MP.addLayer(createdT5);
    } else if (action.equals("DELETE")) { // supported since 3.7.0 for bug 2917
      int indexFirstDelete = dataTab.getL("P1") - 1;
      int indexLastDelete = dataTab.getL("P2");
      indexLastDelete = (indexLastDelete == -1) ? -1 : indexLastDelete - 1;
      if (indexLastDelete != -1 && (indexLastDelete < indexFirstDelete)) {
         M.error("T5LAYER DELETE Message has P2 less than P1:"+msg);
      }
      Layer layer = getLayer(layerNum);
      if (layer instanceof LayerT5) {
        LayerT5 layt5 = (LayerT5)layer;
        layt5.deletePoints(indexFirstDelete, indexLastDelete);
      }
      else {
        M.warning("The layer I got, "+layer.getName()+", is not the right type, LayerT5");
      }
    } else if (action.equals("COPY")) { // supported since 3.7.0 for bug 2917
      int indexOriginal = dataTab.getL("P1") - 1; // NeXtMidas is 0 based (X-Midas 1 based)
      int indexForCopy = dataTab.getL("P2") - 1;  // NeXtMidas is 0 based (X-Midas 1 based)
      Layer layer = getLayer(layerNum);
      if (layer instanceof LayerT5) {
        LayerT5 layt5 = (LayerT5)layer;
        layt5.copyPoint(indexOriginal, indexForCopy);
      }
      else {
        M.warning("The layer I got, "+layer.getName()+", is not the right type, LayerT5");
      }
    } else {
      warn("Unsupported T5LAYER message action: "+action);
    }
    return 0;
  }

  /* */
  private int processTRACE(Message msg) {
/*
name    trace
infoalias  layer
L:linetype
L:thick
A:symbol
L:radius
L:color

  /TRACE/ L:line L:thick A:symbol L:radius L:color
  info = Layer number

  subroutine MX$TRACE_MSG (mqd, linetype, thick, symbol, radius, color)
  implicit none
  include 'types.inc'
  include 'xstructures.inc'
  include 'qmessdef.inc'
  record /MQD_TRACE/ mqd
  integer*4 linetype, thick, symbol, radius, color
  integer*4 is, ls, PARSE_TAG
  character tag*8, switch*128, sval*80
  real*8 dval

  if (color .eq. 999) then
    MQD.linetype = 0
    MQD.thick = 0
    MQD.symbol = ' '
    MQD.radius = 0
    MQD.color = 0
    ls  = M$GET_USWITCH('TRACE', switch)
    if (ls.le.1) then
    elseif (M$SEARCH(switch(1:ls),'=').ne.0) then
      is = 1
      do while (PARSE_TAG(is,switch(1:ls),tag,sval,dval).ge.0)
        if (tag.eq.'LINE') then
    MQD.linetype = M$SEARCH('NVHCBS', sval(1:1)) - 1
    if (MQD.linetype.le.0) MQD.linetype = -MQD.linetype - 1
        elseif (tag.eq.'SYM') then
    MQD.symbol = sval
        elseif (tag.eq.'THK') then
    MQD.thick = dval
        elseif (tag.eq.'RAD') then
    MQD.radius = dval
        endif
        is = is + 1
      enddo
    endif
  endif

  if (MQD.linetype.ne.0) linetype = max(0,MQD.linetype)
  if (MQD.thick.ne.0) thick = MQD.thick

  ls = M$LENGTH(mqd.symbol)

  if (ls.eq.0) then
    ! ignore
  elseif (M$SEARCH('NONE', mqd.symbol(1:ls)).eq.1) then
    symbol = 0
    radius = 0
  elseif (M$SEARCH('PIXELS', mqd.symbol(1:ls)).eq.1) then
    symbol = L_PixelSymbol
  elseif (M$SEARCH('CIRCLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_CircleSymbol
  elseif (M$SEARCH('SQUARES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_SquareSymbol
  elseif (mqd.symbol .eq. '+') then
    symbol = L_PlusSymbol
  elseif (mqd.symbol .eq. 'X') then
    symbol = L_XSymbol
  elseif (M$SEARCH('TRIANGLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_TriangleSymbol
  elseif (M$SEARCH('ITRIANGLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_ITriangleSymbol
  elseif (mqd.symbol(1:6) .eq. 'ASCII:') then
    symbol = ICHAR(mqd.symbol(7:7))
  else
    call M$WARNING('TRACE unknown symbol "'//mqd.symbol//'" ignored.')
  endif

  if (MQD.radius.ne.0) radius = MQD.radius
  if (MQD.color.gt.L_NoColor .and. MQD.color.le.L_color) then
    if (MQD.color .ne. 0) color = MQD.color
  endif

  return
  end


  public static final String typeList = "Solid,Point,Dot,Fill,Bar,Symbol";
  public static final int SOLID=0x1, POINT=0x2, DOT=0x4, FILL=0x8, BAR=0x10, SYMBOL=0x20;

*/
    Table dataTab = Convert.o2t(msg.data);
    int layerNum = msg.info;
    Layer layer = getLayer(layerNum);

    debug("TRACE: layer number "+layerNum+" layer "+layer);
    if (layer != null) {
      int colorNum = dataTab.getL("COLOR");
      if (colorNum == 999) {
       M.warning("Special processing for TRACE with COLOR=999 is TBD");
      } else {
        nxm.sys.libg.Line line = layer.getLine();
        debug ("Current line "+line);
        debug ("Current symbol "+line.getSymbol());
        if (line.getSymbol() != null) {
          debug ("Current symbol name "+line.getSymbol().getName());
          int lineTypeNum   = dataTab.getL("LINETYPE");

          int thickness = dataTab.getL("THICK");
          String xmSymbol   = dataTab.getS("SYMBOL");
          String symbol     = convertXMSymbolToNXM(xmSymbol, line.getSymbol().getName());
          int radius        = dataTab.getL("RADIUS");
          line.setColor(colorNum);
          line.setRadius(radius);
          line.setSymbol(symbol);
          if (lineTypeNum != 0) {
            // Set of line type is TBD
          }
          if (thickness > 0) line.setThickness(thickness);
          debug ("New line "+line);
        } else {
          warn("TRACE - SYMBOL is NULL, WHY????????????");
        }
      }
    }
    return 0;
  }

  /** Convert X-Midas symbol to NeXtMidas equivalent
   *  @param xmsym X-Midas symbol
   *  @param def   NeXtMidas default (of equivalent not found)
   *  @return      matching NeXtMidas symbol
   */
  public String convertXMSymbolToNXM(String xmsym, String def) {
/* A comma-delimited list of valid symbol types.
  public static final String symbolList
    = "Aero,Arrow,Circle,Cross,Diamond,Dot,Plus,Square,Star,"
    + "Triangle,UTri,DTri,RTri,LTri,A,B,C,D,Ascii-XYZ,Null";
  if (ls.eq.0) then
    ! ignore
  elseif (M$SEARCH('NONE', mqd.symbol(1:ls)).eq.1) then
    symbol = 0
    radius = 0
  elseif (M$SEARCH('PIXELS', mqd.symbol(1:ls)).eq.1) then
    symbol = L_PixelSymbol
  elseif (M$SEARCH('CIRCLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_CircleSymbol
  elseif (M$SEARCH('SQUARES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_SquareSymbol
  elseif (mqd.symbol .eq. '+') then
    symbol = L_PlusSymbol
  elseif (mqd.symbol .eq. 'X') then
    symbol = L_XSymbol
  elseif (M$SEARCH('TRIANGLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_TriangleSymbol
  elseif (M$SEARCH('ITRIANGLES', mqd.symbol(1:ls)).eq.1) then
    symbol = L_ITriangleSymbol
  elseif (mqd.symbol(1:6) .eq. 'ASCII:') then
    symbol = ICHAR(mqd.symbol(7:7))
  else
    call M$WARNING('TRACE unknown symbol "'//mqd.symbol//'" ignored.')
  endif
*/
    String symbol = def;
    if (xmsym.length() == 0) {
      // ignore
    } else if (M_SEARCH("NONE", xmsym))   {
      symbol = "NONE";
    } else if (M_SEARCH("PIXELS", xmsym)) {
      //symbol = L_PixelSymbol
      M.warning("PIXEL symbol is TBD");
    } else if (M_SEARCH("CIRCLES", xmsym)) {
      symbol = "CIRCLE";
    } else if (M_SEARCH("SQUARES", xmsym)) {
      symbol = "SQUARE";
    } else if (xmsym.equals("+")) {
      symbol = "PLUS";
    } else if (xmsym.equals("X")) {
      symbol = "CROSS";
    } else if (M_SEARCH("TRIANGLES", xmsym)) {
      symbol = "TRIANGLE";
    } else if (M_SEARCH("ITRIANGLES", xmsym)) {
      symbol = "UTRIANGLE";
    } else if (xmsym.startsWith("ASCII:")) {
      symbol = "ASCII-"+xmsym.substring(6);
    } else {
      M.warning("TRACE unknown symbol "+xmsym+" ignored.");
    }
    return symbol;
  }

  private boolean M_SEARCH(String toSearch, String sub) {
    return (toSearch.indexOf(sub) > 0);
  }
  /* Process the external (X-Midas) SETVIEW message */
  private int processSETVIEW(Message msg) {

    Table dataTab = Convert.o2t(msg.data);
    debug (" SETVIEW -> data "+msg.data);
    Table zTab = new Table();

    zTab.put("XMIN",dataTab.getD("VAL1"));
    zTab.put("XMAX",dataTab.getD("VAL2"));
    zTab.put("YMIN",dataTab.getD("VAL3"));
    zTab.put("YMAX",dataTab.getD("VAL4"));
    debug (" SETVIEW -> zoom table "+zTab);
    if (msg.info == XGEO_SETVIEW_LATLON && !MP.getView().equals("LatLon")) {
      MP.setView("LatLon");
    }
    else if (msg.info == XGEO_SETVIEW_PERSPECTIVE && !MP.getView().equals("Geodetic")) {
      MP.setView("Geodetic");
    }
    if (MP.zoom(zTab, 0)) MP.refresh();
    return 0;
  }
  /* Process the external (X-Midas) ZOOM message */
  private int processZOOM(Message msg) {

    Table zTab = Convert.o2t(msg.data);
/*
    double xmin = zTab.getD("X1");
    double xmax = zTab.getD("X2");
    double ymin = zTab.getD("Y1");
    double ymax = zTab.getD("Y2");

    double [] darray = new double[] {xmin, xmax, ymax, ymin};
    if (MP.zoom(darray, 0)) MP.refresh();
*/
    if (MP.zoom(zTab, 0)) MP.refresh();
    return 0;
  }
  /** Get the layer name given the X-Midas layer number
    @param lnum Layer number
  */
  private String getLayerName(int lnum) {
    Layer lay = getLayer(lnum);
    return lay.getName();
  }
  /** Get a Layer object given the layer number.  A negative value
    indicates from the end of layers.
    @param lnum Layer number -nlayers to +nlayers
    @return layer of null if not found
  */
  private Layer getLayer(int lnum) {
    Layer lay = null;

    // X-Midas layers start from 1, NeXtMidas layer 0 is GRID
    int layerNum = lnum;
    for (int n=1; n<MP.layers.size(); n++) {
      Layer tmplay = (Layer)MP.layers.get(n);
      debug("Layer num "+n+" = "+tmplay);
    }
    if (layerNum < 0) layerNum = MP.layers.size()+layerNum+1;
    if (layerNum > 0) lay = (Layer)MP.layers.get(layerNum);
    else M.warning("Invalid layer number "+lnum);
    return lay;
  }

  /** Parses a string of the form (X:xx,Y:yy,Z:zz,XY:xy,X+Y:xpy) and places strings
      after the colon into a results parameter
    @param mtag_in The MTAG string
  */
  private void parseMTAG(String mtag_in) {
    if (mtag_in == null) return;
    try {
      if (!mtag_in.startsWith("(") || !mtag_in.endsWith(")")) {
        M.warning("Unable to parse MTAG='"+mtag_in+"' . It should start and end with parenthesis");
      } else {
        String mtag = mtag_in.substring(1,mtag_in.length()-1);
        Parser p = new Parser(mtag,',');
        while (p.more()) {
          String str = p.next();
          //print("Parser next = '"+str+"'");
          if (str.startsWith("X:")) {
            resName_XCLICK = str.substring(2);
          } else if (str.startsWith("Y:")) {
            resName_YCLICK = str.substring(2);
          } else if (str.startsWith("Z:")) {
            warn("Do not handle MTAG string "+str+" yet");
          } else if (str.startsWith("XY:")) {
            warn("Do not handle MTAG string "+str+" yet");
          } else if (str.startsWith("X+Y:")) {
            warn("Do not handle MTAG string "+str+" yet");
          } else {
            warn("Unrecognized MTAG string "+str);
          }
        }
      }
    } catch (Exception e) {
     warn("Unable to set result names from string "+mtag_in);
    }
  }

/* GEO Layer methods -- start*/
  /** Initialize the Geo Layers. Layers are Global disabled and must be enabled
      to be seen
      @param sitePos the site position object
   */
  public void initGeoLayers(Position sitePos) {
    if (stateVectors != null && stateVectors.length > 0) {
      int numGeoLayers = getNumLayerGeos(stateVectors.length);
      geoLayers = new LayerGeo[numGeoLayers];
      for (int i = 0; i < stateVectors.length; i++) {
        //print("Initializing GEO layer "+i);
        for (int j = i+1; j < stateVectors.length; j++) {
          debug("Initializing GEO layers "+i+" and "+j);
          geoLayers[i] = initGeoLayer("GEO"+(i+1), sitePos, stateVectors[i], stateVectors[j]);
          geoLayers[i].setEnable("-GLOBAL");
          // Add the layer to the plot
          MP.addLayer(geoLayers[i]);
        }
      }
    } else {
      geoLayers = null;
    }
  }

  /** Compute the number of LayerGEOs given the number of State Vectors
   *  @param numSVs the number of State Vectors
   *  @return the number of LayerGEOs
   */
  public int getNumLayerGeos(int numSVs) {
    int numLGs = 0;
    if (numSVs > 1) {
      if        (numSVs == 2) {
        numLGs = 1;
      } else if (numSVs == 3) {
        numLGs = 3;
      } else if (numSVs == 4) {
        numLGs = 6;
      } else {
        M.error("Too many state vectors "+numSVs);
      }
    }
    return numLGs;
  }
  /** Initialize a Geo Layer
      @param name The Layer name
      @param sitePos The site Position object
      @param sv1 State Vector 1
      @param sv2 State Vector 2
      @return the initialized Geo Layer
  */
  public LayerGeo initGeoLayer(String name, Position sitePos, StateVector sv1, StateVector sv2) {
    LayerGeo lg = new LayerGeo();

    lg.setName(name);
    lg.setSite(sitePos);
    lg.setSiteMode("Current");
    lg.setSVecMode("Current");
    lg.setTdoaMode("Contour");
    lg.setFdoaMode("Contour");
    lg.setFLinkT(100e6);
    lg.setFLinkA(-100e6);
    lg.setFLinkB(-100e6);

    lg.setSVA(sv1);
    lg.setSVB(sv2);

    return lg;
  }

  /** Changes the state vectors for geo layers */
  public void changeStateVectors() {
    if (geoLayers == null) {
      initGeoLayers(new Position());
    } else {
      if (stateVectors != null && stateVectors.length > 0) {
        for (int i = 0; i < stateVectors.length; i++) {
          debug("Changing state vectors GEO layer "+i);
          for (int j = i+1; j < stateVectors.length; j++) {
            debug("Initializing GEO layers "+i+" and "+j);
            geoLayers[i].setSVA(stateVectors[i]);
            geoLayers[i].setSVB(stateVectors[j]);
          }
        }
      } else {
        geoLayers = null;
      }
    }
  }

  /** Load an event file.  This has the effect of modifying the existing Geo Layers
      @param eventFileName The event file name
   */
  public void loadEventFile(String eventFileName) {
    debug("Load Event file "+eventFileName);
    try {
      // Open and read event type 5011 file
      DataFile hevent = new DataFile (M,eventFileName,"5011","NH",0);
      hevent.open(BaseFile.INPUT);
      String referenceFrame = hevent.getReferenceFrame();
      int numEvents = (int)hevent.getSize();
  debug("Event file reference frame '"+referenceFrame+"', num events "+numEvents);
      if (numEvents > 0) {

        LayerGeo lg = null;
        for (int offset = 0; offset < numEvents; offset++) {
          if (geoLayers == null || offset > geoLayers.length) {
            debug("Creating a new GEO layer for offset "+offset);
            lg = new LayerGeo();
            lg.setName("GEO"+(offset+1));
          } else {
            lg = geoLayers[offset];
          }
      debug("------------------------------------------------------------------");
      debug("Changing attributes for layer "+lg.getName());
          Table tab = hevent.getDataTable(offset);
      debug("Event table '"+tab+"'");

          // Mode is an 8 byte field
          Data dataMode = tab.getData("MODE");
          byte gsMode = dataMode.getB(0);
          byte tdMode = dataMode.getB(1);
          byte fdMode = dataMode.getB(2);
          byte svMode = dataMode.getB(3);
          byte iballa = dataMode.getB(4);
          byte iballb = dataMode.getB(5);
          //Rest of mode is fill
      debug("MODE -> gsmode="+gsMode+" tdmode="+tdMode+" fdmode="+fdMode+" svmode="+svMode
           +" iballa="+iballa+" iballb="+iballb);

          // Flag is two integers: one for enables and another for flags
          Data dataFlag = tab.getData("FLAG");
          int enables = dataFlag.getL(0);
          int flags   = dataFlag.getL(1);
      debug("FLAG: first long, enable="+enables+" 2nd long flags="+flags);

          // The TIME, TDOA/DTDOA as well as the FDOA/DFDOA are packed into a 2D format
          Data dataTIME = tab.getData("TIME");
          double time = dataTIME.getD(0), dtime = dataTIME.getD(1);
      debug("TIME/DTIME old "+lg.getTime()+" dtime??? new -> "+time+" / "+dtime);

          Data dataTDOA = tab.getData("TDOA");
          double tdoa = dataTDOA.getD(0),  dtdoa = dataTDOA.getD(1);
      debug("TDOA/DTDOA old -> "+lg.getTdoa()+" "+lg.getDTdoa()+" new -> "+tdoa+" / "+dtdoa);

          Data dataFDOA = tab.getData("FDOA");
          double fdoa = dataFDOA.getD(0), dfdoa = dataFDOA.getD(1);
      debug("FDOA/DFDOA old -> "+lg.getFdoa()+" "+lg.getDFdoa()+" new -> "+fdoa+" / "+dfdoa);

          double flinkt = tab.getD("FRQT");
          double flinka = tab.getD("FRQA");
          double flinkb = tab.getD("FRQB");
      debug("FLINK (old) -> t="+lg.getFLinkT()+" a="+lg.getFLinkA()+" b="+lg.getFLinkB());
      debug("FLINK (new) -> t="+flinkt+" a="+flinka+" b="+flinkb);

          // POSS is the site position
          Data dataPOSS = tab.getData("POSS");
          double xs = dataPOSS.getD(0), ys=dataPOSS.getD(1), zs=dataPOSS.getD(2);
          Position sitePos = new Position();
          sitePos.setCar(xs, ys, zs);
      debug("SITE POS old="+lg.getSite().toString(Position.GEODETIC));
      debug("SITE POS new="+sitePos.toString(Position.GEODETIC));

          // POSA/VELA
          Data dataPOSA = tab.getData("POSA");
          Data dataVELA = tab.getData("VELA");
          double xa  = dataPOSA.getD(0),  ya=dataPOSA.getD(1),  za=dataPOSA.getD(2);
          double vxa = dataVELA.getD(0), vya=dataVELA.getD(1), vza=dataVELA.getD(2);
          StateVector sva = new StateVector();
          sva.setCar(xa, ya, za);
          sva.setKey("VX", ""+vxa);
          sva.setKey("VY", ""+vya);
          sva.setKey("VZ", ""+vza);
      debug("SVA old="+lg.getSVA());
      debug("SVA new="+sva);

          // POSB/VELB
          Data dataPOSB = tab.getData("POSB");
          Data dataVELB = tab.getData("VELB");
          double xb  = dataPOSB.getD(0),  yb=dataPOSB.getD(1),  zb=dataPOSB.getD(2);
          double vxb = dataVELB.getD(0), vyb=dataVELB.getD(1), vzb=dataVELB.getD(2);
          StateVector svb = new StateVector();
          svb.setCar(xb, yb, zb);
          svb.setVel(vxb, vyb, vzb);
      debug("SVB old="+lg.getSVB());
      debug("SVB new="+svb);

      String oldModes = "Site="+lg.getSiteMode()
                       +", TDOA="+lg.getTdoaMode()
                       +", FDOA="+lg.getFdoaMode()
                       +", SVEC="+lg.getSVecMode();
          lg.setSiteMode(gsMode);
          lg.setTdoaMode(tdMode);
          lg.setFdoaMode(fdMode);
          lg.setSVecMode(svMode);
      String newModes = "Site="+lg.getSiteMode()
                       +", TDOA="+lg.getTdoaMode()
                       +", FDOA="+lg.getFdoaMode()
                       +", SVEC="+lg.getSVecMode();
      debug("MODES: old="+oldModes);
      debug("MODES: new="+"Site  "+newModes);

          // Do not AUTO compute the TDOA/FDOA
          lg.setFlags("-AUTOTFDOA");
          lg.setTime(time);
          // Set the TDOA/FDOA and Delta
          lg.setTdoa(tdoa); lg.setDTdoa(dtdoa);
          lg.setFdoa(fdoa); lg.setDFdoa(dfdoa);

          lg.setFLinkT(flinkt);
          lg.setFLinkA(flinka);
          lg.setFLinkB(flinkb);

          lg.setSVA(sva);
          lg.setSVB(svb);

          lg.setSite(sitePos);

        }
        if (hevent != null) hevent.close();

      } else {
        warn("Event file "+eventFileName+" size is <= 0, no events");
      }
    } catch (Exception e) {
      e.printStackTrace();
      warn("Unable to load event file "+eventFileName);
    }
    //return lg;
  }
/* GEO Layer methods -- end*/

  /**
   * Test if the bit at a position is set.
   * @param val Value being checked
   * @param bit The bit position
   * @return true if bit is set, false otherwise
   */
  public static boolean btest (int val, int bit) {
    int bitValue = 1<<(bit);  // since 3.4.2 corrected logic for BUG 2807
    if (Installation.ApplyBugFix.XGEOPLOT_USE_ORIGINAL_BTEST) {
      bitValue = 1<<(bit-1);
    }
    return ( (val & bitValue)!=0 );
  }

  /** Get next num for layer name suffix
   *  (provided it is not already taken)
   *
   *  @return number suffix for the next auto-generated layer name
   */
  @InternalUseOnly
  public int getNextNumForLayerName () {
    return t5AutoNameCounter.get();
  }

  /** Print message
   *  @param str string message for System.out.println */
  public void print (String str) {System.out.println("XGEO: "+str); }
  /** Info message
   *  @param str string message for M.info */
  public void info  (String str) {M.info("XGEO: "+str); }
  /** Warn
   *  @param str string message for M.warning */
  public void warn  (String str) {M.warning("XGEO: "+str); }
  /** Print debug level message
   *  @param str string message for System.out.println that is active only if in DEBUG mode */
  public void debug (String str) {if (debug) System.out.println("XGEO: "+str); }

}
