#include <scwx/qt/map/radar_site_layer.hpp>
#include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/gl/draw/geo_lines.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/settings/text_settings.hpp>
#include <scwx/qt/util/maplibre.hpp>
#include <scwx/qt/util/tooltip.hpp>
#include <scwx/common/geographic.hpp>
#include <scwx/util/logger.hpp>

#include <imgui.h>
#include <mbgl/util/constants.hpp>

#include <QGuiApplication>

namespace scwx::qt::map
{

static const std::string logPrefix_ = "scwx::qt::map::radar_site_layer";
static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);

class RadarSiteLayer::Impl
{
public:
   explicit Impl(RadarSiteLayer*                       self,
                 const std::shared_ptr<gl::GlContext>& glContext) :
       self_ {self}, geoLines_ {std::make_shared<gl::draw::GeoLines>(glContext)}
   {
   }
   ~Impl() = default;

   Impl(const Impl&)             = delete;
   Impl& operator=(const Impl&)  = delete;
   Impl(const Impl&&)            = delete;
   Impl& operator=(const Impl&&) = delete;

   void RenderRadarSite(const QMapLibre::CustomLayerRenderParameters& params,
                        std::shared_ptr<config::RadarSite>& radarSite);
   void RenderRadarLine(const std::shared_ptr<MapContext>& mapContext);

   RadarSiteLayer* self_;

   std::vector<std::shared_ptr<config::RadarSite>> radarSites_ {};

   glm::vec2 mapScreenCoordLocation_ {};
   float     mapScale_ {1.0f};
   float     mapBearingCos_ {1.0f};
   float     mapBearingSin_ {0.0f};
   float     halfWidth_ {};
   float     halfHeight_ {};

   std::string hoverText_ {};

   std::shared_ptr<gl::draw::GeoLines>                       geoLines_;
   std::array<std::shared_ptr<gl::draw::GeoLineDrawItem>, 2> radarSiteLines_ {
      nullptr, nullptr};
};

RadarSiteLayer::RadarSiteLayer(
   const std::shared_ptr<gl::GlContext>& glContext) :
    DrawLayer(glContext, "RadarSiteLayer"),
    p(std::make_unique<Impl>(this, glContext))
{
}

RadarSiteLayer::~RadarSiteLayer() = default;

void RadarSiteLayer::Initialize(const std::shared_ptr<MapContext>& mapContext)
{
   logger_->debug("Initialize()");

   p->radarSites_ = config::RadarSite::GetAll();

   p->geoLines_->StartLines();
   p->radarSiteLines_[0] = p->geoLines_->AddLine();
   p->radarSiteLines_[1] = p->geoLines_->AddLine();
   p->geoLines_->FinishLines();

   static const boost::gil::rgba32f_pixel_t color0 {0.0f, 0.0f, 0.0f, 1.0f};
   static const boost::gil::rgba32f_pixel_t color1 {1.0f, 1.0f, 1.0f, 1.0f};
   static const float                       width = 1;
   p->geoLines_->SetLineModulate(p->radarSiteLines_[0], color0);
   p->geoLines_->SetLineWidth(p->radarSiteLines_[0], width + 2);

   p->geoLines_->SetLineModulate(p->radarSiteLines_[1], color1);
   p->geoLines_->SetLineWidth(p->radarSiteLines_[1], width);

   AddDrawItem(p->geoLines_);
   p->geoLines_->set_thresholded(false);

   DrawLayer::Initialize(mapContext);
}

void RadarSiteLayer::Render(
   const std::shared_ptr<MapContext>&            mapContext,
   const QMapLibre::CustomLayerRenderParameters& params)
{
   p->hoverText_.clear();

   auto mapDistance = util::maplibre::GetMapDistance(params);
   auto threshold   = units::length::kilometers<double>(
      settings::GeneralSettings::Instance().radar_site_threshold().GetValue());

   if (!(threshold.value() == 0.0 || mapDistance <= threshold ||
         (threshold.value() < 0 && mapDistance >= -threshold)))
   {
      return;
   }

   gl::OpenGLFunctions& gl = gl_context()->gl();

   // Update map screen coordinate and scale information
   p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate(
      {params.latitude, params.longitude});
   p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D /
                  mbgl::util::DEGREES_MAX;
   p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians);
   p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians);
   p->halfWidth_     = params.width * 0.5f;
   p->halfHeight_    = params.height * 0.5f;

   ImGuiFrameStart(mapContext);
   // Radar site ImGui windows shouldn't have padding
   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0.0f, 0.0f});

   for (auto& radarSite : p->radarSites_)
   {
      p->RenderRadarSite(params, radarSite);
   }

   ImGui::PopStyleVar();

   p->RenderRadarLine(mapContext);

   DrawLayer::RenderWithoutImGui(params);

   ImGuiFrameEnd();
   SCWX_GL_CHECK_ERROR();
}

