/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * Licensed under Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with License.
 * You may obtain a copy of License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "oh_audio_play.h"
#include <cstdint>
#include <hilog/log.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include "ohaudio/native_audio_session_manager.h"
#include "audio_constants.h"
#include "securec.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN AudioConstants::LOG_DOMAIN
#define LOG_TAG "OH_AUDIO_PLAY"

// [Start OnAudioInterruptEvent]
void OnAudioInterruptEvent(OH_AudioRenderer *audioRenderer, [[maybe_unused]] void *userData,
                                   OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) {
    auto &player = OHAudioPlayer::GetInstance();
    
    if (hint == AUDIOSTREAM_INTERRUPT_HINT_PAUSE) {
        OH_LOG_INFO(LOG_APP, "Audio paused by interrupt.");
        player.isPaused.store(true);
        auto now = std::chrono::system_clock::now().time_since_epoch();
        player.pauseStartTime.store(std::chrono::duration_cast<std::chrono::milliseconds>(now).count());
        player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
    } else if (hint == AUDIOSTREAM_INTERRUPT_HINT_STOP) {
        OH_LOG_INFO(LOG_APP, "Audio stopped by interrupt.");
        player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
    } else if ((type == AUDIOSTREAM_INTERRUPT_SHARE) && (hint == AUDIOSTREAM_INTERRUPT_HINT_RESUME)) {
        OH_LOG_INFO(LOG_APP, "Audio resume interrupt received.");
        if (player.isPaused.load()) {
            auto now = std::chrono::system_clock::now().time_since_epoch();
            int64_t resumeTime = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
            int64_t pauseTime = player.pauseStartTime.load();
            int64_t elapsedMs = resumeTime - pauseTime;
            OH_LOG_INFO(LOG_APP, "Audio resume after %{public}ld ms.", elapsedMs);
            
            player.isPaused.store(false);
            
            if (elapsedMs <= player.RESUME_THRESHOLD_MS) {
                OH_LOG_INFO(LOG_APP, "Resume within 15s, resuming playback.");
                player.PlayStatusCallback(AudioConstants::PlayStatus::Play);
            } else {
                OH_LOG_INFO(LOG_APP, "Resume after 15s, not resuming playback.");
            }
        }
    } else if (hint == AUDIOSTREAM_INTERRUPT_HINT_MUTE) {
        OH_LOG_INFO(LOG_APP, "Audio muted, stopping progress update.");
        player.isMuted.store(true);
        player.mutePosition.store(player.GetCurrentPosition());
        auto now = std::chrono::system_clock::now().time_since_epoch();
        player.muteStartTime.store(std::chrono::duration_cast<std::chrono::milliseconds>(now).count());
        player.PlayStatusCallback(AudioConstants::PlayStatus::Muted);
    } else if (hint == AUDIOSTREAM_INTERRUPT_HINT_UNMUTE) {
        if (player.isMuted.load()) {
            auto now = std::chrono::system_clock::now().time_since_epoch();
            int64_t unmuteTime = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
            int64_t muteTime = player.muteStartTime.load();
            int64_t elapsedMs = unmuteTime - muteTime;
            player.isMuted.store(false);
            
            if (elapsedMs <= player.RESUME_THRESHOLD_MS) {
                OH_LOG_INFO(LOG_APP, "Unmute within 15s, notifying unmuted state.");
                player.PlayStatusCallback(AudioConstants::PlayStatus::UnMuted);
            } else {
                OH_LOG_INFO(LOG_APP, "Unmute after 15s, preparing position and notifying pause state.");
                uint32_t position = player.mutePosition.load();
                player.PreparePosition(position);
                player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
            }
        }
    }
}
// [End OnAudioInterruptEvent]

void OnAudioErrorEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer, [[maybe_unused]] void *userData,
                              OH_AudioStream_Result error) {
    OH_LOG_ERROR(LOG_APP, "Audio render error, ret: %{public}d", error);
}

void OnAudioOutputDeviceChangeEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer,
                                [[maybe_unused]] void *userData, OH_AudioStream_DeviceChangeReason reason) {
    OH_LOG_INFO(LOG_APP, "Audio output device changed.");
}

