#ifndef GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H
#define GERMANAIRLINESVA_GACONNECTOR_SIMULATORDATABASE_H

#include <cstdint>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include "gate.h"
#include "runway.h"

#define CURRENT_VERSION 1

/*
 * Header
 *
 * CHAR[5] | UINT8
 * --------+--------
 * VGAS    | VERSION
 */
/*
 * Airport
 *
 * UINT8  | CHAR[] | UINT16   | GATE[] | UINT8   | RUNWAY[]
 * --------+--------+----------+--------+---------+---------
 * STRLEN | ICAO   | NUMGATES | GATES  | NUMRWYS | RUNWAYS
 */

namespace germanairlinesva_simdata
{
  static inline void
      toFile(std::map<std::string,
                      std::pair<std::vector<germanairlinesva_simdata::Gate>,
                                std::vector<germanairlinesva_simdata::Runway>>>
                 &airports,
             const std::string &file)
  {
    std::uint8_t null = 0;
    std::ofstream out(file, std::fstream::binary);

    // File Header
    std::uint8_t header[] = {'V', 'G', 'A', 'S', 0, CURRENT_VERSION};
    out.write(reinterpret_cast<const char *>(header), 6);
    // Num Airports
    std::uint16_t numAirports = airports.size();
    out.write(reinterpret_cast<const char *>(&numAirports),
              sizeof(numAirports));
    // Airport
    for (const std::pair<
             const std::string,
             std::pair<std::vector<germanairlinesva_simdata::Gate>,
                       std::vector<germanairlinesva_simdata::Runway>>>
             &airport : airports) {
      std::string icao = airport.first;
      std::vector<germanairlinesva_simdata::Gate> gates = airport.second.first;
      std::vector<germanairlinesva_simdata::Runway> runways =
          airport.second.second;
      // ICAO
      std::uint8_t icaoLength = icao.length();
      out.write(reinterpret_cast<const char *>(&icaoLength),
                sizeof(icaoLength));
      out.write(icao.c_str(), icaoLength);
      out.write(reinterpret_cast<const char *>(&null), sizeof(null));
      // Gates
      std::uint16_t numGates = gates.size();
      out.write(reinterpret_cast<const char *>(&numGates), sizeof(numGates));
      for (germanairlinesva_simdata::Gate &gate : gates) {
        out.write(reinterpret_cast<const char *>(gate.getBinaryData()),
                  (std::streamsize)gate.getBinaryLength());
      }
      // Runways
      std::uint8_t numRunways = runways.size();
      out.write(reinterpret_cast<const char *>(&numRunways),
                sizeof(numRunways));
      for (germanairlinesva_simdata::Runway &runway : runways) {
        out.write(reinterpret_cast<const char *>(runway.getBinaryData()),
                  (std::streamsize)runway.getBinaryLength());
      }
    }
    out.close();
  }

  static inline std::map<
      std::string,
      std::pair<std::vector<germanairlinesva_simdata::Gate>,
                std::vector<germanairlinesva_simdata::Runway>>>
      readVersion1(std::ifstream &in)
  {
    std::map<std::string,
             std::pair<std::vector<germanairlinesva_simdata::Gate>,
                       std::vector<germanairlinesva_simdata::Runway>>>
        airports;

    std::uint16_t numAirports;
    in.read(reinterpret_cast<char *>(&numAirports), sizeof(numAirports));

    for (int i = 0; i < numAirports; i++) {
      // ICAO
      std::uint8_t icaoLength;
      in.read(reinterpret_cast<char *>(&icaoLength), sizeof(icaoLength));
      char *icao = static_cast<char *>(calloc(icaoLength + 1, sizeof(char)));
      in.read(icao, icaoLength + 1);
      // Gates
      std::uint16_t numGates;
      in.read(reinterpret_cast<char *>(&numGates), sizeof(numGates));
      for (int j = 0; j < numGates; j++) {
        // ICAO
        std::uint8_t designatorLength;
        in.read(reinterpret_cast<char *>(&designatorLength),
                sizeof(designatorLength));
        char *designator =
            static_cast<char *>(calloc(designatorLength + 1, sizeof(char)));
        in.read(designator, designatorLength + 1);
        // Center
        germanairlinesva_geodata::point center;
        in.read(reinterpret_cast<char *>(&center), sizeof(center));
        // Radius
        std::uint8_t radius;
        in.read(reinterpret_cast<char *>(&radius), sizeof(radius));

        airports[icao].first.emplace_back(designator, center, radius);
      }
      // Runways
      std::uint8_t numRunways;
      in.read(reinterpret_cast<char *>(&numRunways), sizeof(numRunways));
      for (int j = 0; j < numRunways; j++) {
        // ICAO
        std::uint8_t designatorLength;
        in.read(reinterpret_cast<char *>(&designatorLength),
                sizeof(designatorLength));
        char *designator =
            static_cast<char *>(calloc(designatorLength + 1, sizeof(char)));
        in.read(designator, designatorLength + 1);
        // Bounds
        germanairlinesva_geodata::box bounds;
        in.read(reinterpret_cast<char *>(&bounds), sizeof(bounds));
        // Width
        std::uint8_t width;
        in.read(reinterpret_cast<char *>(&width), sizeof(width));
        // Length
        std::uint16_t length;
        in.read(reinterpret_cast<char *>(&length), sizeof(length));
        // True Heading
        std::uint16_t trueHeading;
        in.read(reinterpret_cast<char *>(&trueHeading), sizeof(trueHeading));

        airports[icao].second.emplace_back(designator,
                                           bounds,
                                           width,
                                           length,
                                           trueHeading);
      }
    }

    in.close();

    return airports;
  }

  static inline std::map<
      std::string,
      std::pair<std::vector<germanairlinesva_simdata::Gate>,
                std::vector<germanairlinesva_simdata::Runway>>>
      fromFile(const std::string &file)
  {
    std::map<std::string,
             std::pair<std::vector<germanairlinesva_simdata::Gate>,
                       std::vector<germanairlinesva_simdata::Runway>>>
        airports;
    std::ifstream in(file, std::ifstream::binary);

    // File Header
    char ident[5];
    in.read(ident, 5);
    if (strcmp(ident, "VGAS") != 0) {
      throw std::invalid_argument("Wrong file");
    }
    std::uint8_t version;
    in.read(reinterpret_cast<char *>(&version), 1);

    if (version == 1) {
      return readVersion1(in);
    }
    return airports;
  }
} // namespace germanairlinesva_simdata

#endif
