#include "NodeNetwork.hpp"

#include "MayaFlux/Transitive/Reflect/EnumReflect.hpp"

namespace MayaFlux::Nodes::Network {

void NodeNetwork::ensure_initialized()
{
    if (!m_initialized) {
        initialize();
        m_initialized = true;
    }
}

std::optional<std::vector<double>> NodeNetwork::get_audio_buffer() const
{
    if (m_output_mode != OutputMode::AUDIO_SINK && m_output_mode != OutputMode::AUDIO_COMPUTE)
        return std::nullopt;

    while (m_audio_buffer_lock.test_and_set(std::memory_order_acquire))
        std::this_thread::yield();

    auto result = m_last_audio_buffer.empty()
        ? std::nullopt
        : std::optional<std::vector<double>> { m_last_audio_buffer };
    m_audio_buffer_lock.clear(std::memory_order_release);

    return result;
}

void NodeNetwork::apply_output_scale()
{
    if (m_output_scale == 1.0)
        return;

    for (auto& s : m_last_audio_buffer)
        s *= m_output_scale;
}

[[nodiscard]] std::unordered_map<std::string, std::string>
NodeNetwork::get_metadata() const
{
    return { { "topology", topology_to_string(m_topology) },
        { "output_mode", output_mode_to_string(m_output_mode) },
        { "node_count", std::to_string(get_node_count()) },
        { "enabled", m_enabled ? "true" : "false" } };
}

void NodeNetwork::map_parameter(const std::string& param_name,
    const std::shared_ptr<Node>& source,
    MappingMode mode)
{
    m_parameter_mappings.push_back({ .param_name = param_name,
        .mode = mode,
        .broadcast_source = source,
        .network_source = nullptr });
}

void NodeNetwork::map_parameter(const std::string& param_name,
    const std::shared_ptr<NodeNetwork>& source_network)
{
    m_parameter_mappings.push_back(
        { .param_name = param_name,
            .mode = MappingMode::ONE_TO_ONE,
            .broadcast_source = nullptr,
            .network_source = source_network });
}

void NodeNetwork::unmap_parameter(const std::string& param_name)
{
    std::erase_if(m_parameter_mappings,
        [&](const auto& m) { return m.param_name == param_name; });
}

bool NodeNetwork::is_processing() const
{
    return m_processing_state.load(std::memory_order_acquire);
}

void NodeNetwork::add_channel_usage(uint32_t channel_id)
{
    if (channel_id < 32) {
        m_channel_mask |= (1U << channel_id);
    }
}

void NodeNetwork::remove_channel_usage(uint32_t channel_id)
{
    if (channel_id < 32) {
        m_channel_mask &= ~(1U << channel_id);
    }
}

bool NodeNetwork::is_registered_on_channel(uint32_t channel_id) const
{
    if (channel_id < 32) {
        return (m_channel_mask & (1U << channel_id)) != 0;
    }
    return false;
}

std::vector<uint32_t> NodeNetwork::get_registered_channels() const
{
    std::vector<uint32_t> channels;
    for (uint32_t i = 0; i < 32; ++i) {
        if (m_channel_mask & (1U << i)) {
            channels.push_back(i);
        }
    }
    return channels;
}

void NodeNetwork::mark_processing(bool processing)
{
    m_processing_state.store(processing, std::memory_order_release);
}

bool NodeNetwork::is_processed_this_cycle() const
{
    return m_processed_this_cycle.load(std::memory_order_acquire);
}

void NodeNetwork::mark_processed(bool processed)
{
    m_processed_this_cycle.store(processed, std::memory_order_release);
}

void NodeNetwork::request_reset_from_channel(uint32_t channel_id)
{
    if (channel_id >= 32)
        return;

    auto channel_bit = static_cast<uint32_t>(1ULL << channel_id);
    uint32_t old_pending = m_pending_reset_mask.fetch_or(channel_bit,
        std::memory_order_acq_rel);
    uint32_t new_pending = old_pending | channel_bit;
    uint32_t active_channels = m_channel_mask.load(std::memory_order_acquire);

    if ((new_pending & active_channels) == active_channels && active_channels != 0) {
        uint32_t expected = new_pending;
        if (m_pending_reset_mask.compare_exchange_strong(expected, 0,
                std::memory_order_acq_rel)) {
            mark_processed(false);
        }
    }
}

std::string NodeNetwork::topology_to_string(Topology topo)
{
    return std::string(MayaFlux::Reflect::enum_to_string(topo));
}

std::string NodeNetwork::output_mode_to_string(OutputMode mode)
{
    return std::string(MayaFlux::Reflect::enum_to_string(mode));
}

} // namespace MayaFlux::Nodes::Network
