#ifndef FRAME_DRAWER_HPP_
#define FRAME_DRAWER_HPP_

#include <PipelineStage/PipelineStage.hpp>
#include <Vk/CommandBuffer/CommandBuffer.hpp>
#include <Vk/DescriptorSet/DescriptorSet.hpp>
#include <Vk/Buffer/Buffer.hpp>
#include <GraphicsContext/GraphicsContext.hpp>
#include <PipelineStage/DrawStage/DrawStage.hpp>
#include <PipelineStage/LightingStage/LightingStage.hpp>
#include <PipelineStage/DrawStage/CombineStage/CombineStage.hpp>
#include <PipelineStage/DrawStage/ParticlesStage/ParticlesStage.hpp>
#include <PipelineStage/DrawStage/ModelStage/ModelStage.hpp>
#include <PipelineStage/DrawStage/TerrainStage/TerrainStage.hpp>
#include <Vk/Framebuffer/Framebuffer.hpp>
#include <Vk/QueryPool/QueryPool.hpp>
#include <memory>
#include <vector>

class WorldDisplay;

/**
 * @brief Data associated with a frame of the game.
 */
class FrameDrawer
{
private:
    static constexpr float fogLen = 16.0f;

    struct FrameData
    {
        alignas(16) glm::mat4 proj;
        alignas(16) glm::mat4 view;
        alignas(16) glm::mat4 antiVP;
        alignas(16) glm::mat4 skybox;
        alignas(16) glm::mat4 antiSkybox;
        alignas(16) glm::mat4 cloudTransform;
        alignas(16) glm::vec3 ambientColor;
        alignas(16) glm::uvec3 cameraVoxel;
        alignas(16) glm::vec3 cameraOffset;
        alignas(8) glm::uvec2 screenTiles;
        alignas(8) glm::uvec2 screenSize;
        float solarAngle;
        float fogStartZ;
        float playerZ;
        float farZ;
        float cloudCover;
        uint32_t ticks;
        uint32_t flags;         // GLSL_FRAMEDATA_*
        float debugSpecular;
        float debugRefractValue;
    };

    std::shared_ptr<Vk::Buffer> dataBuffer;
    std::shared_ptr<Vk::DescriptorSet> dataDescSet;
    std::shared_ptr<Vk::CommandPool> cmdpool;
    std::vector<PipelineStage*> stages;

    void drawWorld(GraphicsContext &context);
    void drawEntities(GraphicsContext &context);

public:
    WorldDisplay &worldDisplay;
    std::unique_ptr<TerrainStage> terrainStage;
    std::unique_ptr<ModelStage> modelStage;
    std::unique_ptr<ParticlesStage> particlesStage;
    std::unique_ptr<LightingStage> lightingStage;
    std::unique_ptr<CombineStage> combineStage;

    glm::mat4 projMatrix;
    glm::mat4 viewMatrix;

    FrameDrawer(
        WorldDisplay &worldDisplay,
        std::shared_ptr<Vk::DescriptorPool> frameDataDescriptorPool,
        std::shared_ptr<Vk::DescriptorPool> skyboxDescriptorPool
    );

    /**
     * @brief Draw the frame.
     */
    void draw(GraphicsContext &context);

    /**
     * @brief Create a secondary command buffer.
     */
    std::shared_ptr<Vk::CommandBuffer> createStageCommandBuffer();

    /**
     * @brief Set the model matrix, across all applicable stages.
     */
    void setModelMatrix(const glm::mat4 &modelMatrix);

    /**
     * @brief Write debug information about the stages to the stream.
     */
    void writeStageDebugLines(std::ostream &os);
};

#endif