import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser';
import { generateTangentArc } from '../pathGenerators/generateTangentArc';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';

export const TerminatorsCF = (
  leg: CFTerminalEntry,
  previousFix: NavFix,
  lastCourse: number,
  waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
  const speed = computeSpeed(leg, previousFix);
  const crsIntoEndpoint = leg.Course.toTrue(previousFix);
  const line: LineSegment[] = [];

  const targetFix: NavFix = {
    latitude: leg.WptLat,
    longitude: leg.WptLon,
    name: waypoint?.Ident ?? undefined,
    isFlyOver: leg.IsFlyOver,
    altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
    speed: speed,
    speedConstraint: leg.SpeedLimit,
    altitudeConstraint: leg.Alt,
    IsFAF: leg.IsFAF,
    IsMAP: leg.IsMAP,
  };
  const crsToIntercept = leg.Course.toTrue(targetFix);

  // Compute overfly arc
  let arc1: LineSegment[] | null = null;
  let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
  if (previousFix.isFlyOver) {
    arc1 = generateTangentArc(crsIntoEndpoint, lastCourse, previousFix, targetFix, leg.TurnDir);
  } else {
    arc1 = [[previousFix.longitude, previousFix.latitude]];
  }

  let arc;
  if (arc1 && arc1.length > 1) {
    const endCrs = getGreatCircleBearing(
      {
        latitude: arc1.at(-1)![1],
        longitude: arc1.at(-1)![0],
      },
      targetFix
    );
    const endDist = getPreciseDistance(
      {
        latitude: arc1.at(-1)![1],
        longitude: arc1.at(-1)![0],
      },
      targetFix
    );

    if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
  }

  if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsToIntercept))) {
    const turnRate = computeTurnRate(speed, Parser.AC_BANK);
    let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);

    // Turn Dir
    if (!leg.TurnDir || leg.TurnDir === 'E') {
      let prov = lastCourse - crsIntoEndpoint;
      prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
      leg.TurnDir = prov > 0 ? 'L' : 'R';
    }

    // Generate arc
    let lastDistance = getPreciseDistance(previousFix, targetFix);
    while (!updatedCrsToIntercept.equal(crsToIntercept)) {
      let interceptAngle = 0;
      if (leg.TurnDir === 'R') interceptAngle = Math.abs(lastCourse - crsToIntercept);
      else interceptAngle = Math.abs(crsToIntercept - lastCourse);

      let time = 0;
      const increment = 0.1;
      if (interceptAngle < 44.9 || interceptAngle >= 45.1) {
        if (leg.TurnDir === 'R') {
          lastCourse = (lastCourse + increment).normaliseDegrees();
          time = increment / turnRate;
        } else {
          lastCourse = (lastCourse - increment).normaliseDegrees();
          time = increment / turnRate;
        }
      } else time = increment / turnRate;

      const arcFix = computeDestinationPoint(
        {
          latitude: arc2.at(-1)![1],
          longitude: arc2.at(-1)![0],
        },
        ((speed / 3600) * time).toMetre(),
        lastCourse
      );

      arc2.push([arcFix.longitude, arcFix.latitude]);

      // Update previousFix
      previousFix.latitude = arcFix.latitude;
      previousFix.longitude = arcFix.longitude;
      updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);

      const newDistance = getPreciseDistance(previousFix, targetFix);
      if (lastDistance <= newDistance && lastDistance < 25) break;
      lastDistance = newDistance;
    }
  }

  if (!arc) arc = arc2;
  line.push(...arc);

  line.push([targetFix.longitude, targetFix.latitude]);

  return [targetFix, line];
};