// [Start OnAudioRendererWriteDataEvent]
OH_AudioData_Callback_Result OnAudioRendererWriteDataEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer,
                                                            void *userData, void *audioData, int32_t audioDataSize) {
    auto audioBookFileInfo = reinterpret_cast<AudioBookFileInfo *>(userData);
    auto &player = OHAudioPlayer::GetInstance();
    int64_t remainedSize = audioBookFileInfo->audioBookFileSize - audioBookFileInfo->currentPlayOffset;
    int32_t bytesToRead = std::min(static_cast<int64_t>(audioDataSize), remainedSize);
    size_t actualRead = player.bufferQueue->Read(audioData, bytesToRead);
    
    if (actualRead < 0) {
        OH_LOG_ERROR(LOG_APP, "Read audio data error.");
        return AUDIO_DATA_CALLBACK_RESULT_INVALID;
    }
    
    audioBookFileInfo->currentPlayOffset += actualRead;
    int64_t framesProvided = actualRead / AudioConstants::FRAME_SIZE;
    player.totalFramesProvided += framesProvided;
    
    if (actualRead == audioDataSize) {
        return AUDIO_DATA_CALLBACK_RESULT_VALID;
    }
    
    if (actualRead < audioDataSize) {
        bool isLastFrame = (audioBookFileInfo->currentPlayOffset >= audioBookFileInfo->audioBookFileSize);
        if (isLastFrame) {
            memset_s(static_cast<char*>(audioData) + actualRead, audioDataSize - actualRead, 0, audioDataSize - actualRead);
            return AUDIO_DATA_CALLBACK_RESULT_VALID;
        } else {
            player.totalFramesProvided -= framesProvided;
            audioBookFileInfo->currentPlayOffset -= actualRead;
            OH_LOG_INFO(LOG_APP, "Incomplete frame, will retry. Read: %{public}ld, Requested: %{public}d", actualRead, audioDataSize);
            return AUDIO_DATA_CALLBACK_RESULT_INVALID;
        }
    }
    return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
// [End OnAudioRendererWriteDataEvent]

// [Start InitPlayer]
void OHAudioPlayer::InitPlayer() {
    if ((audioRenderer != nullptr) || (rendererBuilder != nullptr) || (audioBookFileInfo != nullptr)) {
        OH_LOG_INFO(LOG_APP, "Previous audio player or builder or fileInfo remained and release it.");
        ReleasePlayer();
    }

    OH_AudioStream_Type streamType = AUDIOSTREAM_TYPE_RENDERER;
    auto ret = OH_AudioStreamBuilder_Create(&rendererBuilder, streamType);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Create stream builder failed, ret: %{public}d", ret);
        return;
    }

    audioBookFileInfo = new AudioBookFileInfo();

    (void)OH_AudioStreamBuilder_SetSamplingRate(rendererBuilder, AudioConstants::SAMPLE_RATE);
    (void)OH_AudioStreamBuilder_SetChannelCount(rendererBuilder, AudioConstants::CHANNEL_COUNT);
    (void)OH_AudioStreamBuilder_SetSampleFormat(rendererBuilder, AUDIOSTREAM_SAMPLE_S16LE);
    (void)OH_AudioStreamBuilder_SetEncodingType(rendererBuilder, AUDIOSTREAM_ENCODING_TYPE_RAW);
    (void)OH_AudioStreamBuilder_SetRendererInfo(rendererBuilder, AUDIOSTREAM_USAGE_AUDIOBOOK);
    // [Start OH_AudioStreamBuilder_SetRendererInterruptCallback]
    (void)OH_AudioStreamBuilder_SetRendererInterruptCallback(rendererBuilder, OnAudioInterruptEvent, nullptr);
    // [End OH_AudioStreamBuilder_SetRendererInterruptCallback]
    (void)OH_AudioStreamBuilder_SetRendererErrorCallback(rendererBuilder, OnAudioErrorEvent, nullptr);
    (void)OH_AudioStreamBuilder_SetRendererOutputDeviceChangeCallback(rendererBuilder, OnAudioOutputDeviceChangeEvent, nullptr);
    (void)OH_AudioStreamBuilder_SetRendererWriteDataCallback(rendererBuilder, OnAudioRendererWriteDataEvent,
                                                                reinterpret_cast<void *>(audioBookFileInfo));

    ret = OH_AudioStreamBuilder_GenerateRenderer(rendererBuilder, &audioRenderer);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Create audio renderer failed, ret: %{public}d", ret);
        ReleasePlayer();
    }
    OH_AudioSession_Strategy strategy = {
        .concurrencyMode = CONCURRENCY_DEFAULT
    };
    // 4 = PAUSE_WHEN_INTERRUPTED, 2 = MUTE_WHEN_INTERRUPTED
    OH_AudioRenderer_SetIndependentAudioSessionStrategy(audioRenderer, &strategy, 4);
    OH_AudioManager_GetAudioResourceManager(&audioResourceManager);
    if (audioResourceManager != nullptr) {
        OH_AudioResourceManager_CreateWorkgroup(audioResourceManager, "AudioBookWorkgroup", &audioWorkgroup);
        OH_LOG_INFO(LOG_APP, "Audio workgroup created");
    }
    bufferQueue = new AudioBufferQueue(prefetchBufferSize);
    OH_LOG_INFO(LOG_APP, "Init player successfully.");
}
// [End InitPlayer]

