/*
 * Copyright 2024 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "speechsynthesizer.h"

#include <jsoncpp/json/json.h>

#include <filesystem>
#include <fstream>

#include "modelconfig/modelstatustracker.h"
#include "speech.h"
#include "taskmanager/taskrunner.h"
#include "utils/logger.h"
#include "utils/util.h"

SpeechSynthesizer::SpeechSynthesizer() {}

SpeechSynthesizer::~SpeechSynthesizer() {
    destroy();
    LOGD("~SpeechSynthesizer");
}

bool SpeechSynthesizer::init(int &errorCode, const ISpeechEnginePtr &speechEngine) {
    speechEngine_ = speechEngine;

    ModelStatusTracker modelStatusTracker(speechEngine_->currentModel());
    auto result = speechEngine_->initSynthesizeModule();
    if (!result.success) {
        LOGE("Init synthesize once module engine error {}", result.error.getMessage());
        errorCode = engineError::getSpeechErrorCode(result.error.getCode());
        return false;
    }

    return true;
}

void SpeechSynthesizer::destroy() {
    stopSpeaking();
    synthesisResultCallback_ = nullptr;
    if (speechEngine_ == nullptr) {
        return;
    }
    speechEngine_->setSynthesisResultCallback(nullptr);
    auto result = speechEngine_->destroySynthesizeModule();
    if (!result.success) {
        LOGE("Failed to destroy synthesize module: {}", result.error.getMessage());
    }
}

void SpeechSynthesizer::setSynthesisResultCallback(const SynthesisResultCallback &callback) {
    synthesisResultCallback_ = callback;
}

void SpeechSynthesizer::setAudioConfig(const AudioConfigInfo &audioConfigInfo) {
    outputAudioInfo_ = audioConfigInfo.outputAudioInfo();
}

void SpeechSynthesizer::synthesizeTextOnce(const std::string &text) {
    TtsParam ttsParam;
    ttsParam.outputAudioInfo = outputAudioInfo_;
    ttsParam.text = text;
    ttsParamQueue_.enqueue(ttsParam);
    onceTtsAsync();
}

void SpeechSynthesizer::stopSpeaking() {
    // TODO 该接口需实现
    LOGD("SpeechSynthesizer stopSpeaking!");
}

void SpeechSynthesizer::setSynthesisResultCallbackWithError(const ai_engine::EngineError &error) const {
    SpeechSynthesisResult result{};
    result.reason = int(ai_engine::core_ai::speech::SpeechResultReason::SpeechErrorOccurred);
    result.audioFormat = "";
    result.audioRate = 0;
    result.audioChannel = 0;
    result.base64AudioData.clear();
    result.errorCode = engineError::getSpeechErrorCode(error.getCode());
    result.errorMessage = error.getMessage();
    if (synthesisResultCallback_ != nullptr) {
        synthesisResultCallback_(result);
    }
}

void SpeechSynthesizer::writePcmToFile(const std::vector<uint8_t> &audioData) {
    std::filesystem::path filename = outputAudioInfo_.outputFilePath;
    std::filesystem::path directory = filename.parent_path();
    if (!std::filesystem::exists(directory)) {
        if (!std::filesystem::create_directories(directory)) {
            LOGE("Failed to create directory: {}", directory.string());
            return;
        }
    }
    if (!std::filesystem::exists(filename)) {
        std::ofstream file(filename, std::ios::binary);
        if (!file.is_open()) {
            LOGE("Failed to create file {}", filename.string());
            return;
        }

        file.write(reinterpret_cast<const char *>(audioData.data()), audioData.size());
        file.close();
        return;
    }
    if (isFirstWrite_) {
        std::ofstream file(filename, std::ios::binary | std::ios::trunc);
        if (!file.is_open()) {
            LOGE("Failed to open file {}", filename.string());
            return;
        }

        file.write(reinterpret_cast<const char *>(audioData.data()), audioData.size());
        file.close();
    } else {
        std::ofstream file(filename, std::ios::binary | std::ios::app);
        if (!file.is_open()) {
            LOGE("Failed to open file {}", filename.string());
            return;
        }

        file.write(reinterpret_cast<const char *>(audioData.data()), audioData.size());
        file.close();
    }
}

void SpeechSynthesizer::handleSynthesisResult(const ai_engine::core_ai::speech::SynthesisResult &result,
                                              const std::shared_ptr<AudioPlayer> &audioPlayer,
                                              const TtsParam &ttsParam) {
    SpeechSynthesisResult synthesisResult{};
    synthesisResult.reason = int(result.reason);
    synthesisResult.audioFormat = result.audioFormat;
    synthesisResult.audioRate = result.audioRate;
    synthesisResult.audioChannel = result.audioChannel;
    synthesisResult.base64AudioData = result.base64AudioData;
    synthesisResult.errorCode = engineError::getSpeechErrorCode(result.error.getCode());
    synthesisResult.errorMessage = result.error.getMessage();

    if (ttsParam.outputAudioInfo.outputTarget == OutputAudioInfo::OutputTarget::DEFAULT_SPEAKER) {
        std::vector<uint8_t> audioData = util::base64Decode(result.base64AudioData);
        audioPlayer->playing(audioData);
        if (synthesisResultCallback_ != nullptr) {
            synthesisResultCallback_(synthesisResult);
        }
    } else if (ttsParam.outputAudioInfo.outputTarget == OutputAudioInfo::OutputTarget::PCM_OUTPUT_FILE) {
        std::vector<uint8_t> audioData = util::base64Decode(result.base64AudioData);
        writePcmToFile(audioData);
        if (isFirstWrite_) {
            isFirstWrite_ = false;
        }
        if (synthesisResultCallback_ != nullptr) {
            synthesisResultCallback_(synthesisResult);
        }
    } else if (ttsParam.outputAudioInfo.outputTarget == OutputAudioInfo::OutputTarget::PCM_DATA) {
        if (synthesisResultCallback_ != nullptr) {
            synthesisResultCallback_(synthesisResult);
        }
    }
}

void SpeechSynthesizer::onceTtsAsync() {
    if (taskId_ != task::INVALID_TASK_ID) {
        return;
    }
    taskId_ = task::generateTaskId();
    std::shared_ptr sharedObject = getSharedObject();
    task::TaskRunner::getInstance().addTask({taskId_, task::TaskType::OnceTextToSpeech, [sharedObject]() {
                                                 sharedObject->onceTtsTask();
                                                 sharedObject->taskId_ = task::INVALID_TASK_ID;
                                             }});
}

void SpeechSynthesizer::onceTtsTask() {
    TtsParam ttsParam;
    while (ttsParamQueue_.wait_dequeue_timed(ttsParam, 10 * 1000 * 1000)) {
        Json::Value value;
        value["speed"] = ttsParam.outputAudioInfo.speed;
        value["volume"] = ttsParam.outputAudioInfo.volume;
        value["pitch"] = ttsParam.outputAudioInfo.pitch;
        std::shared_ptr<AudioPlayer> audioPlayer = std::make_shared<AudioPlayer>();
        speechEngine_->setSynthesisResultCallback(
            [ttsParam, audioPlayer, this](const ai_engine::core_ai::speech::SynthesisResult &result) {
                handleSynthesisResult(result, audioPlayer, ttsParam);
            });
        ModelStatusTracker modelStatusTracker(speechEngine_->currentModel());
        engineInUse_ = true;
        auto result = speechEngine_->synthesizeText(util::jsonToString(value), ttsParam.text);
        if (!result.success) {
            LOGE("Failed to speech synthesize once: {}, {}.", result.error.getCode(), result.error.getMessage());
            setSynthesisResultCallbackWithError(result.error);
        }
        engineInUse_ = false;
    }
}
