#include "tracker_frame.h"

#include <fmt/core.h>
#include <wx/aboutdlg.h>
#include <wx/choicebk.h>
#include <wx/filedlg.h>
#include <wx/notebook.h>
#include <wx/splitter.h>
#include <wx/stdpaths.h>
#include <wx/webrequest.h>

#include <algorithm>
#include <nlohmann/json.hpp>
#include <sstream>

#include "achievements_pane.h"
#include "ap_state.h"
#include "connection_dialog.h"
#include "ipc_dialog.h"
#include "ipc_state.h"
#include "items_pane.h"
#include "log_dialog.h"
#include "logger.h"
#include "options_pane.h"
#include "paintings_pane.h"
#include "settings_dialog.h"
#include "subway_map.h"
#include "tracker_config.h"
#include "tracker_panel.h"
#include "version.h"

namespace {

std::string GetStatusMessage() {
  std::string msg = AP_GetStatusMessage();

  std::optional<std::string> ipc_msg = IPC_GetStatusMessage();
  if (ipc_msg) {
    msg += " ";
    msg += *ipc_msg;
  }

  return msg;
}

}  // namespace

enum TrackerFrameIds {
  ID_AP_CONNECT = 1,
  ID_CHECK_FOR_UPDATES = 2,
  ID_SETTINGS = 3,
  ID_ZOOM_IN = 4,
  ID_ZOOM_OUT = 5,
  ID_IPC_CONNECT = 7,
  ID_LOG_DIALOG = 8,
};

wxDEFINE_EVENT(STATE_RESET, wxCommandEvent);
wxDEFINE_EVENT(STATE_CHANGED, StateChangedEvent);
wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent);

TrackerFrame::TrackerFrame()
    : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition,
              wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) {
  ::wxInitAllImageHandlers();

  AP_SetTrackerFrame(this);
  IPC_SetTrackerFrame(this);

  SetTheIconCache(&icons_);

  updater_ = std::make_unique<Updater>(this);
  updater_->Cleanup();

  wxMenu *menuFile = new wxMenu();
  menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago");
  menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo");
  menuFile->Append(ID_SETTINGS, "&Settings");
  menuFile->Append(wxID_EXIT);

  wxMenu *menuView = new wxMenu();
  zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+");
  zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--");
  menuView->AppendSeparator();
  menuView->Append(ID_LOG_DIALOG, "Show Log Window\tCtrl-L");

  zoom_in_menu_item_->Enable(false);
  zoom_out_menu_item_->Enable(false);

  wxMenu *menuHelp = new wxMenu();
  menuHelp->Append(wxID_ABOUT);
  menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates");

  wxMenuBar *menuBar = new wxMenuBar();
  menuBar->Append(menuFile, "&File");
  menuBar->Append(menuView, "&View");
  menuBar->Append(menuHelp, "&Help");

  SetMenuBar(menuBar);

  CreateStatusBar();

  Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT);
  Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT);
  Bind(wxEVT_MENU, &TrackerFrame::OnApConnect, this, ID_AP_CONNECT);
  Bind(wxEVT_MENU, &TrackerFrame::OnIpcConnect, this, ID_IPC_CONNECT);
  Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS);
  Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this,
       ID_CHECK_FOR_UPDATES);
  Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN);
  Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT);
  Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG);
  Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this);
  Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged,
       this);
  Bind(STATE_RESET, &TrackerFrame::OnStateReset, this);
  Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this);
  Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this);
  Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this);

  wxSize logicalSize = FromDIP(wxSize(1280, 728));

  splitter_window_ = new wxSplitterWindow(this, wxID_ANY);
  splitter_window_->SetMinimumPaneSize(logicalSize.x / 5);

  wxChoicebook *choicebook = new wxChoicebook(splitter_window_, wxID_ANY);

  achievements_pane_ = new AchievementsPane(choicebook);
  choicebook->AddPage(achievements_pane_, "Achievements");

  items_pane_ = new ItemsPane(choicebook);
  choicebook->AddPage(items_pane_, "Items");

  options_pane_ = new OptionsPane(choicebook);
  choicebook->AddPage(options_pane_, "Options");

  paintings_pane_ = new PaintingsPane(choicebook);
  choicebook->AddPage(paintings_pane_, "Paintings");

  notebook_ = new wxNotebook(splitter_window_, wxID_ANY);
  tracker_panel_ = new TrackerPanel(notebook_);
  subway_map_ = new SubwayMap(notebook_);
  notebook_->AddPage(tracker_panel_, "Map");
  notebook_->AddPage(subway_map_, "Subway");

  splitter_window_->SplitVertically(choicebook, notebook_, logicalSize.x / 4);

  SetSize(logicalSize);

  if (!GetTrackerConfig().asked_to_check_for_updates) {
    GetTrackerConfig().asked_to_check_for_updates = true;

    if (wxMessageBox(
            "Check for updates automatically when the tracker is opened?",
            "Lingo AP Tracker", wxYES_NO) == wxYES) {
      GetTrackerConfig().should_check_for_updates = true;
    } else {
      GetTrackerConfig().should_check_for_updates = false;
    }

    GetTrackerConfig().Save();
  }

  if (GetTrackerConfig().should_check_for_updates) {
    updater_->CheckForUpdates(/*invisible=*/true);
  }

  SetStatusText(GetStatusMessage());
}

