// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run

#include "polyscope_test.h"

#include "polyscope/curve_network.h"
#include "polyscope/pick.h"
#include "polyscope/point_cloud.h"
#include "polyscope/polyscope.h"
#include "polyscope/surface_mesh.h"
#include "polyscope/types.h"
#include "polyscope/volume_mesh.h"

#include "gtest/gtest.h"

#include <array>
#include <iostream>
#include <list>
#include <string>
#include <vector>


// ============================================================
// =============== Basic tests
// ============================================================


// Show the gui. Note that the pre-suite script calls Polyscope::init() before
TEST_F(PolyscopeTest, InitializeAndShow) { polyscope::show(3); }

TEST_F(PolyscopeTest, FrameTick) {
  for (int i = 0; i < 5; i++) {
    polyscope::frameTick();
  }
}

TEST_F(PolyscopeTest, FrameTickWithImgui) {

  auto showCallback = [&]() { ImGui::Button("do something"); };
  polyscope::state::userCallback = showCallback;

  for (int i = 0; i < 5; i++) {
    polyscope::frameTick();
  }

  polyscope::state::userCallback = nullptr;
}


// We should be able to nest calls to show() via the callback. ImGUI causes headaches here
TEST_F(PolyscopeTest, NestedShow) {

  auto showCallback = [&]() { polyscope::show(3); };
  polyscope::state::userCallback = showCallback;
  polyscope::show(3);

  polyscope::state::userCallback = nullptr;
}

TEST_F(PolyscopeTest, NestedShowWithFrameTick) {

  auto showCallback = [&]() { polyscope::show(3); };
  polyscope::state::userCallback = showCallback;

  for (int i = 0; i < 3; i++) {
    polyscope::frameTick();
  }

  polyscope::state::userCallback = nullptr;
}

TEST_F(PolyscopeTest, Unshow) {

  int32_t count = 0;
  auto showCallback = [&]() {
    if (count > 1) {
      polyscope::unshow();
    }
    count++;
  };
  polyscope::state::userCallback = showCallback;
  polyscope::show(10);

  EXPECT_LT(count, 4);

  polyscope::state::userCallback = nullptr;
}

TEST_F(PolyscopeTest, ShutdownAndReinitialize) {
  polyscope::shutdown();
  SetUpTestSuite();
  polyscope::show(3);

  // do it twice -- we've had some bugs where the first shutdown doesn't clean up properly
  polyscope::shutdown();
  SetUpTestSuite();
  polyscope::show(3);
}

// Make sure that creating an empty buffer does not throw errors
TEST_F(PolyscopeTest, EmptyBuffer) {


  std::vector<glm::vec3> empty_points;
  polyscope::PointCloud* psPoints = polyscope::registerPointCloud("empty cloud", empty_points);
  polyscope::show(3);

  std::vector<std::array<uint32_t, 2>> empty_edges;
  polyscope::CurveNetwork* psNet = polyscope::registerCurveNetwork("empty curve", empty_points, empty_edges);
  polyscope::show(3);

  polyscope::removeAllStructures();
}

TEST_F(PolyscopeTest, WindowProperties) {

  // set/get window size
  int32_t target_w = 500;
  int32_t target_h = 300;
  polyscope::view::setWindowSize(target_w, target_h);
  int32_t w, h;
  std::tie(w, h) = polyscope::view::getWindowSize();
  EXPECT_EQ(w, target_w);
  EXPECT_EQ(h, target_h);

  polyscope::show(3);

  // get buffer size
  // (hard to say what this should be, given hi-dpi etc)
  int32_t buffer_w, buffer_h;
  std::tie(buffer_w, buffer_h) = polyscope::view::getBufferSize();

  float target_aspect = target_w / static_cast<float>(target_h);
  float buffer_aspect = buffer_w / static_cast<float>(buffer_h);
  EXPECT_NEAR(target_aspect, buffer_aspect, 0.01);


  // resizable
  polyscope::view::setWindowResizable(false);
  EXPECT_FALSE(polyscope::view::getWindowResizable());

  polyscope::show(3);
}

