// IWYU pragma: private

/// @file spi_4_stub.cpp
/// @brief Stub/Mock implementation of Quad-SPI for testing
///
/// This provides a mock SPIQuad implementation for unit tests.
/// It captures transmitted data for inspection without real hardware.

#include "platforms/stub/spi_4_stub.h"
#include "platforms/shared/spi_manager.h"  // For DMABuffer, TransmitMode, SPIError
#include "fl/log/log.h"
#include "fl/stl/noexcept.h"

#if defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL)

namespace fl {

// ============================================================================
// SpiHw4Stub Implementation
// ============================================================================

SpiHw4Stub::SpiHw4Stub(int bus_id, const char* name)
 FL_NOEXCEPT : mBusId(bus_id)
    , mName(name)
    , mInitialized(false)
    , mBusy(false)
    , mClockSpeed(20000000)
    , mTransmitCount(0)
    , mCurrentBuffer()
    , mBufferAcquired(false) {
}

bool SpiHw4Stub::begin(const SpiHw4::Config& config) FL_NOEXCEPT {
    if (mInitialized) {
        return true;  // Already initialized
    }

    // Validate bus_num against mBusId if driver has pre-assigned ID
    if (mBusId != -1 && config.bus_num != static_cast<u8>(mBusId)) {
        return false;  // Mismatch
    }

    mClockSpeed = config.clock_speed_hz;
    mInitialized = true;
    return true;
}

void SpiHw4Stub::end() FL_NOEXCEPT {
    mInitialized = false;
    mBusy = false;
    mLastBuffer.clear();

    // Release current buffer
    mCurrentBuffer.reset();
    mBufferAcquired = false;
}

DMABuffer SpiHw4Stub::acquireDMABuffer(size_t bytes_per_lane) FL_NOEXCEPT {
    if (!mInitialized) {
        return DMABuffer(SPIError::NOT_INITIALIZED);
    }

    // Auto-wait if previous transmission still active
    if (mBusy) {
        waitComplete();
    }

    // For quad-lane SPI: num_lanes = 4
    constexpr size_t num_lanes = 4;
    const size_t total_size = bytes_per_lane * num_lanes;

    // Create a new DMABuffer with the specified size
    mCurrentBuffer = DMABuffer(total_size);

    if (!mCurrentBuffer.ok()) {
        return mCurrentBuffer;  // Return error
    }

    mBufferAcquired = true;

    // Return a copy of the buffer (shared_ptr will be shared)
    return mCurrentBuffer;
}

bool SpiHw4Stub::transmit(TransmitMode mode) FL_NOEXCEPT {
    (void)mode;  // Unused in stub

    if (!mInitialized || !mBufferAcquired) {
        return false;
    }

    if (!mCurrentBuffer.ok() || mCurrentBuffer.data().size() == 0) {
        return true;  // Nothing to transmit
    }

    // Capture data for inspection
    mLastBuffer.clear();
    fl::span<u8> buffer_span = mCurrentBuffer.data();
    mLastBuffer.reserve(buffer_span.size());
    for (size_t i = 0; i < buffer_span.size(); ++i) {
        mLastBuffer.push_back(buffer_span[i]);
    }

    mTransmitCount++;
    mBusy = true;

    return true;
}

bool SpiHw4Stub::waitComplete(u32 timeout_ms) FL_NOEXCEPT {
    (void)timeout_ms;  // Unused in mock
    mBusy = false;

    // AUTO-RELEASE DMA buffer
    mBufferAcquired = false;

    return true;  // Always succeeds instantly
}

bool SpiHw4Stub::isBusy() const FL_NOEXCEPT {
    return mBusy;
}

bool SpiHw4Stub::isInitialized() const FL_NOEXCEPT {
    return mInitialized;
}

int SpiHw4Stub::getBusId() const FL_NOEXCEPT {
    return mBusId;
}

const char* SpiHw4Stub::getName() const FL_NOEXCEPT {
    return mName;
}

const fl::vector<u8>& SpiHw4Stub::getLastTransmission() const FL_NOEXCEPT {
    return mLastBuffer;
}

u32 SpiHw4Stub::getTransmissionCount() const FL_NOEXCEPT {
    return mTransmitCount;
}

u32 SpiHw4Stub::getClockSpeed() const FL_NOEXCEPT {
    return mClockSpeed;
}

bool SpiHw4Stub::isTransmissionActive() const FL_NOEXCEPT {
    return mBusy;
}

void SpiHw4Stub::reset() FL_NOEXCEPT {
    mLastBuffer.clear();
    mTransmitCount = 0;
    mBusy = false;
}

fl::vector<fl::vector<u8>> SpiHw4Stub::extractLanes(u8 num_lanes, size_t bytes_per_lane) const FL_NOEXCEPT {
    fl::vector<fl::vector<u8>> lanes(num_lanes);

    // Pre-allocate per-lane buffers
    for (u8 lane = 0; lane < num_lanes; ++lane) {
        lanes[lane].resize(bytes_per_lane);
    }

    // De-interleave: reverse the quad-SPI bit interleaving
    // For quad-SPI: each output byte contains 2 bits from each of 4 lanes
    // Byte format: [D7 C7 B7 A7 D6 C6 B6 A6] (2 bits per lane)

    size_t output_bytes = bytes_per_lane * 4;  // Interleaved size

    for (size_t out_idx = 0; out_idx < output_bytes && out_idx < mLastBuffer.size(); ++out_idx) {
        u8 interleaved_byte = mLastBuffer[out_idx];

        // Which input byte does this correspond to?
        size_t in_byte_idx = out_idx / 4;
        size_t nibble_idx = out_idx % 4;  // Which 2-bit chunk (0-3)

        if (in_byte_idx >= bytes_per_lane) break;

        // Extract 2 bits for each lane
        for (u8 lane = 0; lane < num_lanes && lane < 4; ++lane) {
            u8 bits = (interleaved_byte >> (lane * 2)) & 3;
            lanes[lane][in_byte_idx] |= (bits << ((3 - nibble_idx) * 2));
        }
    }

    return lanes;
}

// ============================================================================
// Instance Registration
// ============================================================================

namespace {
// Singleton getters for mock controller instances (Meyer's Singleton pattern)
fl::shared_ptr<SpiHw4Stub>& getController2_Spi4() FL_NOEXCEPT {
    static fl::shared_ptr<SpiHw4Stub> instance = fl::make_shared<SpiHw4Stub>(2, "MockQuad2");
    return instance;
}

fl::shared_ptr<SpiHw4Stub>& getController3_Spi4() FL_NOEXCEPT {
    static fl::shared_ptr<SpiHw4Stub> instance = fl::make_shared<SpiHw4Stub>(3, "MockQuad3");
    return instance;
}
}  // anonymous namespace

}  // namespace fl

// Platform-specific initialization for stub SPI hardware
namespace fl {
namespace platforms {

/// @brief Initialize stub SpiHw4 instances for testing
///
/// Called lazily on first access to SpiHw4::getAll().
/// Registers mock SpiHw4 controller instances for testing.
void initSpiHw4Instances() FL_NOEXCEPT {
    FL_WARN("Registering SpiHw4 stub instances...");
    SpiHw4::registerInstance(getController2_Spi4());
    SpiHw4::registerInstance(getController3_Spi4());
    FL_WARN("SpiHw4 stub instances registered!");
}

}  // namespace platforms
}  // namespace fl

#endif  // defined(FASTLED_TESTING) || defined(FASTLED_STUB_IMPL)