void TrackerFrame::ConnectToAp(std::string server, std::string user,
                               std::string pass) {
  QueueEvent(new ApConnectEvent(CONNECT_TO_AP, GetId(), std::move(server),
                                std::move(user), std::move(pass)));
}

void TrackerFrame::UpdateStatusMessage() {
  QueueEvent(new wxCommandEvent(STATUS_CHANGED));
}

void TrackerFrame::ResetIndicators() {
  QueueEvent(new wxCommandEvent(STATE_RESET));
}

void TrackerFrame::UpdateIndicators(StateUpdate state) {
  QueueEvent(new StateChangedEvent(STATE_CHANGED, GetId(), std::move(state)));
}

void TrackerFrame::OnAbout(wxCommandEvent &event) {
  wxAboutDialogInfo about_info;
  about_info.SetName("Lingo Archipelago Tracker");
  about_info.SetVersion(kTrackerVersion.ToString());
  about_info.AddDeveloper("hatkirby");
  about_info.AddDeveloper("art0007i");
  about_info.AddArtist("Brenton Wildes");
  about_info.AddArtist("kinrah");

  wxAboutBox(about_info);
}

void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); }

void TrackerFrame::OnApConnect(wxCommandEvent &event) {
  ConnectionDialog dlg;

  if (dlg.ShowModal() == wxID_OK) {
    GetTrackerConfig().connection_details.ap_server = dlg.GetServerValue();
    GetTrackerConfig().connection_details.ap_player = dlg.GetPlayerValue();
    GetTrackerConfig().connection_details.ap_password = dlg.GetPasswordValue();

    std::deque<ConnectionDetails> new_history;
    new_history.push_back(GetTrackerConfig().connection_details);

    for (const ConnectionDetails &details :
         GetTrackerConfig().connection_history) {
      if (details != GetTrackerConfig().connection_details) {
        new_history.push_back(details);
      }
    }

    while (new_history.size() > 10) {
      new_history.pop_back();
    }

    GetTrackerConfig().connection_history = std::move(new_history);
    GetTrackerConfig().Save();

    AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(),
               dlg.GetPasswordValue());
  }
}

void TrackerFrame::OnIpcConnect(wxCommandEvent &event) {
  IpcDialog dlg;

  if (dlg.ShowModal() == wxID_OK) {
    GetTrackerConfig().ipc_address = dlg.GetIpcAddress();
    GetTrackerConfig().Save();

    IPC_Connect(dlg.GetIpcAddress());
  }
}