TEST_F(PolyscopeTest, Screenshot) {
  polyscope::screenshot("test_screeshot.png");
  polyscope::screenshot();

  polyscope::ScreenshotOptions opts;
  opts.includeUI = true;
  opts.transparentBackground = false;
  polyscope::screenshot(opts);
}

TEST_F(PolyscopeTest, ScreenshotBuffer) {
  std::vector<unsigned char> buff = polyscope::screenshotToBuffer();
  EXPECT_EQ(buff.size(), polyscope::view::bufferWidth * polyscope::view::bufferHeight * 4);

  std::vector<unsigned char> buff2 = polyscope::screenshotToBuffer(false);
  EXPECT_EQ(buff2.size(), polyscope::view::bufferWidth * polyscope::view::bufferHeight * 4);
}

TEST_F(PolyscopeTest, ImPlotBasic) {

  std::vector<float> xvals = {0., 2., 4., 8.};

  auto showCallback = [&]() {
    ImGui::Button("do something");
    if (ImPlot::BeginPlot("test plot")) {
      ImPlot::PlotLine("test line", &xvals.front(), xvals.size());
      ImPlot::EndPlot();
    }
  };
  polyscope::state::userCallback = showCallback;

  polyscope::show(3);

  for (int i = 0; i < 3; i++) {
    polyscope::frameTick();
  }

  polyscope::state::userCallback = nullptr;
}


TEST_F(PolyscopeTest, ImPlotScreenshot) {
  // test this because there is some context logic duplicated there

  std::vector<float> xvals = {0., 2., 4., 8.};

  auto showCallback = [&]() {
    ImGui::Button("do something");
    if (ImPlot::BeginPlot("test plot")) {
      ImPlot::PlotLine("test line", &xvals.front(), xvals.size());
      ImPlot::EndPlot();
    }
  };
  polyscope::state::userCallback = showCallback;

  polyscope::show(3);

  polyscope::ScreenshotOptions opts;
  opts.includeUI = true;
  opts.transparentBackground = false;
  polyscope::screenshot(opts);

  polyscope::show(3);

  polyscope::state::userCallback = nullptr;
}

// ============================================================
// =============== View and navigation
// ============================================================

TEST_F(PolyscopeTest, NavigationMode) {

  // Cycle through the navigation options

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::Turntable);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::Free);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::Planar);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::Arcball);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::None);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::FirstPerson);
  polyscope::show(3);

  polyscope::view::setNavigateStyle(polyscope::NavigateStyle::Turntable); // set back to usual default
}

TEST_F(PolyscopeTest, UIScale) {

  // this should trigger a reload of the font atlas

  polyscope::options::uiScale = 1.25;
  polyscope::show(3);

  polyscope::options::uiScale = 1.;
  polyscope::show(3);
}

// ============================================================
// =============== Ground plane tests
// ============================================================

TEST_F(PolyscopeTest, GroundPlaneTest) {

  // Add a structure and cycle through the ground plane options
  auto psMesh = registerTriangleMesh();

  polyscope::options::groundPlaneMode = polyscope::GroundPlaneMode::None;
  polyscope::refresh();
  polyscope::show(3);

  polyscope::options::groundPlaneMode = polyscope::GroundPlaneMode::Tile;
  polyscope::refresh();
  polyscope::show(3);

  polyscope::options::groundPlaneMode = polyscope::GroundPlaneMode::TileReflection;
  polyscope::refresh();
  polyscope::show(3);

  polyscope::options::groundPlaneMode = polyscope::GroundPlaneMode::ShadowOnly;
  polyscope::refresh();
  polyscope::show(3);

  polyscope::options::groundPlaneHeightMode = polyscope::GroundPlaneHeightMode::Manual;
  polyscope::options::groundPlaneHeight = -0.3;
  polyscope::show(3);

  polyscope::options::groundPlaneHeightMode = polyscope::GroundPlaneHeightMode::Automatic;
  polyscope::show(3);

  polyscope::removeAllStructures();
}
