#pragma once

#include <atomic>
#include <limits>
#include <memory>
#include <memory_resource>

#include "renderive/renderable/base/Renderable_Base_p.hpp"

class Renderable_Test_Harness : public Renderable_Base {
private:
    class Test_Impl final : public Renderable_Base::Impl {
    public:
        void build_default_prepare_graph(Renderable_Graph_Builder& builder) {
            Renderable_Base::Impl::build_prepare_graph(builder);
        }
        void build_default_paint_graph(Renderable_Graph_Builder& builder) {
            Renderable_Base::Impl::build_paint_graph(builder);
        }
    private:
        void build_prepare_graph(Renderable_Graph_Builder& builder) override {
            static_cast<Renderable_Test_Harness&>(owner())
                .build_prepare_graph_for_test(builder);
        }
        void build_paint_graph(Renderable_Graph_Builder& builder) override {
            static_cast<Renderable_Test_Harness&>(owner())
                .build_paint_graph_for_test(builder);
        }
        void prepare(const Prepare_Render_Context& context) override {
            static_cast<Renderable_Test_Harness&>(owner())
                .prepare_for_test(context);
        }
    };

protected:
    explicit Renderable_Test_Harness(
        Renderable_Configuration configuration = {},
        std::pmr::memory_resource& memory_resource =
            *std::pmr::get_default_resource())
        : Renderable_Base(std::make_unique<Test_Impl>(), configuration,
                          memory_resource) {}
    virtual void prepare_for_test(const Prepare_Render_Context&) {}
    virtual void build_prepare_graph_for_test(
        Renderable_Graph_Builder& builder) {
        test_impl().build_default_prepare_graph(builder);
    }
    virtual void build_paint_graph_for_test(
        Renderable_Graph_Builder& builder) {
        test_impl().build_default_paint_graph(builder);
    }

public:
    [[nodiscard]] Scene_Base& scene_for_test() const {
        return d_func().scene();
    }
    [[nodiscard]] std::pmr::memory_resource& memory_resource_for_test() const {
        return d_func().memory_resource();
    }
    [[nodiscard]] Renderable_Configuration configuration_for_test() const {
        return d_func().configuration.load(std::memory_order_acquire);
    }
    void set_configuration_for_test(Renderable_Configuration configuration) {
        d_func().set_configuration(configuration);
    }
    void invalidate_prepare_for_test() noexcept {
        d_func().invalidate_prepare();
    }
    void invalidate_paint_for_test() noexcept {
        d_func().invalidate_paint();
    }
    void rebuild_render_graph_for_test() {
        d_func().rebuild_render_graph();
    }
    [[nodiscard]] std::shared_ptr<const Renderable_Graph>
    render_graph_for_test() {
        return d_func().render_graph_snapshot();
    }
    [[nodiscard]] bool prepare_cache_valid_for_test() const noexcept {
        const auto configuration = d_func().configuration.load(
            std::memory_order_acquire);
        const auto prepare_revision = d_func().prepare_revision.load(
            std::memory_order_acquire);
        return prepare_revision != std::numeric_limits<std::uint64_t>::max() &&
               configuration.cache_enabled &&
               d_func().prepared_revision.load(std::memory_order_acquire) ==
                   prepare_revision;
    }
    [[nodiscard]] bool paint_cache_valid_for_test() const noexcept {
        if (!prepare_cache_valid_for_test())
            return false;
        const auto paint_revision = d_func().paint_revision.load(
            std::memory_order_acquire);
        return paint_revision != std::numeric_limits<std::uint64_t>::max() &&
               d_func().painted_revision.load(std::memory_order_acquire) ==
                   paint_revision &&
               d_func().painted_prepare_revision.load(
                   std::memory_order_acquire) ==
                   d_func().prepared_revision.load(std::memory_order_acquire);
    }

private:
    Test_Impl& test_impl() noexcept {
        return d_func<Test_Impl>();
    }
};
