import "./utils/extensions.ts";
import { handleTurnAtFix } from "./utils/handleTurnAtFix.ts";
import * as geolib from "geolib";
import geojson from "geojson";

class Parser {
  private static _instance: Parser;

  private waypoints: Waypoint[];
  private runways: Runway[];
  private terminals: Terminal[];

  public static AC_SPEED = 250;

  private constructor(
    waypoints: Waypoint[],
    runways: Runway[],
    terminals: Terminal[]
  ) {
    this.waypoints = waypoints;
    this.runways = runways;
    this.terminals = terminals;
  }

  public static instance = async () => {
    if (!Parser._instance) {
      const waypoints = await (await fetch("navdata/Waypoints.json")).json();
      const runways = await (await fetch("navdata/Runways.json")).json();
      const terminals = await (await fetch("navdata/Terminals.json")).json();

      Parser._instance = new Parser(waypoints, runways, terminals);
    }

    return Parser._instance;
  };

  public parse = async (terminalID: number) => {
    // Private functions
    /**
     * @param line Line segments
     */
    const updateLastCourse = (line: LineSegment[]) => {
      lastCourse = geolib.getGreatCircleBearing(
        {
          latitude: line.at(-2)![1],
          longitude: line.at(-2)![0],
        },
        {
          latitude: line.at(-1)![1],
          longitude: line.at(-1)![0],
        }
      );
    };

    // Get Procedure main
    const terminal = this.terminals.find(({ ID }) => ID === terminalID);
    if (!terminal) throw new Error("Procedure does not exists");
    // Get runway this procedure is for
    const runway = this.runways.find(({ ID }) => ID === terminal.RwyID);
    if (!runway) throw new Error("Procedure links to non existent Runway");
    // Load procedure
    const procedure = (await (
      await fetch(`navdata/TermID_${terminalID}.json`)
    ).json()) as TerminalEntry[];

    // Output variables
    const navFixes: NavFix[] = [];
    const lineSegments: { line: LineSegment[] }[] = [];

    // Initials
    navFixes.push({
      latitude: runway.Latitude,
      longitude: runway.Longitude,
      altitude: runway.Elevation,
      speed: 0,
      name: runway.Ident,
    });
    let lastCourse = runway.TrueHeading;

    // Main
    for (let index = 0; index < procedure.length; index++) {
      const leg = procedure[index];
      const previousFix = navFixes.at(-1)!;
      const waypoint = this.waypoints.filter(({ ID }) => ID === leg.WptID)[0];

      switch (leg.TrackCode) {
        case "AF":
        case "CA":
        case "CD":
          break;
        case "CF": {
          const _leg = leg as CFTerminalEntry;
          const targetFix: NavFix = {
            latitude: _leg.WptLat,
            longitude: _leg.WptLon,
            name: waypoint?.Ident ?? undefined,
            "marker-color": _leg.IsFlyOver ? "#ff0000" : undefined,
            isFlyOver: _leg.IsFlyOver,
            altitude: previousFix.altitude,
          };
          navFixes.push(targetFix);

          const line = handleTurnAtFix(
            _leg.Course.toTrue(previousFix),
            _leg.Course.toTrue(previousFix),
            lastCourse,
            previousFix,
            targetFix,
            _leg.TurnDir
          );

          lineSegments.push({ line });
          updateLastCourse(lineSegments.at(-1)!.line);
          break;
        }
        case "CI":
        case "CR":
        case "DF":
        case "FA":
        case "FC":
        case "FD":
        case "FM":
        case "HA":
        case "HF":
        case "HM":
        case "IF":
        case "PI":
        case "RF":
        case "TF":
        case "VA":
        case "VD":
        case "VI":
        case "VM":
        case "VR":
        default:
          console.error("Unknown TrackCode", leg.TrackCode);
          break;
      }
    }

    return geojson.parse([...navFixes, ...lineSegments], {
      Point: ["latitude", "longitude"],
      LineString: "line",
    });
  };
}

export default Parser;