void RadarSiteLayer::Impl::RenderRadarSite(
   const QMapLibre::CustomLayerRenderParameters& params,
   std::shared_ptr<config::RadarSite>&           radarSite)
{
   const std::string windowName = fmt::format("radar-site-{}", radarSite->id());

   const auto screenCoordinates =
      (util::maplibre::LatLongToScreenCoordinate(
          {radarSite->latitude(), radarSite->longitude()}) -
       mapScreenCoordLocation_) *
      mapScale_;

   // Rotate text according to map rotation
   float rotatedX = screenCoordinates.x;
   float rotatedY = screenCoordinates.y;
   if (params.bearing != 0.0)
   {
      rotatedX = screenCoordinates.x * mapBearingCos_ -
                 screenCoordinates.y * mapBearingSin_;
      rotatedY = screenCoordinates.x * mapBearingSin_ +
                 screenCoordinates.y * mapBearingCos_;
   }

   // Convert screen to ImGui coordinates
   float x = rotatedX + halfWidth_;
   float y = params.height - (rotatedY + halfHeight_);

   // Setup window to hold text
   ImGui::SetNextWindowPos(
      ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f});
   if (ImGui::Begin(windowName.c_str(),
                    nullptr,
                    ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
                       ImGuiWindowFlags_AlwaysAutoResize))
   {
      // Render text
      if (ImGui::Button(radarSite->id().c_str()))
      {
         Q_EMIT self_->RadarSiteSelected(radarSite->id());
         self_->ImGuiSelectContext();
      }

      // Store hover text for mouse picking pass
      if (settings::TextSettings::Instance()
             .radar_site_hover_text_enabled()
             .GetValue() &&
          ImGui::IsItemHovered())
      {
         hoverText_ =
            fmt::format("{} ({})\n{}\n{}, {}",
                        radarSite->id(),
                        radarSite->type_name(),
                        radarSite->location_name(),
                        common::GetLatitudeString(radarSite->latitude()),
                        common::GetLongitudeString(radarSite->longitude()));
      }

      // End window
      ImGui::End();
   }
}

void RadarSiteLayer::Impl::RenderRadarLine(
   const std::shared_ptr<MapContext>& mapContext)
{
   if ((QGuiApplication::keyboardModifiers() &
        Qt::KeyboardModifier::ShiftModifier) &&
       mapContext->radar_site() != nullptr)
   {
      const auto&  mouseCoord     = mapContext->mouse_coordinate();
      const double radarLatitude  = mapContext->radar_site()->latitude();
      const double radarLongitude = mapContext->radar_site()->longitude();

      geoLines_->SetLineLocation(radarSiteLines_[0],
                                 static_cast<float>(mouseCoord.latitude_),
                                 static_cast<float>(mouseCoord.longitude_),
                                 static_cast<float>(radarLatitude),
                                 static_cast<float>(radarLongitude));
      geoLines_->SetLineVisible(radarSiteLines_[0], true);

      geoLines_->SetLineLocation(radarSiteLines_[1],
                                 static_cast<float>(mouseCoord.latitude_),
                                 static_cast<float>(mouseCoord.longitude_),
                                 static_cast<float>(radarLatitude),
                                 static_cast<float>(radarLongitude));
      geoLines_->SetLineVisible(radarSiteLines_[1], true);
   }
   else
   {
      geoLines_->SetLineVisible(radarSiteLines_[0], false);
      geoLines_->SetLineVisible(radarSiteLines_[1], false);
   }
}

void RadarSiteLayer::Deinitialize()
{
   logger_->debug("Deinitialize()");

   p->radarSites_.clear();
}

bool RadarSiteLayer::RunMousePicking(
   const std::shared_ptr<MapContext>& /* mapContext */,
   const QMapLibre::CustomLayerRenderParameters& /* params */,
   const QPointF& /* mouseLocalPos */,
   const QPointF& mouseGlobalPos,
   const glm::vec2& /* mouseCoords */,
   const common::Coordinate& /* mouseGeoCoords */,
   std::shared_ptr<types::EventHandler>& /* eventHandler */)
{
   if (!p->hoverText_.empty())
   {
      util::tooltip::Show(p->hoverText_, mouseGlobalPos);
      return true;
   }

   return false;
}

} // namespace scwx::qt::map
