#pragma once
#include <chrono>
#include <thread>
#include <iostream>
#include <memory>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <imgui_helper.h>
#include <stb_image_write.h>

#include <interop.hpp>
#include <Renderer.h>
#include <Denoiser.h>
#include <Volume.h>
#include <Camera.hpp>


class Window {
public:
    explicit Window(std::unique_ptr<Renderer> renderer, std::unique_ptr<Volume> volume,
                    std::unique_ptr<Volume> importanceSDF, std::unique_ptr<Camera> camera,
                    std::unique_ptr<TransferFunction> transfer_function, std::vector<CUDA::PointLight> lights,
                    const CUDA::OptimizationParameters& optParams = {1, 70, 30, 1, 0.3, 1, false, false, false, 20, 1},
                    int width = 800, int height = 600, float4 volumeProperties = make_float4(30, 0.3, 0.7, 0));

    ~Window();

    void render();

private:
    void renderOpengl(unsigned int texture) const;

    void initShader();

    void initImGui() const;

    void initOpenGL();

    void updateTexture();

    void setCallbacks() const;

    void framebufferSizeCallback(int width, int height);

    void mouseCursorCallback(double xposIn, double yposIn);

    void mouseButtonCallback(int button, int action, int mods);

    void scrollCallback(double xoffset, double yoffset) const;

    void minimizeCallback(int iconified);

    void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);

    void updateCamera() const;

    void WindowGUI();

    void submit();

    void applyOptParams(CUDA::OptimizationParameters optParams) {
        temp_p = optParams.p;
        temp_q = optParams.q;
        temp_r = optParams.r;
        temp_lambda = optParams.lambda;
        temp_thresholdMin = optParams.thresholdMin;
        temp_thresholdMax = optParams.thresholdMax;
        temp_optimizeView = optParams.view;
        temp_optimizeLight = optParams.light;
        temp_phong = optParams.phong;
        temp_shiny = (int) optParams.shiny;
        temp_scale = optParams.scale;
    }

    bool sleep = false;

    int width_ = 800;
    int height_ = 600;

    float lastX = static_cast<float>(width_) / 2.0f;
    float lastY = static_cast<float>(height_) / 2.0f;
    bool firstMouse = true;
    bool lbutton_down = false;

    float deltaTime = 0.0f;
    float lastFrame = 0.0f;

    GLFWwindow* window;
    std::unique_ptr<Renderer> renderer;

    unsigned int texture_id = 0;
    unsigned int texture_id_denoised = 0;
    unsigned int shaderID = 0;
    unsigned int VAO = 0;
    unsigned int VBO = 0;

    Interop interop;
    Interop interopDenoised;

    cudaEvent_t start = nullptr;
    cudaEvent_t stop = nullptr;

    float frametime = 0;

    unsigned char movementDir = 0;
    float speed = 1.f;

    std::unique_ptr<Volume> volume = nullptr;
    std::unique_ptr<Camera> camera = nullptr;
    std::unique_ptr<TransferFunction> transferFunction = nullptr;
#ifdef ENABLE_OPTIX
    std::unique_ptr<Denoiser> denoiser = nullptr;
#endif
    std::vector<CUDA::PointLight> lights;
    std::unique_ptr<Volume> importanceSDF = nullptr;

    float density = 0;
    float temp_density = 0;
    float absorption = 0;
    float temp_absorption = 0;
    float scattering = 0;
    float temp_scattering = 0;
    float g = 0;
    float temp_g = 0;

    float thresholdMin = 0;
    float temp_thresholdMin = 0;
    float thresholdMax = 0;
    float temp_thresholdMax = 0;

    float p = 0;
    float temp_p = 0;
    float q = 0;
    float temp_q = 0;
    float r = 0;
    float temp_r = 0;
    float lambda = 0;
    float temp_lambda = 0;

    bool optimizeView = false;
    bool temp_optimizeView = false;
    bool optimizeLight = false;
    bool temp_optimizeLight = false;

    bool phong = false;
    bool temp_phong = false;
    float scale = 0;
    float temp_scale = 0;
    float shiny = 0;
    int temp_shiny = 0;

    bool sdf = false;
    bool temp_sdf = false;
    bool position = false;
    bool temp_position = false;

    bool denoise = false;
    bool trilinear = true;
    bool temp_trilinear = true;

    CUDA::Volume volumeData = {};
    CUDA::OptimizationParameters optParams = {};

    cudaTextureObject_t volumeDataTex = 0;
    cudaTextureObject_t importanceSDFTex = 0;

    CUDA::TF transferData = {};

    int currItem = 0;
};
