/**
  * Waypoint Encounter Class for Rob's Moving Map package
  * @author Robert J Morton YE572246C
  * @version 06 January 1998 */

/*  Contains data and methods to transact a navigational encounter between an
    aircraft and a waypoint. Each encounter is a transaction with a transient
    lifespan which lasts from when the aircraft enters the waypoint's range
    of influence until it leaves it. */

// Waypoint Encounter (Spherical Geometry version)
class wpenc implements navconst {

  private static wpenc we;  // reference to current waypoint encounter object

  private aircraft ac;  // reference to current aircraft object
  private waypnt wp;    // reference to current waypoint
  private waypnt pw;    // reference to previous waypoint
  private waypnt nw;    // reference to next waypoint

  private boolean
    approach,  // true if aircraft has not yet passed over waypoint
    toFlag;    // to/from flag for the waypoint encounter

  private double
    InRad,   // inbound radial from previous waypoint
    OutRad,  // outbound radial to next waypoint
    rBrg,    // bearing of waypoint from aircraft
    tBrg,    // bearing of aircraft from waypoint
    ReqHdg,  // heading the aircraft must fly
    pDst,    // previously computed distance to waypoint
    Dst,     // currently computed distance to waypoint
    tcr,     // radius of aircraft's minimum desired turning circle
    tcd,     // diameter of turning circle - double the above
    TCR,     // square of aircraft's turning circle radius
    RunDst,  // distance to run past this waypoint before acquiring next
    ThDst,   // threshold approach distance to decelerate to SPEED
    DL,      // half-width of approach corridor
    CM,      // distance at which corridor rules end on approach

    RS = 50 / KphRps;  // 50 kph in radians/second (min speed on landing)


  // CONSTRUCTOR: SET UP FOR APPROACH WITH NEW OUTBOUND RADIAL
  wpenc(waypnt w, aircraft ac) {
    we = this;     // reference current waypoint encounter object
    this.ac = ac;  // reference to aircraft object
    wp = w;        // get the reference of the current waypoint

    waypnt.setCurrent(wp);    // set the reference of the current waypoint
    pw = wp.getPrev();        // reference to previous waypoint object
    nw = wp.getNext();        // reference to next waypoint object
    InRad = wp.getInRad();    // inbound radial from previous waypoint
    OutRad = wp.getOutRad();  // outbound radial to next waypoint [radians]

    tcr = ac.getTCR();  // Get aircraft's turning circle radius, the square
    TCR = tcr * tcr;    // of it, its diameter and twice its diameter
    tcd = tcr + tcr;    // (which is the aircraft's threshold approach 
    ThDst = tcd + tcd;  // distance to decelerate to its landing SPEED.
    wp.DandB();         // Compute range and bearings to this waypoint and
    Dst = wp.getDst();  // find initial distance & bearings of new waypoint.

    RunDst = wp.getDST() / 2;  // Distance to run past this waypoint
                               // before capturing the next one.

    CM = ac.getCM();  // corridor rules cease when aircraft gets this close
    pDst = Dst + .1;  // initialise previous distance value
    approach = true;  // A waypoint encounter always starts off with
    toFlag = true;    // the aircraft leaving the starting point
    DL = wp.getDL();  // half-width of approach corridor for current waypoint
  }


  // ADVANCES THE AIRCRAFT AND UPDATES ALL THE ENCOUNTER VARIABLES
  boolean enRoute() {

    /* If the aircraft is still approaching the waypoint or not yet gone 
    a distance of RunDst past the waypoint, then advance the aircraft 
    along its track, make last time's distance this time's previous dist-
    ance, compute distance & bearings between aircraft and waypoint and
    signal TRUE that the aircraft is still encountering the waypoint. */

    if(approach || Dst < RunDst) {
      ac.advance(getReqHdg(),getReqSpd());
      pDst = Dst;
      wp.DandB();
      Dst = wp.getDst();
      return true; 
    }
    return false;  // Otherwise, return FALSE, indicating that the
  }                // air craft has just finished this encounter.


  // COMPUTE THE HEADING ON WHICH THE AIRCRAFT MUST FLY
  private double getReqHdg() {
    tBrg = wp.gettBrg();  // bearing of the aircraft viewed from the waypoint
    rBrg = wp.getrBrg();  // bearing of the waypoint viewed from the aircraft

    if(Dst < pDst)     // If the aircraft is approaching waypoint,
      toFlag = true;   // set the 'to' flag true
    else               // else
      toFlag = false;  // set the 'to' flag false (= 'from').

    if(approach && !toFlag  // If aircraft is now receding from the waypoint
    && Dst < tcr)           // but is still closer than its turning radius,
      approach = false;     // it is deemed to have passed the waypoint.

    /* If the aircraft is close enough to the waypoint
    to be able to use Euclidean approach & turn/retreat
    geometry, then get short-range radio heading. */

    if(Dst < wp.getRange())
      ReqHdg = getRadioHeading();

    /* Otherwise, if more than 150 kilometres from either waypoint,
    get the trans-oceanic GPS or inertial heading. */

    else
      ReqHdg = getGPSheading();

    // return the rationalised (0-2Ï€ range) required heading
    return ReqHdg = ac.RatAng(ReqHdg);
  }


  // DETERMINE THE REQUIRED SPEED OF THE AIRCRAFT
  private double getReqSpd() {

    // aircraft's current distance from the waypoint, its required speed
    if(wp.getDest()   // If aircraft has passed over
    && !approach)     // the destination waypoint,
      x = -1.75 * x;  // decelerate it by the appropriate amount.

    /* If this results in the aircraft falling below its minimum speed
    for passing a waypoint, set its speed to the minimum allowed. */

    if((rs = ac.getSPEED() * (1 + x / ThDst)) < RS)
      rs = RS;

    return rs;  // return required speed (to ac.advance())
  }


