package nxm.sys.test;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Primitive;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;
import nxm.sys.libg.Feature;
import nxm.sys.libg.MPlot;
import nxm.sys.prim.plot;

/** Used for testing timeline issues. Outputs a file with a time line.
    <pre>
      IN=      - Input file (required)
      OUT=     - Output file (required)
      TSTART=  - Start time (default is outFile.getStart())
      TDELTA=  - Time delta (default is outFile.getDelta())
      JITTER=  - Max value of jitter to add (default is 0.0)

      /RT      - Real-time (for pipe output)
      /TL=     - Transfer length (default is 32KiB for Type 1000, 1 element for all others)
    </pre>
    Examples:
    <pre>
      nM&gt; nxm.sys.test.plotellipsetester IN=apenny.prm OUT=mypenny.prm{YU=TIME} &amp;
      --&gt; TSTART=2000:01:01::00:00:00 TDELTA=0.25 JITTER=0.125
    </pre>

    @author  J. Czechowski IV
    @since   NeXtMidas 2.5.0
*/
public class plotellipsetester extends Primitive {
  private DataFile outFile;           // Output file/pipe
  private Data     outData;           // Output data table
  private boolean  rt;                // The real-time mode
  private String   rframe;            // Reference frame
  private double   tdelta;            // File time: delta
  private double   tnext;             // System time: next transfer written (use with /RT)
  private double   size;              // Size of file
  private long     offset;            // current offset
  private double[] pos;               // position (alt,lat,lon)
  private double[] elps;              // ellipse (maj,min,tilt)
  private boolean  byQuadrants;       // fill one quadrant with points before going to next
  private boolean  diagonal = false;  // make diagonal pattern
  private double   lat = 0;           // needs to be field for diagonal
  private double   lon = 0;           // needs to be field for diagonal
  private static final int HALF_LAT_RANGE = MPlot.LATITUDE_RANGE/2;
  private static final int HALF_LON_RANGE = MPlot.LONGITUDE_RANGE/2;
  private static final int NW_QUADRANT   = 0;
  private static final int NE_QUADRANT   = 1;
  private static final int SW_QUADRANT   = 2;
  private static final int SE_QUADRANT   = 3;
  private static final int NUM_QUADRANTS = 4;
  private static final int DIAGONAL      = 4;
  private static final int RANDOM        = 5;
  private static final int SNAKE         = 6;
  private int quadrant;
  private byte color;
  private String directColor;
  private boolean noFlags;
  private String noteModeType = "";
  private char noteMode = '0';
  private char noteType = 'A';
  private boolean useSymbols = false;
  private String symbolName = "";
  private static final String NOTE_A = "__note_";
  private String note = "";
  private double timeAdjustment = 0;
  private boolean outOfBounds;
  private static boolean running;
  private boolean isSnake;
  private int snakeRepeat = 0;
  private static final int NUM_SNAKE_REPEATS = 5;
  private boolean snakeAddLat = Math.random() > 0.5;
  private boolean snakeAddLon = Math.random() > 0.5;

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

