#pragma once
#include <Renderer.h>
#include <Volume.h>
#include <array.hpp>
#include <random>

class ArrayRenderer : public RendererInterface<ArrayRenderer> {
public:
    static inline const std::string class_identifier = "Array";
    static std::unique_ptr<Renderer> create_func(int width, int height, int maxSamples, int samplesPerFrame, const std::vector<CUDA::PointLight>& lightsVector) {
        return std::make_unique<ArrayRenderer>(width, height, maxSamples, samplesPerFrame, lightsVector);
    }

    ArrayRenderer(int width, int height, int maxSamples, int samplesPerFrame, std::vector<CUDA::PointLight> lightsVector);

    ~ArrayRenderer() override {
        delete[] offsets;
        delete[] arraySizes;
        cudaStreamDestroy(streamKernel);

        CUDA::free(offsetsDev);
        CUDA::free(arraySizesDev);
        CUDA::free(oldArray);
        CUDA::free(newArray);
        CUDA::free(lights);
    }

    std::string getType() override {
        return class_identifier;
    }

    void setParameters(RenderParameters params) override {
        if (params.lightSamples >= 0) {
            temp_multiscatter = params.multiscatter;
            temp_full = params.fullIllumination;
            temp_bootstrapped = params.bootstrap;
            temp_numSamples = params.lightSamples;
            temp_debug = params.debug;
            name = std::move(params.name);
        }else {
            temp_multiscatter = false;
            temp_full = false;
            temp_bootstrapped = true;
            temp_numSamples = 500;
            temp_debug = false;
            name = "";
        }

        submit();
    }

    void render(cudaSurfaceObject_t surface, CUDA::Camera camera, CUDA::Volume volume,
                CUDA::OptimizationParameters optParams) override;

    void renderSimple(cudaSurfaceObject_t surface, CUDA::Camera camera, CUDA::Volume volume,
                      CUDA::OptimizationParameters optParams) override;

    void drawGui(float deltaTime, float frametime) override;

    void submit() override;

    void resetRendering(int width, int height) override;

    std::string getName() override {
        if (debug) {
            return "importance";
        }
        if (name.empty()) {
            return "array_acc_" + std::to_string(maxSamples) + "_" + std::to_string(numSamples);
        } else {
            return name;
        }
    }

private:
        cudaStream_t streamKernel;

    std::random_device dev;
    CUDA::PointLight* lights;
    int numLights;

    bool full;
    bool temp_full;
    bool bootstrapped;
    bool temp_bootstrapped;
    int numSamples;
    int temp_numSamples;
    bool multiscatter;
    bool temp_multiscatter;

    bool debug = false;
    bool temp_debug = false;

    float4* newArray = nullptr;
    float4* oldArray = nullptr;
    float2* tempArray = nullptr;

    CUDA::ArrayValuesSmall* arraySizesDev = nullptr;
    int3* offsetsDev = nullptr;

    int3* offsets = nullptr;
    CUDA::ArrayValuesSmall* arraySizes = nullptr;
    int maxSize = -1;

    std::string name;

    bool updated = true;

    float temp_pos[3] = {0.f, 0.f, 0.f};
    float3 pos = make_float3(0);
    float temp_intensity = 0;
    float intensity = 0;
};
