#ifndef GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H
#define GERMANAIRLINESVA_FILE_SIMDATA_RUNWAY_H

#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "geodata.hpp"
#include "helpers.hpp"
#include "util.hpp"

namespace germanairlinesva
{
namespace file
{
  namespace simdata
  {
    /*
     * Representation of one runway with supplementary information
     * Heading in degrees true
     * Threshold center in lat/lon degrees
     * Width and length in metres
     *
     * UINT8  | CHAR[]     | BOX    | UINT8 | UINT16 | DOUBLE
     * -------+------------+--------+-------+--------+-------
     * STRLEN | DESIGNATOR | BOUNDS | WIDTH | LENGTH | TRUHDG
     */
    class Runway
    {
      private:
        std::string designator;
        struct geodata::box bounds;
        std::uint8_t width;
        std::uint16_t length;
        double trueHeading;

      public:
        // From X-Plane or MakeRwys
        inline Runway(std::string designator,
                      double latitudeStart,
                      double longitudeStart,
                      double latitudeEnd,
                      double longitudeEnd,
                      double width)
            : designator(designator), width(width)
        {
          this->length = geodata::distanceEarthD(latitudeStart,
                                                 longitudeStart,
                                                 latitudeEnd,
                                                 longitudeEnd);
          this->trueHeading = geodata::bearingDD(latitudeStart,
                                                 longitudeStart,
                                                 latitudeEnd,
                                                 longitudeEnd);
          this->bounds =
              geodata::calculateBoxDD({latitudeStart, longitudeStart},
                                      {latitudeEnd, longitudeEnd},
                                      this->trueHeading,
                                      this->width);
        };
        // From database
        inline Runway(std::string designator,
                      struct geodata::box bounds,
                      std::uint8_t width,
                      std::uint16_t length,
                      double trueHeading)
            : designator(designator), bounds(bounds), width(width),
              length(length), trueHeading(trueHeading){};

        inline bool containsPoint(const geodata::point point) const
        {
          size_t j = 3;
          bool c = false;
          std::vector<geodata::point> poly{this->bounds.topLeft,
                                           this->bounds.topRight,
                                           this->bounds.bottomRight,
                                           this->bounds.bottomLeft};

          for (size_t i = 0; i < poly.size(); i++) {
            if ((point.latitude == poly[i].latitude) &&
                (point.longitude == poly[i].longitude)) {
              // point is a corner
              return true;
            }
            if (((poly[i].longitude > point.longitude) !=
                 (poly[j].longitude > point.longitude))) {
              double slope = (point.latitude - poly[i].latitude) *
                                 (poly[j].longitude - poly[i].longitude) -
                             (poly[j].latitude - poly[i].latitude) *
                                 (point.longitude - poly[i].longitude);
              if (slope == 0) {
                // point is on boundary
                return true;
              }
              if ((slope < 0) != (poly[j].longitude < poly[i].longitude)) {
                c = !c;
              }
            }
            j = i;
          }

          return c;
        }

        inline void toFile(std::ofstream &out) const
        {
          writeString(out, this->designator);
          write<decltype(this->bounds)>(out, this->bounds);
          write<decltype(this->width)>(out, this->width);
          write<decltype(this->length)>(out, this->length);
          write<decltype(this->trueHeading)>(out, this->trueHeading);
        }

        inline const std::string to_string() const
        {
          std::ostringstream str;
          str << "Runway " << this->designator << " with bounds "
              << this->bounds.topLeft.latitude << "N "
              << this->bounds.topLeft.longitude << "E, "
              << this->bounds.topRight.latitude << "N "
              << this->bounds.topRight.longitude << "E, "
              << this->bounds.bottomRight.latitude << "N "
              << this->bounds.bottomRight.longitude << "E, "
              << this->bounds.bottomLeft.latitude << "N "
              << this->bounds.bottomLeft.longitude << "E, "
              << "Width " << (int)this->width << "m, Length " << this->length
              << "m, True Heading " << this->trueHeading << "°";
          return str.str();
        }

        friend inline std::ostream &operator<<(std::ostream &os,
                                               const Runway &runway)
        {
          return os << runway.to_string();
        }
    };
  } // namespace simdata
} // namespace file
} // namespace germanairlinesva

#endif