void TrackerFrame::OnSettings(wxCommandEvent &event) {
  SettingsDialog dlg;

  if (dlg.ShowModal() == wxID_OK) {
    GetTrackerConfig().should_check_for_updates =
        dlg.GetShouldCheckForUpdates();
    GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas();
    GetTrackerConfig().visible_panels = dlg.GetVisiblePanels();
    GetTrackerConfig().track_position = dlg.GetTrackPosition();
    GetTrackerConfig().Save();

    UpdateIndicators(StateUpdate{.cleared_locations = true,
                                 .player_position = true,
                                 .changed_settings = true});
  }
}

void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) {
  updater_->CheckForUpdates(/*invisible=*/false);
}

void TrackerFrame::OnZoomIn(wxCommandEvent &event) {
  if (notebook_->GetSelection() == 1) {
    subway_map_->Zoom(true);
  }
}

void TrackerFrame::OnZoomOut(wxCommandEvent &event) {
  if (notebook_->GetSelection() == 1) {
    subway_map_->Zoom(false);
  }
}

void TrackerFrame::OnOpenLogWindow(wxCommandEvent &event) {
  if (log_dialog_ == nullptr) {
    log_dialog_ = new LogDialog(this);
    log_dialog_->Show();
    TrackerSetLogDialog(log_dialog_);

    log_dialog_->Bind(wxEVT_CLOSE_WINDOW, &TrackerFrame::OnCloseLogWindow,
                      this);
  } else {
    log_dialog_->SetFocus();
  }
}

void TrackerFrame::OnCloseLogWindow(wxCloseEvent& event) {
  TrackerSetLogDialog(nullptr);
  log_dialog_ = nullptr;

  event.Skip();
}

void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
  zoom_in_menu_item_->Enable(event.GetSelection() == 1);
  zoom_out_menu_item_->Enable(event.GetSelection() == 1);
}

void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) {
  notebook_->Refresh();
}

void TrackerFrame::OnStateReset(wxCommandEvent &event) {
  tracker_panel_->UpdateIndicators(/*reset=*/true);
  achievements_pane_->UpdateIndicators();
  items_pane_->ResetIndicators();
  options_pane_->OnConnect();
  paintings_pane_->ResetIndicators();
  subway_map_->OnConnect();
  Refresh();
}

void TrackerFrame::OnStateChanged(StateChangedEvent &event) {
  const StateUpdate &state = event.GetState();

  bool hunt_panels = false;
  if (GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) {
    hunt_panels = std::any_of(
        state.panels.begin(), state.panels.end(), [](int solve_index) {
          return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)).hunt;
        });
  } else if (GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) {
    hunt_panels = true;
  }
  
  if (!state.items.empty() || !state.paintings.empty() ||
      state.cleared_locations || hunt_panels) {
    // TODO: The only real reason to reset tracker_panel during an active
    // connection is if the hunt panels setting changes. If we remove hunt
    // panels later, we can get rid of this.
    tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings);
    subway_map_->UpdateIndicators();
    Refresh();
  } else if (state.player_position && GetTrackerConfig().track_position) {
    if (notebook_->GetSelection() == 0) {
      tracker_panel_->Refresh();
    }
  }

  if (state.achievements) {
    achievements_pane_->UpdateIndicators();
  }

  if (!state.items.empty()) {
    items_pane_->UpdateIndicators(state.items);
  }

  if (!state.paintings.empty()) {
    paintings_pane_->UpdateIndicators(state.paintings);
  }
}

void TrackerFrame::OnStatusChanged(wxCommandEvent &event) {
  SetStatusText(wxString::FromUTF8(GetStatusMessage()));
}

void TrackerFrame::OnConnectToAp(ApConnectEvent &event) {
  AP_Connect(event.GetServer(), event.GetUser(), event.GetPass());
}