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

export const TerminatorsDF = (
  leg: DFTerminalEntry,
  previousFix: NavFix,
  lastCourse: number,
  waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
  const speed = computeSpeed(leg, previousFix);
  const turnRate = computeTurnRate(speed, Parser.AC_BANK);
  const originalCrsFromOrigin = lastCourse;
  const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];

  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,
  };

  let crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
  let force360 = getPreciseDistance(previousFix, targetFix) < 25;

  // Check if there even is an arc
  if (force360 || !lastCourse.equal(crsIntoEndpoint)) {
    // 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
    while (!lastCourse.equal(crsIntoEndpoint)) {
      let time = 0;
      if (leg.TurnDir === 'R') {
        const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
        const increment = delta < 0.1 ? delta : 0.1;
        lastCourse = (lastCourse + increment).normaliseDegrees();
        time = increment / turnRate;
      } else {
        const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
        const increment = delta < 0.1 ? delta : 0.1;
        lastCourse = (lastCourse - increment).normaliseDegrees();
        time = increment / turnRate;
      }

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

      crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);

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

      // made a loop
      if (line.length >= 3600) {
        if (!force360) {
          line.splice(1);
        }
        force360 = false;
        break;
      }
    }

    // Second half
    if (force360) {
      const temp = crsIntoEndpoint;
      crsIntoEndpoint = originalCrsFromOrigin;
      lastCourse = temp;

      while (!lastCourse.equal(crsIntoEndpoint)) {
        let time = 0;
        if (leg.TurnDir === 'R') {
          const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
          const increment = delta < 0.1 ? delta : 0.1;
          lastCourse = (lastCourse + increment).normaliseDegrees();
          time = increment / turnRate;
        } else {
          const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
          const increment = delta < 0.1 ? delta : 0.1;
          lastCourse = (lastCourse - increment).normaliseDegrees();
          time = increment / turnRate;
        }

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

        crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);

        line.push([arcFix.longitude, arcFix.latitude]);
      }
    }
  }

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

  return [targetFix, line];
};
