// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include "wpi/hal/Main.h"

#include <cstdio>
#include <memory>
#include <string_view>

#include <imgui.h>

#include "AddressableLEDGui.hpp"
#include "AnalogInputSimGui.hpp"
#include "DIOSimGui.hpp"
#include "DriverStationGui.hpp"
#include "EncoderSimGui.hpp"
#include "NetworkTablesSimGui.hpp"
#include "PCMSimGui.hpp"
#include "PHSimGui.hpp"
#include "PWMSimGui.hpp"
#include "PowerDistributionSimGui.hpp"
#include "RoboRioSimGui.hpp"
#include "TimingGui.hpp"
#include "wpi/glass/Context.hpp"
#include "wpi/glass/Storage.hpp"
#include "wpi/glass/hardware/Pneumatic.hpp"
#include "wpi/glass/other/Plot.hpp"
#include "wpi/gui/wpigui.hpp"
#include "wpi/hal/Extensions.h"
#include "wpi/halsim/gui/HALSimGui.hpp"
#include "wpi/halsim/gui/HALSimGuiExt.hpp"
#include "wpi/halsim/gui/SimDeviceGui.hpp"

using namespace halsimgui;

namespace gui = wpi::gui;

static std::unique_ptr<wpi::glass::PlotProvider> gPlotProvider;

extern "C" {
#if defined(WIN32) || defined(_WIN32)
__declspec(dllexport)
#endif
int HALSIM_InitExtension(void) {
  std::puts("Simulator GUI Initializing.");

  gui::CreateContext();
  wpi::glass::CreateContext();

  wpi::glass::SetStorageName("simgui");

  gui::AddInit([] { ImGui::GetIO().ConfigDockingWithShift = true; });

  HAL_RegisterExtension(HALSIMGUI_EXT_ADDGUIINIT,
                        reinterpret_cast<void*>((AddGuiInitFn)&AddGuiInit));
  HAL_RegisterExtension(
      HALSIMGUI_EXT_ADDGUILATEEXECUTE,
      reinterpret_cast<void*>((AddGuiLateExecuteFn)&AddGuiLateExecute));
  HAL_RegisterExtension(
      HALSIMGUI_EXT_ADDGUIEARLYEXECUTE,
      reinterpret_cast<void*>((AddGuiEarlyExecuteFn)&AddGuiEarlyExecute));
  HAL_RegisterExtension(HALSIMGUI_EXT_GUIEXIT,
                        reinterpret_cast<void*>((GuiExitFn)&GuiExit));
  HAL_RegisterExtension(
      HALSIMGUI_EXT_GETGUICONTEXT,
      reinterpret_cast<void*>((GetGuiContextFn)&gui::GetCurrentContext));
  HAL_RegisterExtension(HALSIMGUI_EXT_GETGLASSCONTEXT,
                        reinterpret_cast<void*>(
                            (GetGlassContextFn)&wpi::glass::GetCurrentContext));

  HALSimGui::GlobalInit();
  DriverStationGui::GlobalInit();
  gPlotProvider = std::make_unique<wpi::glass::PlotProvider>(
      wpi::glass::GetStorageRoot().GetChild("Plot"));
  gPlotProvider->GlobalInit();

  // These need to initialize first
  EncoderSimGui::Initialize();
  SimDeviceGui::Initialize();

  AddressableLEDGui::Initialize();
  AnalogInputSimGui::Initialize();
  DIOSimGui::Initialize();
  NetworkTablesSimGui::Initialize();
  PCMSimGui::Initialize();
  PowerDistributionSimGui::Initialize();
  PWMSimGui::Initialize();
  PHSimGui::Initialize();
  RoboRioSimGui::Initialize();
  TimingGui::Initialize();

  HALSimGui::halProvider->RegisterModel(
      "AllPneumaticControls",
      [] {
        return PCMSimGui::PCMsAnyInitialized() || PHSimGui::PHsAnyInitialized();
      },
      [] {
        return std::make_unique<wpi::glass::AllPneumaticControlsModel>(
            PCMSimGui::GetPCMsModel(), PHSimGui::GetPHsModel());
      });

  HALSimGui::halProvider->RegisterView(
      "Solenoids", "AllPneumaticControls",
      [](wpi::glass::Model* model) {
        auto pneumaticModel =
            static_cast<wpi::glass::AllPneumaticControlsModel*>(model);
        return PCMSimGui::PCMsAnySolenoids(pneumaticModel->pcms.get()) ||
               PHSimGui::PHsAnySolenoids(pneumaticModel->phs.get());
      },
      [](wpi::glass::Window* win, wpi::glass::Model* model) {
        win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
        win->SetDefaultPos(290, 20);
        return wpi::glass::MakeFunctionView([=] {
          auto pneumaticModel =
              static_cast<wpi::glass::AllPneumaticControlsModel*>(model);
          wpi::glass::DisplayPneumaticControlsSolenoids(
              pneumaticModel->pcms.get(),
              HALSimGui::halProvider->AreOutputsEnabled());
          wpi::glass::DisplayPneumaticControlsSolenoids(
              pneumaticModel->phs.get(),
              HALSimGui::halProvider->AreOutputsEnabled());
        });
      });

  HALSimGui::mainMenu.AddMainMenu([] {
    if (ImGui::BeginMenu("Hardware")) {
      HALSimGui::halProvider->DisplayMenu();
      ImGui::EndMenu();
    }
    if (ImGui::BeginMenu("NetworkTables")) {
      NetworkTablesSimGui::DisplayMenu();
      ImGui::Separator();
      HALSimGui::ntProvider->DisplayMenu();
      ImGui::EndMenu();
    }
    if (ImGui::BeginMenu("DS")) {
      DriverStationGui::dsManager->DisplayMenu();
      ImGui::EndMenu();
    }
    if (ImGui::BeginMenu("Plot")) {
      bool paused = gPlotProvider->IsPaused();
      if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
        gPlotProvider->SetPaused(paused);
      }
      ImGui::Separator();
      gPlotProvider->DisplayMenu();
      ImGui::EndMenu();
    }
    if (HALSimGui::manager->GetNumWindows() > 0 && ImGui::BeginMenu("Window")) {
      HALSimGui::manager->DisplayMenu();
      ImGui::EndMenu();
    }
  });

  if (!gui::Initialize("Robot Simulation", 1280, 720,
                       ImGuiConfigFlags_DockingEnable)) {
    return 0;
  }
  HAL_RegisterExtension(
      HALSIMGUI_EXT_GETIMGUICONTEXT,
      reinterpret_cast<void*>((GetImguiContextFn)&ImGui::GetCurrentContext));
  HAL_RegisterExtensionListener(
      nullptr, [](void*, const char* name, void* data) {
        if (std::string_view{name} == "ds_socket") {
          DriverStationGui::SetDSSocketExtension(data);
        }
      });
  HAL_SetMain(
      nullptr,
      [](void*) {
        gui::Main();
        wpi::glass::DestroyContext();
        gui::DestroyContext();
      },
      [](void*) { gui::Exit(); });
  std::puts("Simulator GUI Initialized!");

  return 0;
}
}  // extern "C"