  // COMPUTE STEERING OFF-SET FROM THE WAYPOINT TO THE GUIDE-CIRCLE TANGENT
  double getStrOff(double a) {
    a = ac.BipAng(a - OutRad + π);  // deviation from the inbound radial

    // Indicates whether radial deviation is right or left
    boolean right = true;
    if(a < 0) {       // If it is to the left of the inbound radial
      a = -a;         // from the waypoint's point of view, make it positive 
      right = false;  // and use the flag to indicate its sense.
    }
    /* Now is a good time to refer to the Waypoint Encounter
    'Pre-Capture' diagram in AirNav6.htm. 'e' is the square of
    distance to where tangent touches circle. 's' is the steer-
    ing off-set: angle between bearing to waypoint and tangent. */

    double
      b = tcr * Math.cos(a),
      c = Dst - tcr * Math.sin(a),
      e = b * b + c * c - TCR,
      s = 0;

    /* If the aircraft has not yet reached the turning circle, compute
    the steering offset 's' from the waypoint bearing required to keep
    the aircraft on a tangent to the turning circle. Set the steering
    offset negative if aircraft to right of OutRad + π  */

    if(e > 0 && c != 0) {
      s = Math.atan2(b, c) - Math.atan2(tcr,Math.sqrt(e));
      if(right)
        s = -s;
    }
    return s;  // return the required steering offset
  }


  // KEEP THE AIRCRAFT WITHIN THE WIND DRIFT CORRIDOR
  private double getWndOff(double a) {

    /* The aircraft's approach distance from waypoint TIMES
    the aircraft's deviation from inbound radial InRad PLUS
    the half-width of the approach confinement corridor */

    double 
      q = Dst * Math.sin(ac.BipAng(a - InRad)) + DL,
      s = 0;  // default value of corridor re-entry steering off-set

    /* If the aircraft is outside the approach confinement corridor
    then get the 'along-track' side of the steering triangle. */

    if(Math.abs(q) > Math.abs(DL)) {
      double e = ac.getAPD();
      if(e != 0) {         // Provided it is non-zero 
        if((q /= e) > 10)  // (because it's from another class!), 
          q = 10;          // impose an excursion limit 
        else if(q < -10)   // on tangent of the steering off-set
          q = -10;         // (or corridor re-entry) angle 
        s = Math.atan(q);  // and compute the said angle.
      }
    }
    return s;  // return it as a steering offset from rBrg
  }


  /* The following method computes the required heading from bearing informa-
  tion which is relative to the waypoint's North, not the aircraft's north.
  However, the aircraft can only set its heading relative to its own view of
  which direction North is in. The returned ReqHdg will therefore be accurate
  only up to about 150km. This is of course different if the waypoint itself
  is supplying steering information as in the case of an ILS. */

  // USING WAY-POINT CENTRED BEARING INFORMATION
  private double getRadioHeading() {
    if(approach) {            // If aircraft currently in approach phase...
      double s = 0;           // set default for steering off-set angle
      if(Dst > CM)            // if aircraft still on the corrodor approach
        s = getWndOff(tBrg);  // run, keep it within the corridor boundaries.

      /* If steering command from corridor confinement computation is zero,
      i.e. there is no steering command, invoke normal approach steering. */

      if(s == 0)
        s = getStrOff(tBrg);

      /* If 's' is still zero, do not disturb last time's ReqHdg
      value but if it is now non-zero, compute the bearing of way-
      point from aircraft + its steering correction. */

      if(s != 0)
        ReqHdg = rBrg + s;

    } else {  // else, if receding, track the waypoint's outbound radial
      double
        d = Dst * Math.sin(ac.BipAng(OutRad - tBrg)),
        e = ac.getAPD();   // base of steering triangle

      // Check that 'e' is non-zero because it comes from another class.

      if(e != 0) {         // Provided 'e' is non-zero,
        if((d /= e) > 10)  // impose an exclusion limit
          d = 10;          // on the tangent 'd'
        else if(d < -10)   // of the steering off-set angle.
          d = -10;

        /* Off-set the required heading by the amount required
        to bring the aircraft back on track along the outbound
        radial to the next waypoint. */

        ReqHdg = OutRad + Math.atan(d);
      }
    }
    return ReqHdg;  // return the required heading
  }

  /* The following method returns a Required Heading value which is relative
  to correct for wind drift gets worse the greater the distance between way
  points. There is a better (but more complicated) treatment which makes the
  corrective angle proportional to the actual distance off course. */

  private double getGPSheading() {  // USES ALL BEARINGS RELATIVE TO AIRCRAFT
    if(toFlag) {   // If approaching current waypoint
      pw.DandB();  // compute bearing of previous waypoint
      ReqHdg = 2 * rBrg - pw.getrBrg() - π;
    }     else {   // Else [if receding from current waypoint]

    /* Compute the bearing of the next waypoint and steer twice the reverse
    of the difference between the bearing of the next waypoint and the ext-
    ended bearing line from the previous waypoint. */

      nw.DandB();
      ReqHdg = 2 * nw.getrBrg() - rBrg - π;
    }

    return ReqHdg;
  }

  boolean getToFlag() {return toFlag;}    // return the approach status
  static wpenc getCurrent() {return we;}  // return reference to current
}                                         // waypoint encounter object.