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.prim.plot;

/** Used for testing timeline issues. Outputs a file with a time line.<br><br>
    Global Arguments and Switches:

    <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)
      /WRAP    - Number of times to wrap input data (time does keeps increasing) (default is 0)
    </pre>

    Switches only applicable to Trajectory and Flight:
    <pre>
      /ALT_SHIFT - Altitude shift of path once destination is reached
      /ALT_START - Altitude start
      /ELLIPSOID - Whether or not to plot ellipsoids
      /LAT_DEST  - Latitude destination
      /LAT_SHIFT - Latitude shift of path once destination is reached
      /LAT_START - Latitude start
      /LON_DEST  - Longitude destination
      /LON_SHIFT - Longitude shift of path once destination is reached
      /LON_START - Longitude start
    </pre>

    Switches only applicable to Trajectory
    <pre>
      /ALT_DEST - Altitude destination
      /INCR     - Number of trajectory increments (default is 50)
      /XYZ      - Whether or not using XYZ data
    </pre>
    @author  Jeanette McCall
    @since   NeXtMidas 4.1.1
*/
public class t5pathgenerator 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   alt = 0;           // needs to be field for diagonal
  private double   lat = 0;           // needs to be field for diagonal
  private double   lon = 0;           // needs to be field for diagonal
  private double   altStart = 0;       // used with trajectory and flight
  private double   latStart = 0;       // used with trajectory and flight
  private double   lonStart = 0;       // used with trajectory and flight
  private double   altDest = 0;       // used with trajectory
  private double   latDest = 0;       // used with trajectory and flight
  private double   lonDest = 0;       // used with trajectory and flight
  private double   altShift = 0;       // used with trajectory and flight
  private double   latShift = 0;       // used with trajectory and flight
  private double   lonShift = 0;       // used with trajectory and flight
  private double   altDelta = 0;
  private double   latDelta = 0;
  private double   lonDelta = 0;
  private double   origLatDelta = 0;
  private double   origLonDelta = 0;
  private boolean  landing = false;
  private int      landingPt = 0;
  private double[] landingPattern;
  private static final int SNAKE      = 1;
  private static final int FLIGHT     = 2;
  private static final int TRAJECTORY = 3;
  private int pathType;
  private byte color;
  private String directColor;
  private String namePrefix;
  private boolean noFlags;
  private boolean timeField;
  private boolean noEllipse;
  private boolean ellipsoid;
  private boolean rotate;
  private boolean xyzData;
  private String noteModeType = "";
  private char noteMode = '0';
  private char noteType = 'A';
  private static final String NOTE_A = "note_";
  private static final String NOTE_B = "info_";
  private String note = "";
  private String noteA = "";
  private String noteB = "";
  private double timeAdjustment = 0;
  private boolean outOfBounds;
  private static boolean running;
  private int pointRepeat = 0;
  private static final int NUM_POINT_REPEATS = 5;
  private static final int NUM_FLIGHT_INCREMENTS = 250;
  private static final int NUM_TRAJECTORY_INCREMENTS = 50;
  private double pathAddAlt;
  private double pathAddLat;
  private double pathAddLon;
  private static boolean varyNote = false;
  private int ellipsoidIdx = 0;

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

  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"));
    namePrefix  = MA.getS("/NAME", "POINT");
    if (namePrefix.length() > 7) namePrefix  = namePrefix.substring(0, 7);
    directColor = MA.getS("/DIRECTCOLOR");
    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))) {
      createNoteA();
      createNoteB();
    }
    String timeString = MA.getS("/STARTTIME","");
    Time startTime = Time.currentTime();
    if (!timeString.isEmpty()) {
      startTime = new Time(timeString);
      timeAdjustment = Time.currentTime().getSec() - startTime.getSec();
    }

    // Starting latitude, longitude and altitude
    alt = altStart = MA.getD("/ALT_START", 0);           // used with trajectory and flight
    lat = latStart = MA.getD("/LAT_START", randomLat()); // used with trajectory and flight
    lon = lonStart = MA.getD("/LON_START", randomLon()); // used with trajectory and flight

    // Destination latitude, longitude and altitude
    altDest = MA.getD("/ALT_DEST", 0);           // used with trajectory
    latDest = MA.getD("/LAT_DEST", randomLat()); // used with trajectory and flight
    lonDest = MA.getD("/LON_DEST", randomLon()); // used with trajectory and flight

    // Shift latitude, longitude and altitude
    altShift = MA.getD("/ALT_SHIFT", 0); // used with trajectory and flight
    latShift = MA.getD("/LAT_SHIFT", 0); // used with trajectory and flight
    lonShift = MA.getD("/LON_SHIFT", 0); // used with trajectory and flight

    if (MA.getState("/SNAKE")) {
      pathType = SNAKE;
      pathAddLat = (Math.random() > 0.5) ? 0.5 : -0.5;
      pathAddLon = (Math.random() > 0.5) ? 0.5 : -0.5;
    }
    else if (MA.getState("/FLIGHT")) {
      pathType = FLIGHT;
      landingPattern = circlingToLanding(20, (int)((Math.random()*10)+1), 3,latDest, lonDest);
      origLatDelta = latDelta = landingPattern[0] - latStart;
      origLonDelta = lonDelta = landingPattern[1] - lonStart;
      setCurrentTrajectory(NUM_FLIGHT_INCREMENTS);
    }
    else if (MA.getState("/TRAJECTORY")) {
      pathType = TRAJECTORY;
      xyzData  = MA.getState("/XYZ");
      if (xyzData) rframe = "NONE";
      altDelta = altDest - altStart;
      latDelta = latDest - latStart;
      lonDelta = lonDest - lonStart;
      int increments = MA.getL("/INCR",NUM_TRAJECTORY_INCREMENTS);
      setCurrentTrajectory(increments);
    }
    else {
      pathType = SNAKE;
      pathAddLat = (Math.random() > 0.5) ? 0.5 : -0.5;
      pathAddLon = (Math.random() > 0.5) ? 0.5 : -0.5;
    }

    noEllipse = MA.getState("/NOELLIPSE");
    ellipsoid = MA.getState("/ELLIPSOID");
    if (ellipsoid) rotate = MA.getState("/ROTATE");
    noFlags = MA.getState("/NOFLAGS");
    timeField = MA.getState("/TIME");
    outOfBounds = MA.getState("/OUTOFBOUNDS");

    String ellipseComponent   = (noEllipse)         ? "" : ",ELPS/VD/0/0";
    String ellipsoidComponent = (!ellipsoid)        ? "" : ",ELLIPSOID/6D/0/0";
    String colorComponent     = (directColor == "") ? "" : ",COLOR/2A/0/0";
    String flagComponent      = (noFlags)           ? "" : ",FLAG/8B/0/0";
    String noteComponent      = (noteA.isEmpty())   ? "" : ",NOTE/"+noteMode+""+noteType+"/0/0";
    String timeComponent      = (!timeField)        ? "" : ",TIME/SD/0/0";
    outFile.setComponents("POS/VD/6/5"+ellipseComponent+ellipsoidComponent+",NAME/2A/0/0"+colorComponent+flagComponent+noteComponent+timeComponent);

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

    return NORMAL;
  }

  private void setCurrentTrajectory (int increments) {
    pathAddAlt = altDelta / increments;
    pathAddLat = latDelta / increments;
    pathAddLon = lonDelta / increments;
  }

  public int process() {
    int    status = NORMAL;
    double time   = Time.current() - timeAdjustment;
    if (rt && (time < tnext)) {
      status = NOOP;
    }
    else {
      for (int i = 0; (i < xfer) && (offset < size); i++) {
        if (pathType==SNAKE) {
          pointRepeat = (pointRepeat++ % NUM_POINT_REPEATS);
          if (pointRepeat==0) {
            lat += pathAddLat * Math.random();
            lon += pathAddLon * Math.random();
          }
          if (lat > 90)
          {
            lat = 90;
           pathAddLat = -pathAddLat;
          }
          else if (lat < -90)
          {
            lat = -90;
            pathAddLat = -pathAddLat;
          }
          if (lon > 180)
          {
            lon = -180;
          }
          else if (lon < -180)
          {
            lon = 180;
          }
        }
        else if (pathType == FLIGHT) {
          pointRepeat = (pointRepeat++ % NUM_POINT_REPEATS);
          if (pointRepeat==0) {
            lat += pathAddLat;
            lon += pathAddLon;
            boolean reachedLat = (pathAddLat > 0) ? lat >= landingPattern[0] : lat <= landingPattern[0];
            boolean reachedLon = (pathAddLon > 0) ? lon >= landingPattern[1] : lon <= landingPattern[1];
            if (landing) {
              lat = landingPattern[landingPt*2];
              lon = landingPattern[landingPt*2 + 1];
              landingPt++;
              if (landingPt*2 >= landingPattern.length) {
                lat = latStart = incrementWithWrap(latStart, latShift, -90, 90);
                lon = lonStart = incrementWithWrap(lonStart, lonShift, -180, 540); // use extended bounds
                latDest  = latStart + origLatDelta;
                lonDest  = lonStart + origLonDelta;
                landingPattern = circlingToLanding(20, (int)((Math.random()*15)+1), 3,latDest, lonDest);
                latDelta = landingPattern[0] - latStart;
                lonDelta = landingPattern[1] - lonStart;
                setCurrentTrajectory(NUM_FLIGHT_INCREMENTS);
                landing = false;
                landingPt = 0;
              }
            }
            else if (reachedLon || reachedLat) {
              landing = true;
              landingPt = 0;
            }
          }
        }
        else { // TRAJECTORY
          pointRepeat = (pointRepeat++ % NUM_POINT_REPEATS);
          if (pointRepeat==0) {
            alt += pathAddAlt * Math.random();
            lat += pathAddLat * Math.random();
            lon += pathAddLon * Math.random();
            boolean reachedLat = (pathAddLat > 0) ? lat > latDest : lat < latDest;
            boolean reachedLon = (pathAddLon > 0) ? lon > lonDest : lon < lonDest;
            if (reachedLon || reachedLat) {
              if (!xyzData) {
                alt = altStart = altStart + altShift;
                lat = latStart = incrementWithWrap(latStart, latShift, -90, 90);
                lon = lonStart = incrementWithWrap(lonStart, lonShift, -180, 540);
              }
              else {
                alt = altStart = altStart + altShift;
                lat = latStart = latStart + latShift;
                lon = lonStart = lonStart + lonShift;
              }
              altDest = altStart + altDelta;
              latDest = latStart + latDelta;
              lonDest = lonStart + lonDelta;
            }
          }
        }
        outData.packD( 0, alt);                  // alt
        outData.packD( 8, lat);                  // lat
        outData.packD(16, lon);                  // lon

        int byteOff = 24;
        if (!noEllipse) {
          outData.packD(24, 30000.0);              // semi-major
          outData.packD(32, 7000.0);               // semi-minor
          outData.packD(40, 45.0);                 // tilt
          byteOff += 24;
        }

        if (ellipsoid) {
          double rotX = 0, rotY = 0, rotZ = 0;
          if (rotate) {
            rotX = (ellipsoidIdx == 0) ? 45 : 0;
            rotY = (ellipsoidIdx == 1) ? 25 : 0;
            rotZ = (ellipsoidIdx == 2) ? 60 : 0;
            ellipsoidIdx=++ellipsoidIdx % 4; // allow for every fourth to have no rotation
          }
          outData.packD(byteOff,    100000);     // semi-major
          outData.packD(byteOff+8,  500000);     // semi-minor
          outData.packD(byteOff+16, 250000);     // semi-median
          outData.packD(byteOff+24, rotX);       // rotation X
          outData.packD(byteOff+32, rotY);       // rotation Y
          outData.packD(byteOff+40, rotZ);       // rotation Z
          byteOff+=48;
        }

        outData.packS(byteOff, 16, namePrefix +"_"+offset);  // name
        byteOff += 16;

        if (directColor != "") {
          outData.packS(byteOff, 16, directColor);  // direct color
          byteOff += 16;
        }

        if (!noFlags) {
          //8,0,6,2,-9,1,4,4
          outData.packB(byteOff++, (byte)0);
          outData.packB(byteOff++, (byte)0);
          outData.packB(byteOff++, color);
          outData.packB(byteOff++, (byte)2);
          outData.packB(byteOff++, Byte.valueOf("-9"));
          outData.packB(byteOff++, (byte)1);
          outData.packB(byteOff++, (byte)4);
          outData.packB(byteOff++, (byte)4);
        }

        if (!noteA.isEmpty()) {
          int len = Data.getSPA(noteMode)*Data.getBPS(noteType);
          if (varyNote && (offset%3 == 0)) note = noteB;
          else note = noteA;
          outData.packS(byteOff, len, note);
          byteOff += len;
        }

        if (timeField) {
          outData.packD(byteOff, time);
          byteOff += 8;
        }

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

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

  private double incrementWithWrap (double initialValue, double increment, double minValue, double maxValue) {
    double result = initialValue + increment;
    if (result < minValue) result += maxValue - minValue;
    if (result > maxValue) result -= maxValue - minValue;
    return result;
  }

  public int close() {
    if (outFile != null) outFile.close();
    t5pathgenerator.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 createNoteA () {
    int atypeMultiplier = (noteType == DataTypes.ASCII256) ? 32 : 1;
    int repeats = Data.getSPA(noteMode) * atypeMultiplier;
    for (int i = 1; i <= repeats; i++) {
      noteA += NOTE_A + (i%10)+ "  ";
    }
    noteA = noteA.substring(0, Math.max(noteA.length() -2,0) );
  }

  private void createNoteB () {
    int atypeMultiplier = (noteType == DataTypes.ASCII256) ? 32 : 1;
    int repeats = Data.getSPA(noteMode) * atypeMultiplier;
    for (int i = 1; i <= repeats; i++) {
      noteB += NOTE_B + (i%10) + "  ";
    }
    noteB = noteB.substring(0, Math.max(noteB.length() -2,0) );
  }

  /** Should the NOTE field vary in content
   *  @param varyNote true if should vary
   */
  public static void setVaryNote (boolean varyNote) {
    t5pathgenerator.varyNote = varyNote;
  }

  /** 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;
  }

  private double[] circlingToLanding (int num8thOfPass, int num8thOfPasses, double radius,
      double latCtr, double lonCtr) {
    int landingPoints = num8thOfPass;
    int totalCirclingPts = num8thOfPass*num8thOfPasses;
    int ptsPerCircle = num8thOfPass * 8;
    double[] latLonPts = new double[(totalCirclingPts + landingPoints)*2];
    int p = 0;
    double lastLat = latCtr; // temp
    double lastLon = lonCtr; // temp
    for (int i = 0; i < totalCirclingPts; i++) {
      double angle = Math.toRadians(((double) i / ptsPerCircle) * 360.0);
      latLonPts[p++] = lastLat = latCtr + (Math.cos(angle) * radius);
      latLonPts[p++] = lastLon = lonCtr + (Math.sin(angle) * radius);
//      System.out.println("landing data lat="+lastLat+" lon="+lastLon);
    }
    double latLandingInc = (latCtr - lastLat) / landingPoints;
    double lonLandingInc = (lonCtr - lastLon) / landingPoints;
    for (int i = 0; i < landingPoints; i++) {
      latLonPts[p++] = lastLat = lastLat + latLandingInc;
      latLonPts[p++] = lastLon = lastLon + lonLandingInc;
    }
    return latLonPts;
  }
}
