#include "area_popup.h"

#include <wx/dcbuffer.h>

#include <algorithm>

#include "ap_state.h"
#include "game_data.h"
#include "global.h"
#include "icons.h"
#include "tracker_config.h"
#include "tracker_panel.h"
#include "tracker_state.h"

AreaPopup::AreaPopup(wxWindow* parent, int area_id)
    : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) {
  SetBackgroundStyle(wxBG_STYLE_PAINT);

  LoadIcons();

  // TODO: This is slow on high-DPI screens.
  SetScrollRate(5, 5);

  SetBackgroundColour(*wxBLACK);
  Hide();

  Bind(wxEVT_PAINT, &AreaPopup::OnPaint, this);
  Bind(wxEVT_DPI_CHANGED, &AreaPopup::OnDPIChanged, this);

  ResetIndicators();
}

void AreaPopup::ResetIndicators() {
  indicators_.clear();

  const MapArea& map_area = GD_GetMapArea(area_id_);
  wxFont the_font = GetFont().Scale(GetDPIScaleFactor());
  TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent());

  // Start calculating extents.
  wxMemoryDC mem_dc;
  mem_dc.SetFont(the_font.Bold());
  header_extent_ = mem_dc.GetTextExtent(map_area.name);

  int acc_height = header_extent_.GetHeight() + FromDIP(20);
  int col_width = 0;

  mem_dc.SetFont(the_font);

  for (int section_id = 0; section_id < map_area.locations.size();
       section_id++) {
    const Location& location = map_area.locations.at(section_id);
    if (IsLocationPostgame(location.ap_location_id)) {
      continue;
    }

    if (!AP_IsLocationVisible(location.classification) &&
        !(location.hunt &&
          GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) &&
        !(location.single_panel &&
          GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS)) {
      continue;
    }

    indicators_.emplace_back(section_id, kLOCATION, acc_height);

    wxSize item_extent = mem_dc.GetTextExtent(location.name);
    int item_height =
        std::max(FromDIP(32), item_extent.GetHeight()) + FromDIP(10);
    acc_height += item_height;

    if (item_extent.GetWidth() > col_width) {
      col_width = item_extent.GetWidth();
    }
  }

  if (AP_IsPaintingShuffle()) {
    for (int painting_id : map_area.paintings) {
      if (IsPaintingPostgame(painting_id)) {
        continue;
      }

      indicators_.emplace_back(painting_id, kPAINTING, acc_height);

      const PaintingExit& painting = GD_GetPaintingExit(painting_id);
      wxSize item_extent = mem_dc.GetTextExtent(painting.display_name);
      int item_height =
          std::max(FromDIP(32), item_extent.GetHeight()) + FromDIP(10);
      acc_height += item_height;

      if (item_extent.GetWidth() > col_width) {
        col_width = item_extent.GetWidth();
      }
    }
  }

  int item_width = col_width + FromDIP(10 + 32);
  full_width_ = std::max(header_extent_.GetWidth(), item_width) + FromDIP(20);
  full_height_ = acc_height;

  Fit();
  SetVirtualSize(full_width_, full_height_);

  UpdateIndicators();
}

void AreaPopup::UpdateIndicators() {
  const MapArea& map_area = GD_GetMapArea(area_id_);
  wxFont the_font = GetFont().Scale(GetDPIScaleFactor());
  TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent());

  rendered_ = wxBitmap(full_width_, full_height_);

  wxMemoryDC mem_dc;
  mem_dc.SelectObject(rendered_);
  mem_dc.SetPen(*wxTRANSPARENT_PEN);
  mem_dc.SetBrush(*wxBLACK_BRUSH);
  mem_dc.DrawRectangle({0, 0}, {full_width_, full_height_});

  mem_dc.SetFont(the_font.Bold());
  mem_dc.SetTextForeground(*wxWHITE);
  mem_dc.DrawText(map_area.name,
                  {(full_width_ - header_extent_.GetWidth()) / 2, FromDIP(10)});

  mem_dc.SetFont(the_font);

  for (const IndicatorInfo& indicator : indicators_) {
    switch (indicator.type) {
      case kLOCATION: {
        const Location& location = map_area.locations.at(indicator.id);

        bool checked = false;
        if (IsLocationWinCondition(location)) {
          checked = AP_HasReachedGoal();
        } else {
          checked = AP_HasCheckedGameLocation(location.ap_location_id) ||
                    (location.single_panel &&
                     AP_IsPanelSolved(
                         GD_GetPanel(*location.single_panel).solve_index));
        }

        const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_;

        mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y});

        bool reachable = IsLocationReachable(location.ap_location_id);
        const wxColour* text_color = reachable ? wxWHITE : wxRED;
        mem_dc.SetTextForeground(*text_color);

        wxSize item_extent = mem_dc.GetTextExtent(location.name);
        mem_dc.DrawText(
            location.name,
            {FromDIP(10 + 32 + 10),
             indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2});

        break;
      }
      case kPAINTING: {
        const PaintingExit& painting = GD_GetPaintingExit(indicator.id);

        bool reachable = IsPaintingReachable(indicator.id);
        const wxColour* text_color = reachable ? wxWHITE : wxRED;
        mem_dc.SetTextForeground(*text_color);

        bool checked = reachable && AP_IsPaintingChecked(painting.internal_id);
        const wxBitmap* eye_ptr = checked ? checked_owl_ : unchecked_owl_;
        mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y});

        wxSize item_extent = mem_dc.GetTextExtent(painting.display_name);
        mem_dc.DrawText(
            painting.display_name,
            {FromDIP(10 + 32 + 10),
             indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2});

        break;
      }
    }
  }
}

void AreaPopup::OnPaint(wxPaintEvent& event) {
  wxBufferedPaintDC dc(this);
  PrepareDC(dc);
  dc.DrawBitmap(rendered_, 0, 0);

  event.Skip();
}

void AreaPopup::OnDPIChanged(wxDPIChangedEvent& event) {
  LoadIcons();
  ResetIndicators();

  event.Skip();
}

void AreaPopup::LoadIcons() {
  unchecked_eye_ = GetTheIconCache().GetIcon("assets/unchecked.png",
                                             FromDIP(wxSize{32, 32}));
  checked_eye_ =
      GetTheIconCache().GetIcon("assets/checked.png", FromDIP(wxSize{32, 32}));
  unchecked_owl_ =
      GetTheIconCache().GetIcon("assets/owl.png", FromDIP(wxSize{32, 32}));
  checked_owl_ = GetTheIconCache().GetIcon("assets/checked_owl.png",
                                           FromDIP(wxSize{32, 32}));
}