void OHAudioPlayer::LoadAudioBookInfo(uint32_t audioBookFd, uint32_t audioBookFileSize, uint32_t audioBookDuration,
                                         uint32_t audioBookFileOffset) {
    if (audioBookFileInfo == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioBookFileInfo is null.");
        return;
    }
    StopPrefetch();

    audioBookFileInfo->audioBookFd = audioBookFd;
    audioBookFileInfo->audioBookFileSize = audioBookFileSize;
    audioBookFileInfo->audioBookDuration = audioBookDuration;
    audioBookFileInfo->audioBookFileOffset = audioBookFileOffset;
    audioBookFileInfo->currentPlayOffset = 0;
    (void)lseek(audioBookFileInfo->audioBookFd, audioBookFileOffset, SEEK_SET);
    seekStartPoint = 0;
    totalFramesProvided = 0;
    isFileLoad = true;
    
    if (bufferQueue != nullptr) {
        bufferQueue->Clear();
    }
    StartPrefetch();
    OH_LOG_INFO(LOG_APP,
                "Load audio book information successfully. "
                "Audio book fd: %{public}d, "
                "file size: %{public}d, "
                "Audio book duration: %{public}d."
                "file offset: %{public}d."
                "seekStartPoint: %{public}d",
                audioBookFd, audioBookFileSize, audioBookDuration, audioBookFileOffset, seekStartPoint);
}

// [Start PlayAudioBook]
void OHAudioPlayer::PlayAudioBook() {
    if (audioRenderer == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
        return;
    }
    auto ret = OH_AudioRenderer_Start(audioRenderer);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Play audio book failed, ret: %{public}d", ret);
        return;
    }
    OH_LOG_INFO(LOG_APP, "Play audio book successfully.");
}
// [End PlayAudioBook]

// [Start PauseAudioBook]
void OHAudioPlayer::PauseAudioBook() {
    if (audioRenderer == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
        return;
    }
    auto ret = OH_AudioRenderer_Pause(audioRenderer);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Pause audio book failed, ret: %{public}d", ret);
        return;
    }
    OH_LOG_INFO(LOG_APP, "Pause audio book successfully.");
}
// [Start PauseAudioBook]

void OHAudioPlayer::StopAudioBook() {
    if (audioRenderer == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
        return;
    }
    auto ret = OH_AudioRenderer_Stop(audioRenderer);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Stop audio book failed, ret: %{public}d", ret);
        return;
    }
    ret = OH_AudioRenderer_Flush(audioRenderer);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Flush audio book failed, ret: %{public}d", ret);
        return;
    }
    OH_LOG_INFO(LOG_APP, "Stop audio book successfully.");
}

uint32_t OHAudioPlayer::GetCurrentPosition() {
    if (audioRenderer == nullptr) {
        return 0;
    }
    if (isFileLoad) {
        OH_AudioRenderer_Flush(audioRenderer);
        isFileLoad = false;
    }
    int64_t framePosition;
    int64_t timestamp;
    auto ret = OH_AudioRenderer_GetAudioTimestampInfo(audioRenderer, &framePosition, &timestamp);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Get audio timestamp info failed, ret: %{public}d", ret);
        return 0;
    }
    
    uint32_t playedBytes = static_cast<uint32_t>(framePosition * AudioConstants::FRAME_SIZE);
    OH_LOG_INFO(LOG_APP, "framePosition: %{public}ld "
                "playedBytes: %{public}d "
                "seekStartPoint: %{public}d", framePosition, playedBytes, seekStartPoint);
    
    return (playedBytes + seekStartPoint);
}

