
#include <vector>
#include <algorithm>

#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include <imgui.h>

#include "Utility.hpp"
#include "Light.h"


auto window = sf::RenderWindow(sf::VideoMode({ 1920u, 1080u }), "Ray Casting Test");

Light light;
std::vector<sf::VertexArray> polygons;
sf::CircleShape circle{};

sf::Vector2f mousePos{};
sf::Vector2f center{ 1920.f / 2.f, 1080.f / 2.f };

//imGui Variables
constexpr int numRaysBounds[2] = { 5, 500 };
constexpr float rayLengthBounds[2] = {5.f, 5000.f};
constexpr float offsetAngleBounds[2] = { 5.f, 175.f };
constexpr int vertexMin{ 3 };
constexpr int vertexMax{ 20 };
constexpr float rotationSpeedScale{ 3.f };
float polySize[2] = { 100.f, 100.f };
int numRays{ 500 };
int vertexBounds[2] = { 3, 10 };
int averageSize{ 100 };
float rayLength{ 500.f };
imguiColor rayColor = constructImguiColor(sf::Color::Red);
imguiColor lightColor = constructImguiColor(sf::Color::Yellow);
imguiColor endPointColor = constructImguiColor(sf::Color::Magenta);
bool drawRays{ false };
bool drawLight{ true };
bool drawEndPoints{ false };
bool boundedLight{ false };
bool infiniteRayLength{ false };
float lightCenterAngle{ 0.f };
float lightOffsetAngle{ 45.f };

void GUI();
void userInput();
void render();

int main()
{
  //configure light with default imgui vatiables
  drawRays ? light.setFlags(Light::Flags::drawRays) : light.unsetFlags(Light::Flags::drawRays);
  drawLight ? light.setFlags(Light::Flags::visibility) : light.unsetFlags(Light::Flags::visibility);
  boundedLight ? light.addAngleBounds(sf::degrees(lightCenterAngle), sf::degrees(lightOffsetAngle)) : light.removeAngleBounds();
  drawEndPoints ? light.setFlags(Light::Flags::drawEndPoints) : light.unsetFlags(Light::Flags::drawEndPoints);
  infiniteRayLength ? light.setFlags(Light::Flags::infiniteLength) : light.unsetFlags(Light::Flags::infiniteLength);

  //sfml setup
  sf::Clock clock;
  window.setFramerateLimit(144);
  if (!ImGui::SFML::Init(window))
    return -1;

  mousePos = center;

  polygons.emplace_back(constructRectangle(center, center));

  circle.setOrigin({0,0});
  circle.setPosition(center);
  circle.setRadius(200.f);
  circle.setPointCount(30u);
  polygons.emplace_back(constructVertexArray(circle));

  //main game loop
  while (window.isOpen())
  {
    ImGui::SFML::Update(window, clock.restart());

    light.clearRays();

    userInput();

    GUI();

    light.setOrigin(mousePos);
    light.processRays(polygons);

    render();

  }

  ImGui::SFML::Shutdown();
}

