<?php

namespace germanairlinesva\file\logbook;

require_once "Recording.php";

class Logbook
{
  private const file_name = "/logbook.bin";
  private const recordings_directory = "/recordings/";
  private const ident = "VGAL";
  private const header_unpack = "a4ident/Cversion";
  private const variable_string_size_unpack = "Clength";
  private const flight_start_unpack = "a10date/a1flightType/a4flightNumber/a4aircraftType/a6aircraftRegistration";
  private const flight_middle_unpack = "a5offBlockTime/a5outTime/a5inTime/a5onBlockTime/gtotalFlightTime/" .
    "gtaxiOutFuel/ginFlightFuel/gtaxiInFuel/gtotalFuel/gtaxiOutDistance/ginFlightDistance/gtaxiInDistance/" .
    "gtotalDistance/gmaxLandingRate/Ctouchdowns/gmaxLandingGees";
  private const flight_end_unpack = "gpoints/Cflags";

  private string $directory_name = "";

  private array $flights = [];
  private int $total_flights = 0;
  private float $total_points = 0;
  private float $total_distance = 0;
  private float $total_fuel = 0;
  private array $landing_rates = [];

  public function __construct(string $directory)
  {
    $this->directory_name = $directory;

    $this->read();
  }

  /**
   * Generates a json string representation
   * 
   * @return string Json string representation 
   */
  public function to_json(): string
  {
    $retVal = [
      "flights" => $this->flights,
      "aggregates" => $this->get_aggregates(),
    ];

    return json_encode($retVal);
  }

  /**
   * Generates a json string representation of one flight
   * 
   * @return string Json string representation of one flight
   */
  public function get_flight_json($id): string
  {
    return json_encode($this->get_flight($id));
  }

  private function get_aggregates(): array
  {
    $retVal = [
      "totalFlights" => $this->total_flights,
      "totalPoints" => number_format(doubleval($this->total_points), 2, '.', ','),
      "totalDistance" => number_format(doubleval($this->total_distance), 0, '.', ','),
      "totalFuel" => number_format(doubleval($this->total_fuel), 1, '.', ','),
    ];

    if (count($this->landing_rates) > 0) {
      $rate = 0;
      $lastIndex = count($this->landing_rates) - 1;
      if ($lastIndex % 2 == 0) {
        // Two elements
        $lowerIndex = floor($lastIndex / 2);
        $upperIndex = ceil($lastIndex / 2);
        $lowerRate = $this->landing_rates[$lowerIndex];
        $upperRate = $this->landing_rates[$upperIndex];
        $rate = ($lowerRate + $upperRate) / 2;
      } else {
        // One Element
        $lowerIndex = floor($lastIndex / 2);
        $lowerRate = $this->landing_rates[$lowerIndex];
        $rate = $lowerRate;
      }
      $retVal["landingRate"] = number_format(doubleval($rate), 0, '.', ',');
    } else {
      $retVal["landingRate"] = "N/A";
    }

    return $retVal;
  }

  /**
   * Get a specific flight
   * 
   * @return array The flight
   */
  private function get_flight(int $id): array
  {
    $flight = $this->flights[$id];
    try {
      $rec = new \germanairlinesva\file\recording\Recording($this->directory_name .
        Logbook::recordings_directory . $flight["recordingFilename"] . ".rec");
      $flight["recording"] = $rec->to_json();
    } catch (\InvalidArgumentException) {
    }

    return $flight;
  }

  /**
   * Reads logbook
   */
  private function read()
  {
    $file = fopen($this->directory_name . Logbook::file_name, "rb");
    flock($file, LOCK_SH);

    $version = $this->read_header($file);

    if ($version == 1) {
      while ($this->read_flight_1($file) != false);
    }
    rsort($this->landing_rates);

    flock($file, LOCK_UN);
    fclose($file);
  }

  /**
   * Reads one flight entry
   * 
   * @param resource $file File handle
   * 
   * @return true If not EOF
   * @return false If EOF
   * 
   * @throws InvalidArgumentException If file is not a resource
   */
  private function read_flight_1($file): bool
  {
    if (false === is_resource($file)) {
      throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
    }

    $retVal = [];
    $data = fread($file, 25);
    if ($data) {
      $retVal = array_merge($retVal, unpack(Logbook::flight_start_unpack, $data));

      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["departureAirport"] = fread($file, $size);
      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["departureGate"] = fread($file, $size);
      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["departureRunway"] = fread($file, $size);
      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["arrivalAirport"] = fread($file, $size);
      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["arrivalGate"] = fread($file, $size);
      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["arrivalRunway"] = fread($file, $size);

      $retVal = array_merge($retVal, unpack(Logbook::flight_middle_unpack, fread($file, 65)));

      $size = unpack(Logbook::variable_string_size_unpack, fread($file, 1))["length"];
      $retVal["recordingFilename"] = fread($file, $size);

      $retVal = array_merge($retVal, unpack(Logbook::flight_end_unpack, fread($file, 5)));

      array_push($this->flights, $retVal);
      $this->total_flights++;
      $this->total_points += $retVal["points"];
      $this->total_distance += $retVal["totalDistance"];
      $this->total_fuel += $retVal["totalFuel"];
      array_push($this->landing_rates, $retVal["maxLandingRate"]);

      return true;
    }
    return false;
  }

  /**
   * Reads the file header
   * 
   * @param resource $file File handle
   * 
   * @return string File version
   * 
   * @throws InvalidArgumentException If file is not a resource
   * @throws UnexpectedValueException If ident mismatches
   */
  private function read_header($file): string
  {
    if (false === is_resource($file)) {
      throw new \InvalidArgumentException(sprintf('Argument must be a valid resource type. %s given.', gettype($file)));
    }

    $header = unpack(Logbook::header_unpack, fread($file, 5));

    if ($header["ident"] !== Logbook::ident) {
      throw
      new \UnexpectedValueException(sprintf("Ident mismatch. Got %s, expected %s", $header["ident"], Logbook::ident));
    }

    return $header["version"];
  }
}
