#ifndef GERMANAIRLINESVA_FILE_SIMDATA_SIMDATABASE_H
#define GERMANAIRLINESVA_FILE_SIMDATA_SIMDATABASE_H

#include <cstdint>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include "config/config.hpp"
#include "constants.h"
#include "simdata/gate.hpp"
#include "simdata/runway.hpp"
#include "simdata/simdataXP.hpp"

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

namespace germanairlinesva
{
namespace file
{
  namespace simdata
  {
    class SimDatabase
    {
      private:
        std::map<std::string, std::pair<std::vector<Gate>, std::vector<Runway>>>
            airports;

        inline void readVersion1(std::ifstream &in)
        {
          std::uint16_t numAirports = read<std::uint16_t>(in);

          for (int i = 0; i < numAirports; i++) {
            // ICAO
            std::string icao = readString(in);
            // Gates
            std::uint16_t numGates = read<std::uint16_t>(in);
            for (int j = 0; j < numGates; j++) {
              std::string designator = readString(in);
              struct geodata::point center = read<struct geodata::point>(in);
              std::uint8_t radius = read<std::uint8_t>(in);

              this->airports[icao].first.emplace_back(designator,
                                                      center,
                                                      radius);
            }
            // Runways
            std::uint8_t numRunways = read<std::uint8_t>(in);
            for (int j = 0; j < numRunways; j++) {
              std::string designator = readString(in);
              // Center
              struct geodata::box bounds = read<struct geodata::box>(in);
              std::uint8_t width = read<std::uint8_t>(in);
              std::uint16_t length = read<std::uint16_t>(in);
              double trueHeading = read<double>(in);

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

          in.close();
        }

        inline void fromFile()
        {
          std::ifstream in(BASE_DIRECTORY SIMDATABASE, std::ifstream::binary);

          std::string ident = readString(in, 4);
          if (ident.compare(SIMDATABASE_HEADER) != 0) {
            throw std::invalid_argument("Wrong file");
          }
          std::uint8_t version = read<std::uint8_t>(in);

          if (version == 1) {
            this->readVersion1(in);
          }
        }

      public:
        inline SimDatabase(int xPlaneVersion,
                           const char *hash,
                           std::unique_ptr<config::Config> &configuration,
                           std::function<void(const std::string)> toLog)
        {
          if (strcmp(configuration->getScenery().c_str(), hash) != 0 ||
              !util::fileExists(BASE_DIRECTORY SIMDATABASE)) {
#ifdef XP
            scan(xPlaneVersion < 12000 ? XPLANE11_BASE_SCENERY
                                       : XPLANE12_BASE_SCENERY,
                 XPLANE_CUSTOM_SCENERY,
                 BASE_DIRECTORY "log.txt",
                 airports);
#else
            toLog("ESP SCAN WILL BE HERE");
#endif

            configuration->updateScenery(hash);
            this->toFile();

            toLog("Sim Database updated");
          } else {
            this->fromFile();

            toLog("Sim Database loaded");
          }
        }

        inline void toFile() const
        {
          std::ofstream out(BASE_DIRECTORY SIMDATABASE, std::fstream::binary);

          // File Header
          out.write(SIMDATABASE_HEADER, 4);
          out.write("\1", 1);
          // Num Airports
          write<std::uint16_t>(out, airports.size());
          // Airport
          for (const std::pair<
                   const std::string,
                   std::pair<std::vector<Gate>, std::vector<Runway>>> &airport :
               airports) {
            std::string icao = airport.first;
            const std::vector<Gate> gates = airport.second.first;
            const std::vector<Runway> runways = airport.second.second;
            // ICAO
            writeString(out, icao);
            // Gates
            write<std::uint16_t>(out, gates.size());
            for (const Gate &gate : gates) {
              gate.toFile(out);
            }
            // Runways
            write<std::uint8_t>(out, runways.size());
            for (const Runway &runway : runways) {
              runway.toFile(out);
            }
          }
          out.close();
        }

        inline std::size_t getCount() const { return this->airports.size(); }

        inline const std::pair<const std::vector<Gate>,
                               const std::vector<Runway>>
            operator[](std::string key)
        {
          auto it = this->airports.find(key);
          if (it != this->airports.end()) {
            return this->airports[key];
          } else {
            return std::pair<std::vector<Gate>, std::vector<Runway>>();
          }
        }
    };
  } // namespace simdata
} // namespace file
} // namespace germanairlinesva

#endif
