#include "Player.h"
#include "Logger.h"

// #include <valgrind/memcheck.h>

Player::Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSink> audioSink): bell::Task("player", 10 * 1024, +0, 1)
{
    this->audioSink = audioSink;
    this->manager = manager;
    startTask();
}

void Player::pause()
{
    this->currentTrack->audioStream->isPaused = true;
}

void Player::play()
{
    this->currentTrack->audioStream->isPaused = false;
}

void Player::setVolume(uint32_t volume)
{
    this->volume = (volume / (double)MAX_VOLUME) * 255;

    // Calculate and cache log volume value
    auto vol = 255 - this->volume;
    uint32_t value = (log10(255 / ((float)vol + 1)) * 105.54571334);
    if (value >= 254) value = 256;
    logVolume = value << 8; // *256

    // Pass volume event to the sink if volume is sink-handled
    if (!this->audioSink->softwareVolumeControl)
    {
        this->audioSink->volumeChanged(volume);
    }
}

void Player::seekMs(size_t positionMs)
{
    this->currentTrack->audioStream->seekMs(positionMs);
    // VALGRIND_DO_LEAK_CHECK;
}

void Player::feedPCM(std::vector<uint8_t>& data)
{
    // Simple digital volume control alg
    // @TODO actually extract it somewhere
    if (this->audioSink->softwareVolumeControl)
    {
        int16_t* psample;
        uint32_t pmax;
        psample = (int16_t*)(data.data());
        for (int32_t i = 0; i < (data.size() / 2); i++)
        {
            int32_t temp;
            // Offset data for unsigned sinks
            if (this->audioSink->usign)
            {
                temp = ((int32_t)psample[i] + 0x8000) * logVolume;
            }
            else
            {
                temp = ((int32_t)psample[i]) * logVolume;
            }
            psample[i] = (temp >> 16) & 0xFFFF;
        }
    }

    this->audioSink->feedPCMFrames(data);
}

void Player::runTask()
{
    std::scoped_lock lock(this->runningMutex);
    this->isRunning = true;
    while (isRunning)
    {
        if (this->trackQueue.wpop(currentTrack)) {
            currentTrack->audioStream->startPlaybackLoop();
            currentTrack->loadedTrackCallback = nullptr;
            currentTrack->audioStream->streamFinishedCallback = nullptr;
            currentTrack->audioStream->audioSink = nullptr;
            currentTrack->audioStream->pcmCallback = nullptr;
        } else {
            //usleep(100000);
        }
    }
}

void Player::stop() {
    this->isRunning = false;
    CSPOT_LOG(info, "Stopping player");
    this->trackQueue.clear();
    cancelCurrentTrack();
    CSPOT_LOG(info, "Track cancelled");
    std::scoped_lock lock(this->runningMutex);
    CSPOT_LOG(info, "Done");
}

void Player::cancelCurrentTrack()
{
    if (currentTrack != nullptr)
    {
        if (currentTrack->audioStream != nullptr && currentTrack->audioStream->isRunning)
        {
            currentTrack->audioStream->isRunning = false;
        }
    }
}

void Player::handleLoad(std::shared_ptr<TrackReference> trackReference, std::function<void()>& trackLoadedCallback, uint32_t position_ms, bool isPaused)
{
    std::lock_guard<std::mutex> guard(loadTrackMutex);
    cancelCurrentTrack();

    pcmDataCallback framesCallback = [=](std::vector<uint8_t>& frames) {
        this->feedPCM(frames);
    };

    auto loadedLambda = trackLoadedCallback;

    auto track = std::make_shared<SpotifyTrack>(this->manager, trackReference, position_ms, isPaused);
    track->trackInfoReceived = this->trackChanged;
    track->loadedTrackCallback = [this, track, framesCallback, loadedLambda]() {
        loadedLambda();
        track->audioStream->streamFinishedCallback = this->endOfFileCallback;
        track->audioStream->audioSink = this->audioSink;
        track->audioStream->pcmCallback = framesCallback;
        this->trackQueue.push(track);
    };
}