// Copyright (c) 2026 Ourpad Network
// See LICENSE file in the project root for full license text.

#include "mixer/MixerEngine.hpp"
#include "mixer/MixerChannelStrip.hpp"
#include "mixer/MixerBus.hpp"
#include <algorithm>
#include <cmath>

using namespace mixer;

std::atomic<int64_t> MixerEngine::nextBusId_{1};

MixerEngine::MixerEngine()
{
    // Create the master bus by default
    int64_t masterId = nextBusId_++;
    auto master = std::make_unique<MixerBus>(masterId, MixerBusType::Master, "Master", 2);
    masterBus_ = master.get();
    buses_[masterId] = std::move(master);
}

MixerEngine::~MixerEngine()
{
    Deactivate();
}

void MixerEngine::setSampleRate(uint32_t sampleRate)
{
    sampleRate_ = sampleRate;
}

void MixerEngine::setMaxBufferSize(size_t frames)
{
    maxBufferSize_ = frames;
}

// --- Channel Management ---

MixerChannelStrip* MixerEngine::addChannel(int physicalInputIndex)
{
    auto channel = std::make_unique<MixerChannelStrip>(physicalInputIndex);
    channel->setSampleRate(sampleRate_);
    channel->setMaxBufferSize(maxBufferSize_);
    channel->setLabel("Channel " + std::to_string(physicalInputIndex + 1));

    auto* ptr = channel.get();
    channels_.push_back(std::move(channel));

    // Create default route: this channel → master bus at unity
    MixerRouteEntry route;
    route.sourceType = MixerRouteEntry::SourceChannel;
    route.sourceId = ptr->instanceId();
    route.targetBusId = masterBus_->id();
    route.level = 0.0f; // unity
    routes_.push_back(route);

    return ptr;
}

void MixerEngine::removeChannel(int channelIndex)
{
    if (channelIndex < 0 || channelIndex >= (int)channels_.size()) return;

    auto* channel = channels_[channelIndex].get();
    int64_t instanceId = channel->instanceId();

    // Remove all routes referencing this channel
    routes_.erase(
        std::remove_if(routes_.begin(), routes_.end(),
            [instanceId](const MixerRouteEntry& r) {
                return r.sourceType == MixerRouteEntry::SourceChannel &&
                       r.sourceId == instanceId;
            }),
        routes_.end()
    );

    channel->Unprepare();
    channels_.erase(channels_.begin() + channelIndex);
}

MixerChannelStrip* MixerEngine::getChannel(int channelIndex)
{
    if (channelIndex >= 0 && channelIndex < (int)channels_.size())
        return channels_[channelIndex].get();
    return nullptr;
}

const MixerChannelStrip* MixerEngine::getChannel(int channelIndex) const
{
    if (channelIndex >= 0 && channelIndex < (int)channels_.size())
        return channels_[channelIndex].get();
    return nullptr;
}

// --- Bus Management ---

int64_t MixerEngine::addBus(MixerBusType type, const std::string& name, int channels)
{
    int64_t id = nextBusId_++;
    auto bus = std::make_unique<MixerBus>(id, type, name, channels);
    bus->allocateBuffers(maxBufferSize_);
    buses_[id] = std::move(bus);
    return id;
}

void MixerEngine::removeBus(int64_t busId)
{
    if (busId == masterBus_->id()) return; // Can't remove master

    // Remove all routes targeting this bus
    routes_.erase(
        std::remove_if(routes_.begin(), routes_.end(),
            [busId](const MixerRouteEntry& r) {
                return r.targetBusId == busId;
            }),
        routes_.end()
    );

    buses_.erase(busId);
}

MixerBus* MixerEngine::getBus(int64_t busId)
{
    auto it = buses_.find(busId);
    return (it != buses_.end()) ? it->second.get() : nullptr;
}

const MixerBus* MixerEngine::getBus(int64_t busId) const
{
    auto it = buses_.find(busId);
    return (it != buses_.end()) ? it->second.get() : nullptr;
}

std::vector<int64_t> MixerEngine::busIds() const
{
    std::vector<int64_t> ids;
    ids.reserve(buses_.size());
    for (const auto& [id, _] : buses_) {
        ids.push_back(id);
    }
    return ids;
}

// --- Routing ---

void MixerEngine::routeChannelToBus(int channelIndex, int64_t busId, float levelDb)
{
    if (channelIndex < 0 || channelIndex >= (int)channels_.size()) return;
    if (!getBus(busId)) return;

    auto* channel = channels_[channelIndex].get();

    // Check if route already exists — update level
    for (auto& route : routes_) {
        if (route.sourceType == MixerRouteEntry::SourceChannel &&
            route.sourceId == channel->instanceId() &&
            route.targetBusId == busId) {
            route.level = levelDb;
            return;
        }
    }

    // Add new route
    MixerRouteEntry route;
    route.sourceType = MixerRouteEntry::SourceChannel;
    route.sourceId = channel->instanceId();
    route.targetBusId = busId;
    route.level = levelDb;
    routes_.push_back(route);
}