void GUI()
{
  ImGui::Begin("Config");
  ImGui::Text("Controls:");
  ImGui::Indent();
  ImGui::Text("Right Click: place rectangle at mouse position");
  ImGui::Text("R: place a randomly sized polygon at mouse position");
  ImGui::Text("C: clear all polygons");
  ImGui::Text("Scroll wheel: increase/decrease light direction (if light is bounded)");
  ImGui::Unindent();
  if (ImGui::Checkbox("Render rays", &drawRays))
  {
    light.toggleFlags(Light::Flags::drawRays);
  }

  ImGui::SameLine(); 
  if (ImGui::Checkbox("Render end points", &drawEndPoints))
  {
    light.toggleFlags(Light::Flags::drawEndPoints);
  }

  ImGui::SameLine();
  if (ImGui::Checkbox("Render light", &drawLight))
  {
    light.toggleFlags(Light::Flags::visibility);
  }

  if (ImGui::Checkbox("Infinite ray length", &infiniteRayLength))
  {
    light.toggleFlags(Light::Flags::infiniteLength);
  }

  if (not infiniteRayLength)
  {
    ImGui::SliderInt("number of rays", &numRays, numRaysBounds[0], numRaysBounds[1]);
    light.setRayCount(numRays);

    if (ImGui::InputFloat("Ray Length", &rayLength))
    {
      rayLength = std::clamp(rayLength, rayLengthBounds[0], rayLengthBounds[1]);
    }
    light.setRayLength(rayLength);
  }

  if (drawRays)
  {
    ImGui::ColorEdit4("Ray color", &rayColor.r);
  }

  if (drawLight)
  {
    ImGui::ColorEdit4("Light color", &lightColor.r);
  }

  if (drawEndPoints)
  {
    ImGui::ColorEdit4("End point color", &endPointColor.r);
  }

  light.changeLightColor(lightColor.asSfColor()); 
  light.changeEndPointColor(endPointColor.asSfColor());
  light.changeRayColor(rayColor.asSfColor());

  if(ImGui::Checkbox("Light is bounded", &boundedLight))
  {
    boundedLight ? light.addAngleBounds(sf::degrees(lightCenterAngle), sf::degrees(lightOffsetAngle)) :  light.removeAngleBounds();
  }

  if (boundedLight)
  {
    ImGui::SliderFloat("Light direction", &lightCenterAngle, 0.f, 359.f, "%.0f", ImGuiSliderFlags_::ImGuiSliderFlags_AlwaysClamp);
    light.setCenterAngle(sf::degrees(lightCenterAngle));
   
    ImGui::SliderFloat("Offset size", &lightOffsetAngle, offsetAngleBounds[0], offsetAngleBounds[1], "%.0f", ImGuiSliderFlags_::ImGuiSliderFlags_AlwaysClamp);
    light.setOffsetAngle(sf::degrees(lightOffsetAngle));
   }

  ImGui::InputFloat2("rectangle Size (X,Y)", polySize, "%.1f");
  ImGui::Text("Random polygon config: ");
  if (ImGui::SliderInt2("vertex count lower/upper bounds", vertexBounds, vertexMin, vertexMax))
  {
    if (vertexBounds[0] > vertexBounds[1])
      vertexBounds[0] = vertexBounds[1];

    if (vertexBounds[1] < vertexBounds[0])
      vertexBounds[0] = vertexBounds[1];
  }

  if (ImGui::InputInt("Average size", &averageSize))
  {
    if (averageSize < 20)
    {
      averageSize = 20;
    }
  }

  ImGui::End();
}

void userInput()
{
  while (const std::optional event = window.pollEvent())
  {
    ImGui::SFML::ProcessEvent(window, *event);

    if (event->is<sf::Event::Closed>())
    {
      window.close();
    }

    if (const auto* buttonPressed = event->getIf<sf::Event::KeyPressed>())
    {
      switch (buttonPressed->scancode)
      {
      case sf::Keyboard::Scancode::C:
        polygons.clear(); 
        polygons.emplace_back(constructRectangle(center, center));
        break;

      case sf::Keyboard::Scancode::R:
        polygons.emplace_back(constructRandomPolygon(mousePos, vertexBounds[0] + 1, vertexBounds[1] + 1, averageSize));
        break;

      case sf::Keyboard::Scancode::Escape:
        window.close();
        break;

      default:
        break;
      }
    }

    if (const auto* mousePressed = event->getIf<sf::Event::MouseButtonPressed>())
    {
      switch (mousePressed->button)
      {
      case sf::Mouse::Button::Right:
        polygons.emplace_back(constructRectangle((sf::Vector2f)mousePressed->position, { polySize[0], polySize[1] }, sf::Color::Black));
        break;

      default:
        break;
      }
    }

    if (const auto* mouseWheelScrolled = event->getIf<sf::Event::MouseWheelScrolled>())
    {
      switch (mouseWheelScrolled->wheel)
      {
      case sf::Mouse::Wheel::Vertical:
        if (boundedLight)
        {
          lightCenterAngle += mouseWheelScrolled->delta * rotationSpeedScale;
          if (lightCenterAngle > 359.f) lightCenterAngle = 0.f;
          if (lightCenterAngle < 0.f) lightCenterAngle = 359.f;
        }
        break;

      default:
        break;
      }
    }

    if (const auto* mouseMoved = event->getIf<sf::Event::MouseMoved>())
    {
      mousePos = (sf::Vector2f)mouseMoved->position;
    }
  }
}

void render()
{
  window.clear(sf::Color::White);

  light.draw(window);

  for (auto& poly : polygons) window.draw(poly);

  ImGui::SFML::Render(window);

  window.display();
}