  public int open() {

    size        = MA.getD("ELEM", 32768);
    rframe      = MA.getS("RF", "ECR");
    rt          = MA.getState("/RT");
    tdelta      = 1 / MA.getD("/RATE", 1);
    xfer        = MA.getL("/TL",1);
    tnext       = 0.0;
    outFile     = MA.getDataFile("OUT");
    color       = MA.getB("/COLOR", Byte.valueOf("2"));
    directColor = MA.getS("/DIRECTCOLOR", "GREEN");
    noteModeType = MA.getS("/NOTEMODETYPE", "0A");
    noteMode    = noteModeType.charAt(0);
    if ((noteModeType.length() > 1) && (noteModeType.charAt(1) == DataTypes.ASCII256)) {
      noteType = DataTypes.ASCII256;
    }
    if ((noteMode != -1) && ((DataTypes.DATA_MODE_LIST.indexOf(noteMode) >=0) ||
        (DataTypes.MULT_DATA_MODE_LIST.indexOf(noteMode) >=0))) {
      createNote();
    }
    useSymbols = MA.isPresent("/SYMBOL");
    if (useSymbols) {
      symbolName = MA.getS("/SYMBOL","CROSS");
    }
    String timeString = MA.getS("/STARTTIME","");
    Time startTime = Time.currentTime();
    if (!timeString.isEmpty()) {
      startTime = new Time(timeString);
      timeAdjustment = Time.currentTime().getSec() - startTime.getSec();
    }
    pos         = new double[3];
    elps        = new double[3];

    byQuadrants = MA.getState("/BQ");
    if (!byQuadrants) {
     diagonal = MA.getState("/DIAGONAL");
    }

    if (byQuadrants) {
      quadrant = NW_QUADRANT;
    }
    else if (diagonal) {
      quadrant = DIAGONAL;
    }
    else {
      quadrant = RANDOM;
    }

    isSnake = MA.getState("/SNAKE");
    if (isSnake) {
      quadrant = SNAKE;
      lat = randomLat();
      lon = randomLon();
    }

    noFlags = MA.getState("/NOFLAGS");
    outOfBounds = MA.getState("/OUTOFBOUNDS");

    String noteComponent   = (note.isEmpty()) ? "" : ",NOTE/"+noteMode+""+noteType+"/0/0";
    String flagComponent   = (noFlags)        ? "" : ",FLAG/8B/0/0";
    String symbolComponent = (!useSymbols)    ? "" : ",SYMBOL/2A/0/0";
    outFile.setComponents("POS/VD/6/5,ELPS/VD/0/0,NAME/2A/0/0,COLOR/2A/0/0"+flagComponent+noteComponent+symbolComponent);

    outFile.setSize(size);
    outFile.open(BaseFile.OUTPUT);
    outFile.setTime(startTime);
    outFile.setDelta(tdelta);
    outFile.setReferenceFrame(rframe);
    outData = outFile.getDataBuffer(1);

    return NORMAL;
  }

