#pragma once
#include <TransferFunction.hpp>
#include <stb_image_write.h>
#include <imgui_helper.h>
#include <map>
#include <memory>
#include <vector>
#include <functional>

struct RenderParameters {
    bool debug;
    bool multiscatter;
    bool fullIllumination;
    bool jitter;
    bool bootstrap;
    int lightSamples;
    bool cache;
    bool raymarchOld;
    float stepSizeView;
    float stepSizeLight;
    std::string name;
};


class Renderer {
public:
    void initRenderer(int width, int height, int maxSamples, int samplesPerFrame);

    virtual ~Renderer();

    virtual std::string getType() = 0;

    virtual void setParameters(RenderParameters params) = 0;

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

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

    virtual void resetRendering(int width, int height) {
        this->width = width;
        this->height = height;
        currSamples = 0;
        progressive = false;
        CUDA::free(deviceData);
        CUDA::malloc(deviceData, width * height);
    }

    virtual void drawGui(float deltaTime, float frametime);

    virtual void submit() = 0;

    void saveImage() const;
    void saveImage(std::string filepath) const;
    void saveImage(std::string filepath, uchar4* data, int width, int height) const;
    float4* getImageData() {
        return deviceData;
    }

    virtual void setVolumeSize(int3 size) {}

    float getSamples() {
        return (float) maxSamples / (float) samplesPerFrame;
    }

    virtual std::string getName() {
        return "";
    }

    using create_func =
            std::function<std::unique_ptr<Renderer>(int, int, int, int, const std::vector<CUDA::PointLight>&)>;

    static std::map<std::string, create_func>& get_registry() {
        static std::map<std::string, create_func> registry;
        return registry;
    }

    bool showSDF = true;

protected:
    int height = 0;
    int width = 0;

    int currSamples = 0;
    int maxSamples = 0;
    bool progressive = false;

    int frameCounter = 0;
    int sizeAvgFrames = 20;
    float frameTimes[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    int samplesPerFrame = 1;
    int temp_samplesPerFrame = 1;

    float4* deviceData = nullptr;
};

template<class T>
class RendererInterface : public Renderer {
public:
    ~RendererInterface() override {
        if (!registered_)
            std::cout << "bad" << std::endl;
    }

    static bool register_type() {
        auto& registry = get_registry();
        registry[T::class_identifier] = T::create_func;
        return true;
    }

    static const bool registered_;

protected:
    virtual void DoNotInheritFromRendererDirectlyButFromRendererInterfaceInstead() final {}
};
template<class T>
const bool RendererInterface<T>::registered_ = RendererInterface<T>::register_type();