void MixerEngine::routeBusToBus(int64_t sourceBusId, int64_t targetBusId, float levelDb)
{
    if (!getBus(sourceBusId) || !getBus(targetBusId)) return;

    for (auto& route : routes_) {
        if (route.sourceType == MixerRouteEntry::SourceBus &&
            route.sourceId == sourceBusId &&
            route.targetBusId == targetBusId) {
            route.level = levelDb;
            return;
        }
    }

    MixerRouteEntry route;
    route.sourceType = MixerRouteEntry::SourceBus;
    route.sourceId = sourceBusId;
    route.targetBusId = targetBusId;
    route.level = levelDb;
    routes_.push_back(route);
}

void MixerEngine::removeRoute(int64_t sourceId, int64_t targetBusId)
{
    routes_.erase(
        std::remove_if(routes_.begin(), routes_.end(),
            [sourceId, targetBusId](const MixerRouteEntry& r) {
                return r.sourceId == sourceId && r.targetBusId == targetBusId;
            }),
        routes_.end()
    );
}

void MixerEngine::clearRoutes()
{
    routes_.clear();
}

// --- Lifecycle ---

void MixerEngine::Prepare()
{
    // Allocate bus buffers
    for (auto& [_, bus] : buses_) {
        bus->allocateBuffers(maxBufferSize_);
    }

    // Allocate per-channel output buffers (for routing accumulation)
    channelOutputBuffers_.resize(std::max((size_t)1, channels_.size()));
    for (auto& buf : channelOutputBuffers_) {
        buf.resize(maxBufferSize_ * 2, 0.0f); // stereo output per channel
    }

    // Configure each channel
    for (auto& channel : channels_) {
        channel->setSampleRate(sampleRate_);
        channel->setMaxBufferSize(maxBufferSize_);
        channel->allocateBuffers();
    }
}

void MixerEngine::Activate()
{
    for (auto& channel : channels_) {
        channel->Activate();
    }
}

void MixerEngine::Deactivate()
{
    for (auto& channel : channels_) {
        channel->Deactivate();
    }
}

// --- Solo ---

bool MixerEngine::anySoloActive() const
{
    for (const auto& channel : channels_) {
        if (channel->solo()) return true;
    }
    return false;
}

// --- Audio Processing ---

std::vector<MixerRouteEntry*> MixerEngine::findRoutesForSource(int64_t sourceId)
{
    std::vector<MixerRouteEntry*> result;
    for (auto& route : routes_) {
        if (route.sourceId == sourceId) {
            result.push_back(&route);
        }
    }
    return result;
}

void MixerEngine::routeChannelOutput(
    MixerChannelStrip* channel,
    float** channelOutput,
    uint32_t frames)
{
    bool soloActive = anySoloActive();

    // Find all routes for this channel
    int64_t channelId = channel->instanceId();
    auto channelRoutes = findRoutesForSource(channelId);

    for (auto* route : channelRoutes) {
        MixerBus* targetBus = getBus(route->targetBusId);
        if (!targetBus) continue;

        float levelLinear = std::pow(10.0f, route->level / 20.0f);

        targetBus->accumulate(
            (const float* const*)channelOutput,
            frames,
            levelLinear,
            2 // channelOutput is always stereo
        );
    }

    // Process aux sends
    size_t numAuxSends = channel->auxSendCount();
    for (size_t auxIdx = 0; auxIdx < numAuxSends; ++auxIdx) {
        float effectiveLevel = channel->effectiveAuxLevel(auxIdx, soloActive);
        if (effectiveLevel < -90.0f) continue;

        const auto& sendConfig = channel->auxSend(auxIdx);
        MixerBus* auxBus = getBus(sendConfig.targetBusId);
        if (!auxBus) continue;

        float sendGain = std::pow(10.0f, effectiveLevel / 20.0f);

        if (sendConfig.preFader) {
            // Pre-fader: use the pre-FX buffer
            const float* preFx0 = channel->preFxBuffer(0);
            const float* preFx1 = channel->preFxBuffer(1);
            if (preFx0) {
                const float* preFx[2] = { preFx0, preFx1 };
                auxBus->accumulate(preFx, frames, sendGain, 2);
            }
        } else {
            // Post-fader: use the same output that goes to buses
            auxBus->accumulate(
                (const float* const*)channelOutput,
                frames,
                sendGain,
                2
            );
        }
    }
}