  public int process() {
    int    status = NORMAL;
    double time   = Time.current() - timeAdjustment;
    double latFactor;
    double lonFactor;
    double latSubtract;
    double lonSubtract;
    if (rt && (time < tnext)) {
      status = NOOP;
    }
    else {
      switch (quadrant) {
        case NW_QUADRANT: // 0
          latFactor = (outOfBounds) ?   85 : 70;
          lonFactor = (outOfBounds) ? -190 : -180;
          latSubtract = lonSubtract = 0;
          break;
        case NE_QUADRANT: // 1
          latFactor = (outOfBounds) ?   85 : 70;
          lonFactor = (outOfBounds) ?  190 :  180;
          latSubtract = lonSubtract = 0;
          break;
        case SE_QUADRANT: // 2
          latFactor = (outOfBounds) ?  -85 :  -70;
          lonFactor = (outOfBounds) ?  190 :  180;
          latSubtract = lonSubtract = 0;
          break;
        case SW_QUADRANT: // 3
          latFactor = (outOfBounds) ?  -85 :  -70;
          lonFactor = (outOfBounds) ? -190 : -180;
          latSubtract = lonSubtract = 0;
          break;
        case DIAGONAL: // 4
          latFactor = 1;
          lonFactor = 2;
          latSubtract = lonSubtract = 0;
          break;
        case SNAKE:
          // need random in process
          latSubtract = lonSubtract = 0;
          latFactor = lonFactor = 0; // unused
          break;
        default:
          latFactor = (outOfBounds) ? 170 : 140.0;
          lonFactor = (outOfBounds) ? 380 : 360.0;
          latSubtract = (outOfBounds) ?  85 : 70;
          lonSubtract = (outOfBounds) ? 190 : 180;
          break;
      }
      if (byQuadrants) quadrant = (quadrant + 1) % NUM_QUADRANTS; //  mod 4


      for (int i = 0; (i < xfer) && (offset < size); i++) {
        if (quadrant==SNAKE) {
          if (isSnake) {
            snakeRepeat = (snakeRepeat++ % NUM_SNAKE_REPEATS);
            if (snakeRepeat==0) {
              lat += (snakeAddLat) ? Math.random() * .5 : Math.random() * -.5;
              lon += (snakeAddLon) ? Math.random() * .5 : Math.random() * -.5;
            }
          }
          if (lat > 90)
          {
            lat = 90;
           snakeAddLat = !snakeAddLat;
          }
          else if (lat < -90)
          {
            lat = -90;
            snakeAddLat = !snakeAddLat;
          }
          if (lon > 180)
          {
            lon = -180;
          }
          else if (lon < -180)
          {
            lon = 180;
          }
        }
        else if (quadrant!=DIAGONAL) {
          lat = Math.random() * latFactor -  latSubtract;
          lon = Math.random() * lonFactor - lonSubtract;
        }
        else {
          lat = ((lat + HALF_LAT_RANGE + latFactor) % MPlot.LATITUDE_RANGE) - (HALF_LAT_RANGE);
          lon = ((lon + HALF_LON_RANGE + lonFactor) % MPlot.LONGITUDE_RANGE) - (HALF_LON_RANGE);
        }
        outData.packD( 0, 0.0);                  // alt
        outData.packD( 8, lat);                  // lat
        outData.packD(16, lon);                  // lon
        outData.packD(24, 30000.0);              // semi-major
        outData.packD(32, 7000.0);               // semi-minor
        outData.packD(40, 45.0);                 // tilt
        outData.packS(48, 16, "POINT_"+offset);  // name
        outData.packS(64, 16, directColor);  // direct color
        if (!noFlags) {
          //8,0,6,2,-9,1,4,4
          outData.packB(80, (byte)0);
          outData.packB(81, (byte)0);
          outData.packB(82, color);
          outData.packB(83, (byte)2);
          outData.packB(84, Byte.valueOf("-9"));
          outData.packB(85, (byte)1);
          outData.packB(86, (byte)4);
          outData.packB(87, (byte)4);
        }
        int symbolStart=0;
        if (!note.isEmpty()) {
          int noteStart = (noFlags) ? 80 : 88;
          int len = Data.getSPA(noteMode)*Data.getBPS(noteType);
          outData.packS(noteStart, len, note);
          symbolStart = noteStart + len;
        }
        if (useSymbols) {
          outData.packS(symbolStart, 16, symbolName);
        }

        outFile.write(outData);
        offset++;
      }
      if (tnext==0) tnext = time + tdelta;
      else tnext += (tdelta * xfer);
    }

    return (offset < size)? status: FINISH;
  }

  public int close() {
    if (outFile != null) outFile.close();
    plotellipsetester.running = false;
    return NORMAL;
  }

  /** Reset the current plot
   *  @param plot  the plot to be reset
   *  @param sleep the sleep timer
   */
  public static void resetPlot(plot plot, int sleep) {
    running = true;
    int waitTime = sleep*1000;
    Thread t = new Thread(new Runnable() {
      @Override
      public void run () {
        long start = System.currentTimeMillis();
        int factorX = 1;
        int factorY = 1;
        while (running) {
          if ((System.currentTimeMillis() - start) > waitTime) {
            Table feat = new Table();
            Table line = new Table();
            line.put("Color", "RED");
            feat.put("NAME", "FEAT"+start);
            feat.put("TYPE", "Circle");
            feat.put("LINE", line);
            feat.put("X",Math.random()*180 * factorX);
            feat.put("Y",Math.random()*90 * factorY);
            feat.put("DX", Math.random()*20 );
            plot.MP.clearData();
            if (start % 2 == 0) factorY *= -1;
            plot.MP.addFeature(new Feature(feat));
            factorX*=-1;
            start = System.currentTimeMillis();
          }
        }
      }
    });
    t.start();
  }

  private void createNote () {
    int atypeMultiplier = (noteType == DataTypes.ASCII256) ? 32 : 1;
    int repeats = Data.getSPA(noteMode) * atypeMultiplier;
    for (int i = 1; i <= repeats; i++) {
      note += NOTE_A + (i%10);
    }
  }

  /** get a random longitude
   *  @return  a random longitude
   */
  public static double randomLon() {
    return Math.random() * 355 - 177.5;
  }

  /** get a random latitude
   *  @return  a random latitude
   */
  public static double randomLat() {
    return Math.random() * 175 - 87.5;
  }

}