uint32_t OHAudioPlayer::GetDuration() {
    if (audioBookFileInfo == nullptr) {
        return 0;
    }
    return audioBookFileInfo->audioBookDuration;
}

uint32_t OHAudioPlayer::GetMutePosition() {
    return mutePosition.load();
}

void OHAudioPlayer::PreparePosition(uint32_t position) {
    if (audioBookFileInfo == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioBookFileInfo is null.");
        return;
    }
    if (position > audioBookFileInfo->audioBookFileSize) {
        OH_LOG_ERROR(LOG_APP, "Prepare position exceeds file size.");
        return;
    }
    StopPrefetch();
    
    if (audioRenderer != nullptr) {
        OH_AudioRenderer_Pause(audioRenderer);
    }
    if (audioRenderer != nullptr) {
        OH_AudioRenderer_Flush(audioRenderer);
    }
    
    uint32_t alignedPosition = (position / AudioConstants::FRAME_SIZE) * AudioConstants::FRAME_SIZE;
    auto ret = lseek(audioBookFileInfo->audioBookFd, (audioBookFileInfo->audioBookFileOffset + alignedPosition), SEEK_SET);
    if (ret == -1) {
        OH_LOG_ERROR(LOG_APP, "Prepare position failed.");
        return;
    }
    audioBookFileInfo->currentPlayOffset = alignedPosition;
    seekStartPoint = alignedPosition;
    totalFramesProvided = 0;
    if (bufferQueue != nullptr) {
        bufferQueue->Clear();
    }
    StartPrefetch();
    OH_LOG_INFO(LOG_APP, "Prepare position successfully. Position: %{public}u, Aligned: %{public}u", position, alignedPosition);
}

// [Start SeekTo]
void OHAudioPlayer::SeekTo(uint32_t position) {
    PreparePosition(position);
    if (audioRenderer != nullptr) {
        OH_AudioRenderer_Start(audioRenderer);
    }
    OH_LOG_INFO(LOG_APP, "Seek to position successfully and started.");
}
// [End SeekTo]

void OHAudioPlayer::SetSpeed(float speed) {
    if (audioRenderer == nullptr) {
        OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
        return;
    }
    if (speed < AudioConstants::SPEED_0_25 || speed > AudioConstants::SPEED_4_0) {
        OH_LOG_ERROR(LOG_APP, "Invalid speed value. Speed must be between 0.25 and 4.0.");
        return;
    }
    auto ret = OH_AudioRenderer_SetSpeed(audioRenderer, speed);
    if (ret != AUDIOSTREAM_SUCCESS) {
        OH_LOG_ERROR(LOG_APP, "Set speed failed, ret: %{public}d", ret);
        return;
    }
    OH_LOG_INFO(LOG_APP, "Set speed successfully. Speed: %{public}f", speed);
}

AudioTimestampInfo OHAudioPlayer::GetAudioTimestampInfo() {
    AudioTimestampInfo info{0, 0};
    if (audioRenderer == nullptr) {
        return info;
    }
    
    OH_AudioRenderer_GetAudioTimestampInfo(audioRenderer, &info.framePosition, &info.timestamp);
    return info;
}

// [Start ReleasePlayer]
void OHAudioPlayer::ReleasePlayer() {
    StopPrefetch();
    
    if (bufferQueue != nullptr) {
        delete bufferQueue;
        bufferQueue = nullptr;
    }
    if (audioWorkgroup != nullptr && audioResourceManager != nullptr) {
        OH_AudioResourceManager_ReleaseWorkgroup(audioResourceManager, audioWorkgroup);
        audioWorkgroup = nullptr;
        OH_LOG_INFO(LOG_APP, "Audio workgroup released");
    }
    if (audioResourceManager != nullptr) {
        audioResourceManager = nullptr;
    }
    if (rendererBuilder != nullptr) {
        OH_AudioStreamBuilder_Destroy(rendererBuilder);
        rendererBuilder = nullptr;
    }
    if (audioRenderer != nullptr) {
        OH_AudioRenderer_Stop(audioRenderer);
        OH_AudioRenderer_Release(audioRenderer);
        audioRenderer = nullptr;
    }
    if (audioBookFileInfo != nullptr) {
        delete audioBookFileInfo;
        audioBookFileInfo = nullptr;
    }
    OH_LOG_INFO(LOG_APP, "Release player successfully.");
}
// [End ReleasePlayer]