void MixerEngine::processBusRouting(uint32_t frames)
{
    for (auto& route : routes_) {
        if (route.sourceType != MixerRouteEntry::SourceBus) continue;

        MixerBus* sourceBus = getBus(route.sourceId);
        MixerBus* targetBus = getBus(route.targetBusId);
        if (!sourceBus || !targetBus) continue;

        int nChannels = sourceBus->channelCount();
        std::vector<const float*> srcPtrs(nChannels);
        for (int ch = 0; ch < nChannels; ++ch) {
            srcPtrs[ch] = sourceBus->buffer(ch);
        }

        float levelLinear = std::pow(10.0f, route.level / 20.0f);
        targetBus->accumulate(srcPtrs.data(), frames, levelLinear, nChannels);
    }
}

void MixerEngine::process(
    float** deviceInputs,
    uint32_t inputChannels,
    float** deviceOutputs,
    uint32_t outputChannels,
    uint32_t frames)
{
    // Clamp
    frames = std::min(frames, (uint32_t)maxBufferSize_);

    // Step 1: Clear all bus buffers
    for (auto& [_, bus] : buses_) {
        bus->clear();
    }

    // Step 2: Process each channel
    size_t numChannels = channels_.size();

    // Determine channel pairing mode
    bool stereoPairing = (inputChannels <= 2);

    for (size_t ch = 0; ch < numChannels; ++ch) {
        auto* channel = channels_[ch].get();

        // Build input buffer pointers for this channel
        float* channelInputs[2] = { nullptr, nullptr };
        if (stereoPairing) {
            uint32_t baseInput = (uint32_t)(ch * 2);
            if (baseInput < inputChannels) {
                channelInputs[0] = deviceInputs[baseInput];
                if (baseInput + 1 < inputChannels) {
                    channelInputs[1] = deviceInputs[baseInput + 1];
                }
            }
        } else {
            if (ch < inputChannels) {
                channelInputs[0] = deviceInputs[ch];
            }
        }

        // Build output buffer (stereo, from our per-channel scratch buffers)
        float* channelOutputs[2] = { nullptr, nullptr };
        if (ch < channelOutputBuffers_.size()) {
            channelOutputs[0] = channelOutputBuffers_[ch].data();
            channelOutputs[1] = channelOutputBuffers_[ch].data() + maxBufferSize_;
        }

        // Determine the actual number of input channels for this strip
        size_t stripInputChannels = 0;
        if (channelInputs[0] != nullptr) stripInputChannels = 1;
        if (channelInputs[1] != nullptr) stripInputChannels = 2;

        // Process the channel strip
        channel->process(
            (const float* const*)channelInputs,
            stripInputChannels,
            channelOutputs,
            2,
            frames
        );

        // Route channel output to buses
        routeChannelOutput(channel, channelOutputs, frames);
    }

    // Step 3: Process bus-to-bus routing
    processBusRouting(frames);

    // Step 4: Process each bus (apply volume, compute VU)
    for (auto& [_, bus] : buses_) {
        bus->process(frames);
    }

    // Step 5: Write buses to physical outputs according to output routing
    if (outputRoutes_.empty()) {
        // Legacy fallback: write master bus to device outputs 1:1
        if (masterBus_) {
            for (uint32_t outCh = 0; outCh < outputChannels; ++outCh) {
                if (deviceOutputs[outCh] == nullptr) continue;

                const float* src = masterBus_->buffer(outCh);
                if (src) {
                    std::copy(src, src + frames, deviceOutputs[outCh]);
                } else if (outCh == 1) {
                    const float* srcL = masterBus_->buffer(0);
                    if (srcL) {
                        std::copy(srcL, srcL + frames, deviceOutputs[outCh]);
                    }
                }
            }
        }
    } else {
        // Use configured output routes
        for (const auto& route : outputRoutes_) {
            MixerBus* sourceBus = getBus(route.sourceBusId);
            if (!sourceBus) continue;

            for (int ch = 0; ch < route.channels; ++ch) {
                uint32_t targetCh = (uint32_t)(route.targetStartChannel + ch);
                if (targetCh >= outputChannels) break;
                if (deviceOutputs[targetCh] == nullptr) continue;

                const float* src = sourceBus->buffer(route.sourceStartChannel + ch);
                if (src) {
                    std::copy(src, src + frames, deviceOutputs[targetCh]);
                }
            }
        }
    }
}

// --- State Serialization ---

