#pragma once

#include "../../core/flags.hpp"
#include "../../core/world/actions.hpp"
#include <cassert>
#include <memory>
#include <string>
#include <glm/glm.hpp>
#include <functional>

class Window;
class Camera;
/// Graphics pipeline
namespace render {
class Model;
class LodModel;

enum class Planar {
    Cheap = 1,
    Biplanar = 2,
    Triplanar = 3
};

/// Pass options
struct passOptions {
    /// Apply light properties
    bool pbr = true;
    /// Texture mapping
    Planar planar = Planar::Cheap;
    /// Transform texture UV
    bool stochastic = false;
    /// Active geometry pass
    bool geometry = true;
    /// Blend voxel with mixed materials (requires geometry)
    bool blend = true;
    /// Depth fog
    bool fog = true;
    /// Map planets to sphere
    bool curvature = true;
    /// Keep depth in sphere
    bool curv_depth = true;
    /// Alpha blend
    bool transparency = false;
};

/// Rendering options
struct renderOptions {
    /// Voxel passes
    passOptions voxel;
    /// Display skybox
    bool skybox = true;
    /// Display only wires
    bool wireframe = false;
    /// Texture pack name
    std::string textures = "1024-realistic";
    /// Textures quality
    int textureQuality = 100;
    /// Textures anisotropic mapping
    int textureSharpness = 0;
    /// Depth color
    glm::vec4 clear_color;
    /// Parallel processing frames
    /// Incease FPS but also a bit latency (Vulkan only)
    int inFlightFrames = 2;

    constexpr float getMipmapLodBias() const { return 1 - (textureQuality / 100.f); }
    constexpr int getAnisotropy() const { return textureSharpness >= 1 ? (1 << (textureSharpness - 1)) : 0; }
};

/// Rendering plateform interface
class Renderer {
public:
    virtual ~Renderer() { }

    glm::vec3 LightInvDir = glm::vec3(0.5f, 2, 2);

    /// Start new frame and setup
    virtual void beginFrame() = 0;
    /// Get started world program
    /// (vertex buffer, model matrix, sphereProj, curvature)
    virtual std::function<size_t(render::LodModel *const, glm::mat4, glm::vec4, float)> beginWorldPass(bool solid) = 0;
    /// Get started entity program
    virtual std::function<size_t(render::Model *const, const std::vector<glm::mat4> &)> beginEntityPass() = 0;
    /// Draw line indicator (model, shape, color)
    virtual std::function<size_t(glm::mat4, world::action::Shape, glm::vec4)> beginIndicatorPass() = 0;
    /// Apply postprocessing
    virtual void postProcess() = 0;
    /// Finalise frame
    virtual void endFrame() = 0;
    /// Swap displayed image
    virtual void swapBuffer(Window &) = 0;

    /// Apply camera matrices
    virtual void lookFrom(const Camera &) = 0;

    virtual void setClearColor(glm::vec4) = 0;
    virtual void reloadShaders(const passOptions &) = 0;
    virtual void reloadTextures(const std::string &, float mipMapLOD, int anisotropy) = 0;
    virtual void setFillMode(bool wireframe) = 0;
    virtual void setVSync(bool vSync) = 0;

    virtual void loadUI(Window&) = 0;

    static _FORCE_INLINE_ Renderer *Get() {
        assert(sInstance != nullptr && "Uninitialized renderer");
        return sInstance;
    }
    static void Unload();

protected:
    static Renderer *sInstance;
};
}