#pragma once
#include <array>
#include <atomic>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include "concept/Real_Time_Data.hpp"
#include "renderive/renderable/concept/Renderable.hpp"
#include "renderive/renderable/base/Renderable_Base_p.hpp"
#include "renderive/scene/base/Scene_Base.hpp"
#include "renderive/scene/base/Render_Contexts.hpp"
template <class... Data>
inline constexpr bool Unique_Real_Time_Data_Types_Value = true;
template <class Data, class... Rest>
inline constexpr bool Unique_Real_Time_Data_Types_Value<Data, Rest...> = (!std::same_as<Data, Rest> && ...) && Unique_Real_Time_Data_Types_Value<Rest...>;
template <class... Data>
concept Unique_Real_Time_Data_Types = Unique_Real_Time_Data_Types_Value<Data...>;
template <Real_Time_Data... Data>
requires Unique_Real_Time_Data_Types<Data...>
struct With_Real_Time_Data {
    explicit With_Real_Time_Data(std::shared_ptr<Data>... data) : data(std::move(data)...) {
        if (!std::apply([](const auto&... source) {
            return (static_cast<bool>(source) && ...);
        }, this->data)) {
            throw std::invalid_argument("real-time data source is null");
        }
    }
    std::tuple<std::shared_ptr<Data>...> data;
};
template <class... Data>
With_Real_Time_Data(std::shared_ptr<Data>...) -> With_Real_Time_Data<Data...>;
struct Real_Time_Data_Attachment_State {
    std::size_t source_count{};
    std::uint64_t revision_sum{};
    std::uint64_t total_update_count{};
};
enum class Real_Time_Data_Discard_Mode {
    retain_all,
    retain_frame_interval
};
template <Renderable Renderable_Type, Real_Time_Data... Data>
requires Unique_Real_Time_Data_Types<Data...>
class Attach_Real_Time_Data : public Renderable_Type {
public:
    template <class... Args>
    explicit Attach_Real_Time_Data(With_Real_Time_Data<Data...> attachment, Args&&... args)
        : Renderable_Type(std::forward<Args>(args)...), data_(std::move(attachment.data)) {
        bind_data(std::index_sequence_for<Data...>{});
        this->d_func().add_prepare_action(
            [this] { static_cast<void>(discard_real_time_data()); });
    }
    template <class Data_Type>
    requires ((std::same_as<Data_Type, Data> || ...))
    Data_Type& real_time_data() {
        return *std::get<std::shared_ptr<Data_Type>>(data_);
    }
    template <class Data_Type>
    requires ((std::same_as<Data_Type, Data> || ...))
    const Data_Type& real_time_data() const {
        return *std::get<std::shared_ptr<Data_Type>>(data_);
    }
    std::size_t discard_real_time_data() {
        if (real_time_data_discard_mode.load(std::memory_order_acquire) != Real_Time_Data_Discard_Mode::retain_frame_interval) {
            return 0;
        }
        const std::uint64_t interval_ns = Frame_Control_Strategy_Base::frequency_interval_ns(this->d_func().scene().frame_control_strategy().frequency_hz());
        if (interval_ns == 0) {
            return 0;
        }
        std::size_t discarded{};
        std::apply([interval_ns, &discarded](const auto&... data) {
            ([interval_ns, &discarded, &data] {
                const auto state = data->update_state();
                const std::uint64_t threshold_ns = state.update_time_ns > interval_ns ? state.update_time_ns - interval_ns : 0;
                discarded += data->discard_before_time_ns(threshold_ns);
            }(), ...);
        }, data_);
        return discarded;
    }
    Real_Time_Data_Attachment_State real_time_data_attachment_state() const {
        Real_Time_Data_Attachment_State state{sizeof...(Data), 0, 0};
        std::apply([&state](const auto&... data) {
            ([&state, &data] {
                const auto update = data->update_state();
                state.revision_sum += update.revision;
                state.total_update_count += update.total_update_count;
            }(), ...);
        }, data_);
        return state;
    }
    std::atomic<Real_Time_Data_Discard_Mode> real_time_data_discard_mode{Real_Time_Data_Discard_Mode::retain_all};
private:
    template <std::size_t... Index>
    void bind_data(std::index_sequence<Index...>) {
        (bind_data<Index>(*std::get<Index>(data_)), ...);
    }
    template <std::size_t Index, class Data_Type>
    void bind_data(Data_Type& data) {
        render_bindings_[Index].emplace(
            this->d_func().bind_real_time_data(data));
        if constexpr (requires {
            data.bind(*this);
        }) {
            using Binding = decltype(data.bind(*this));
            if constexpr (std::is_void_v<Binding>) {
                data.bind(*this);
            } else {
                static_assert(std::same_as<std::remove_cvref_t<Binding>, std::shared_ptr<void>>);
                bindings_[Index] = data.bind(*this);
            }
        }
    }
    static_assert(sizeof...(Data) > 0);
    static_assert((std::is_class_v<Data> && ...));
    std::tuple<std::shared_ptr<Data>...> data_;
    std::array<std::optional<Real_Time_Data_Binding>, sizeof...(Data)> render_bindings_;
    std::array<std::shared_ptr<void>, sizeof...(Data)> bindings_;
};