MixerSnapshot MixerEngine::captureSnapshot() const
{
    MixerSnapshot snap;

    for (const auto& channel : channels_) {
        MixerSnapshot::ChannelState cs;
        cs.channelIndex = channel->channelIndex();
        cs.volume = channel->volume();
        cs.pan = channel->pan();
        cs.mute = channel->mute();
        cs.solo = channel->solo();
        cs.channelType = channel->channelType();
        cs.label = channel->label();
        cs.hpEnabled = channel->hpEnabled();
        cs.hpFrequency = channel->hpFrequency();

        for (size_t i = 0; i < channel->auxSendCount(); ++i) {
            cs.auxSendLevels.push_back(channel->auxSend(i).level);
        }

        snap.channels.push_back(cs);
    }

    for (const auto& [id, bus] : buses_) {
        MixerSnapshot::BusState bs;
        bs.id = id;
        bs.name = bus->name();
        bs.type = bus->type();
        bs.volume = bus->volume();
        bs.mute = bus->mute();
        snap.buses.push_back(bs);
    }

    snap.routes = routes_;
    return snap;
}

// --- Output Routing ---

void MixerEngine::setOutputRoutes(const std::vector<MixerOutputRoute>& routes)
{
    outputRoutes_ = routes;
}

void MixerEngine::addOutputRoute(int64_t busId, int sourceStartChannel, int targetStartChannel, int channels)
{
    // Remove any existing route that conflicts with the target
    outputRoutes_.erase(
        std::remove_if(outputRoutes_.begin(), outputRoutes_.end(),
            [busId, targetStartChannel](const MixerOutputRoute& r) {
                return r.sourceBusId == busId &&
                       r.targetStartChannel == targetStartChannel;
            }),
        outputRoutes_.end()
    );

    MixerOutputRoute route;
    route.sourceBusId = busId;
    route.sourceStartChannel = sourceStartChannel;
    route.targetStartChannel = targetStartChannel;
    route.channels = channels;
    outputRoutes_.push_back(route);
}

void MixerEngine::removeOutputRoutes(int64_t busId)
{
    outputRoutes_.erase(
        std::remove_if(outputRoutes_.begin(), outputRoutes_.end(),
            [busId](const MixerOutputRoute& r) { return r.sourceBusId == busId; }),
        outputRoutes_.end()
    );
}

std::vector<MixerOutputRoute> MixerEngine::findOutputRoutesForBus(int64_t busId) const
{
    std::vector<MixerOutputRoute> result;
    for (const auto& route : outputRoutes_) {
        if (route.sourceBusId == busId) {
            result.push_back(route);
        }
    }
    return result;
}

// --- Auto channel creation ---

void MixerEngine::autoCreateChannels(uint32_t inputChannelCount, uint32_t outputChannelCount)
{
    physicalInputCount_ = inputChannelCount;
    physicalOutputCount_ = outputChannelCount;

    // Remove existing channels if any
    for (int i = (int)channels_.size() - 1; i >= 0; --i) {
        removeChannel(i);
    }

    // Create one channel strip per input
    for (uint32_t i = 0; i < inputChannelCount; ++i) {
        addChannel((int)i);
        auto* ch = getChannel((int)i);
        if (ch) {
            char label[32];
            snprintf(label, sizeof(label), "Input %u", i + 1);
            ch->setLabel(label);
        }
    }

    // Set default output routes
    outputRoutes_.clear();

    if (!masterBus_) return;

    MixerOutputRoute masterRoute;
    masterRoute.sourceBusId = masterBus_->id();
    masterRoute.sourceStartChannel = 0;
    masterRoute.targetStartChannel = 0;
    masterRoute.channels = 2;
    outputRoutes_.push_back(masterRoute);
}

void MixerEngine::applySnapshot(const MixerSnapshot& snapshot)
{
    // Apply channel states
    for (const auto& cs : snapshot.channels) {
        auto* channel = getChannel(cs.channelIndex);
        if (!channel) continue;

        channel->setVolume(cs.volume);
        channel->setPan(cs.pan);
        channel->setMute(cs.mute);
        channel->setSolo(cs.solo);
        channel->setChannelType(cs.channelType);
        channel->setLabel(cs.label);
        channel->setHpEnabled(cs.hpEnabled);
        channel->setHpFrequency(cs.hpFrequency);

        for (size_t i = 0; i < cs.auxSendLevels.size() && i < channel->auxSendCount(); ++i) {
            auto config = channel->auxSend(i);
            config.level = cs.auxSendLevels[i];
            channel->setAuxSend(i, config);
        }
    }

    // Apply bus states
    for (const auto& bs : snapshot.buses) {
        auto* bus = getBus(bs.id);
        if (!bus) continue;

        bus->setName(bs.name);
        bus->setVolume(bs.volume);
        bus->setMute(bs.mute);
    }

    // Replace routes
    routes_ = snapshot.routes;
}