void OHAudioPlayer::StartPrefetch() {
    if (isPrefetching.load()) {
        return;
    }
    
    isPrefetching.store(true);
    prefetchThread = new std::thread(&OHAudioPlayer::PrefetchThreadFunc, this);
}

void OHAudioPlayer::StopPrefetch() {
    if (!isPrefetching.load()) {
        return;
    }
    
    isPrefetching.store(false);
    if (prefetchThread != nullptr && prefetchThread->joinable()) {
        prefetchThread->join();
        delete prefetchThread;
        prefetchThread = nullptr;
    }
    if (audioWorkgroup != nullptr && prefetchThreadTokenId != 0) {
        OH_AudioWorkgroup_RemoveThread(audioWorkgroup, prefetchThreadTokenId);
        prefetchThreadTokenId = 0;
        OH_LOG_INFO(LOG_APP, "Prefetch thread removed from workgroup");
    }
}

void OHAudioPlayer::PrefetchThreadFunc() {
    OH_LOG_INFO(LOG_APP, "Prefetch thread started");
    if (audioWorkgroup != nullptr) {
        OH_AudioWorkgroup_AddCurrentThread(audioWorkgroup, &prefetchThreadTokenId);
        OH_LOG_INFO(LOG_APP, "Prefetch thread added to workgroup, token: %{public}d", prefetchThreadTokenId);
    }
    
    while (isPrefetching.load()) {
        if (audioWorkgroup != nullptr) {
            auto now = std::chrono::system_clock::now().time_since_epoch();
            auto startTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
            OH_AudioWorkgroup_Start(audioWorkgroup, startTimeMs, startTimeMs + prefetchDelayMs);
        }
        int64_t remainingFile = audioBookFileInfo->audioBookFileSize - 
                               audioBookFileInfo->currentPlayOffset;
        
        if (remainingFile <= 0) {
            OH_LOG_INFO(LOG_APP, "File read complete, stop prefetching");
            if (audioWorkgroup != nullptr) {
                OH_AudioWorkgroup_Stop(audioWorkgroup);
            }
            break;
        }
        if (bufferQueue->IsFull()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(AudioConstants::TEN_MILLISECONDS));
            
            if (audioWorkgroup != nullptr) {
                OH_AudioWorkgroup_Stop(audioWorkgroup);
            }
            continue;
        }
        
        size_t availableSpace = bufferQueue->GetAvailableSpace();
        size_t bytesToRead = std::min(static_cast<size_t>(remainingFile), availableSpace);
        std::this_thread::sleep_for(std::chrono::milliseconds(prefetchDelayMs));
        bufferQueue->Write(audioBookFileInfo->audioBookFd, bytesToRead);
        
        if (audioWorkgroup != nullptr) {
            OH_AudioWorkgroup_Stop(audioWorkgroup);
        }
    }
    OH_LOG_INFO(LOG_APP, "Prefetch thread stopped");
}

int64_t OHAudioPlayer::GetTotalFramesProvided() const {
    return totalFramesProvided;
}

int64_t OHAudioPlayer::GetCurrentTotalFramesProvided() const {
    uint32_t frameSize = AudioConstants::CHANNEL_COUNT * (AudioConstants::BITS_PER_SAMPLE / 8);
    return (seekStartPoint / frameSize) + totalFramesProvided;;
}

int64_t OHAudioPlayer::GetTotalFileFrames() const {
    if (audioBookFileInfo == nullptr) {
        return 0;
    }
    uint32_t frameSize = AudioConstants::CHANNEL_COUNT * (AudioConstants::BITS_PER_SAMPLE / 8);
    return audioBookFileInfo->audioBookFileSize / frameSize;
}