#include "Datoviz_Frame_Executor.h"

#include "Datoviz_Backend.h"
#include "Frame_Target_Pool.h"
#include "Ordered_Artifact_Transaction.h"

#include <datoviz/scene/frame_plan.h>

#include <cstdio>

namespace renderive::detail {
namespace {

DvzFramePlanEmitConfig emit_configuration(const Render_3D_State& state) {
    DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config();
    configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL;
    configuration.external_color_target = true;
    configuration.color_target_id = Datoviz_Color_Target_Id;
    configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM;
    configuration.target_width = state.viewport.width;
    configuration.target_height = state.viewport.height;
    configuration.device_scale_x = state.device_scale_x;
    configuration.device_scale_y = state.device_scale_y;
    configuration.render_scale = state.render_scale;
    configuration.user_scale = state.user_scale;
    configuration.clear_color[0] = state.clear_color.red;
    configuration.clear_color[1] = state.clear_color.green;
    configuration.clear_color[2] = state.clear_color.blue;
    configuration.clear_color[3] = state.clear_color.alpha;
    return configuration;
}

} // namespace

struct Datoviz_Transaction_Api {
    Datoviz_Backend& backend;
    Frame_Target_Pool& targets;
    Frame_Target& target;
    DvzFramePlanEmitConfig configuration;

    [[nodiscard]] DvzSceneFrameArtifact* emit() { return backend.emit(configuration); }

    [[nodiscard]] static const DvzDrp2CommandStream* stream(DvzSceneFrameArtifact& artifact) {
        return dvz_scene_frame_artifact_stream(&artifact);
    }

    [[nodiscard]] bool attach() {
        const DvzStreamFrame frame = target.stream_frame();
        const bool attached = backend.attach(frame);
        if (attached)
            targets.external_target_replaced();
        return attached;
    }

    [[nodiscard]] bool execute(const DvzDrp2CommandStream& stream) {
        return backend.execute(stream);
    }

    static void destroy(DvzSceneFrameArtifact& artifact) {
        dvz_scene_frame_artifact_destroy(&artifact);
    }
};

bool Datoviz_Frame_Executor::execute(
    Datoviz_Backend& backend, Frame_Target_Pool& targets, Frame_Target& target,
    const Render_3D_Frame_Data& frame) {
    if (!target.begin()) {
        std::fputs("Render_3D: failed to begin the owned command buffer\n", stderr);
        return false;
    }

    Datoviz_Transaction_Api api{
        backend, targets, target, emit_configuration(frame.state)};
    const auto transaction = execute_ordered_artifact_transaction(api);
    if (!static_cast<bool>(transaction)) {
        std::fprintf(
            stderr,
            "Render_3D: Datoviz transaction failed (emit=%d stream=%d attach=%d execute=%d execute_ok=%d)\n",
            transaction.artifact_emitted, transaction.stream_available,
            transaction.target_attached, transaction.stream_executed,
            transaction.execution_succeeded);
        target.abort();
        return false;
    }
    if (!target.submit_and_wait()) {
        std::fputs("Render_3D: failed to submit or complete the owned command buffer\n", stderr);
        return false;
    }
    return true;
}

} // namespace renderive::detail
