/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the 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 "src/utility/timer_worker.h"

#include <boost/bind/bind.hpp>
#include <iostream>

#include "logger/logger.h"
#include "platform_compat.h"

namespace YR {
namespace utility {
using namespace boost::placeholders;
static std::shared_ptr<TimerWorker> timerWorker = nullptr;

Timer::Timer(boost::asio::io_context &io, int timeoutMS, std::weak_ptr<TimerWorker> tw) : weakTW_(tw)
{
    id_ = std::to_string(counter_.fetch_add(1));
    timer_ = std::make_shared<BoostTimer>(io, boost::posix_time::milliseconds(timeoutMS));
}

std::shared_ptr<BoostTimer> &Timer::GetTimer()
{
    return timer_;
}

void Timer::cancel()
{
    if (auto tw = weakTW_.lock(); tw) {
        timer_->cancel();
        std::shared_ptr<Timer> self;
        try {
            self = shared_from_this();
        } catch (const std::exception &e) {
            YRLOG_ERROR("Timer has been destructed, {}", e.what());
            return;
        }
        tw->EarseTimer(self);
    }
}

void Timer::Cancel()
{
    timer_->cancel();
}

std::string Timer::ID()
{
    return id_;
}

TimerWorker::TimerWorker() : isRunning(true)
{
    th = std::thread([this] {
        YR_SET_THREAD_NAME_CURRENT("TimerWorker");
        work = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(
            boost::asio::make_work_guard(io));
        ;
        io.run();
    });
}

TimerWorker::~TimerWorker()
{
    Stop();
}

void TimerWorker::TimeoutHandler(std::weak_ptr<Timer> weakT, int timeoutMilliSeconds, int execTimes,
                                 std::function<void()> fn, const boost::system::error_code &ec)
{
    if (ec) {
        return;
    }
    auto needErase = false;
    auto t = weakT.lock();
    if (t == nullptr) {
        return;
    }
    {
        absl::ReaderMutexLock lock(&this->mu);
        if (!isRunning) {
            return;
        }

        if (execTimes == -1) {
            t->GetTimer()->expires_at(t->GetTimer()->expires_at() +
                                      boost::posix_time::milliseconds(timeoutMilliSeconds));
            t->GetTimer()->async_wait(boost::bind(&TimerWorker::TimeoutHandler, this, weakT, timeoutMilliSeconds,
                                                  execTimes, fn, boost::asio::placeholders::error));
        } else if (auto remainTime = (execTimes - 1); remainTime > 0) {
            t->GetTimer()->expires_at(t->GetTimer()->expires_at() +
                                      boost::posix_time::milliseconds(timeoutMilliSeconds));
            t->GetTimer()->async_wait(boost::bind(&TimerWorker::TimeoutHandler, this, weakT, timeoutMilliSeconds,
                                                  remainTime, fn, boost::asio::placeholders::error));
        } else {
            needErase = true;
        }
    }
    if (needErase) {
        absl::WriterMutexLock lock(&this->timerMu);
        if (timerStore_.find(t->ID()) != timerStore_.end()) {
            timerStore_.erase(t->ID());
        }
    }
    fn();
}

std::shared_ptr<YR::utility::Timer> TimerWorker::CreateTimer(int timeoutMilliSeconds, int execTimes,
                                                             std::function<void()> fn)
{
    auto t = std::make_shared<Timer>(io, timeoutMilliSeconds, weak_from_this());
    {
        absl::ReaderMutexLock lock(&this->mu);
        if (!isRunning) {
            return nullptr;
        }

        std::weak_ptr<Timer> weakT = t;
        t->GetTimer()->async_wait(boost::bind(&TimerWorker::TimeoutHandler, this, weakT, timeoutMilliSeconds, execTimes,
                                              fn, boost::asio::placeholders::error));
    }

    {
        absl::WriterMutexLock lock(&this->timerMu);
        auto ret = timerStore_.emplace(t->ID(), t);
        if (!ret.second) {
            return nullptr;
        }
    }
    return t;
}

void TimerWorker::ExecuteByTimer(std::shared_ptr<YR::utility::Timer> t, int timeoutMilliSeconds,
                                 std::function<void()> fn)
{
    this->ExecuteByTimer(t, timeoutMilliSeconds, 1, fn);
}

void TimerWorker::ExecuteByTimer(std::shared_ptr<YR::utility::Timer> t, int timeoutMilliSeconds, int execTimes,
                                 std::function<void()> fn)
{
    {
        absl::ReaderMutexLock lock(&this->mu);
        if (!isRunning) {
            return;
        }
        if (t == nullptr) {
            YRLOG_WARN("timer is null, will not execute.");
            return;
        }

        std::weak_ptr<Timer> weakT = t;
        t->GetTimer()->expires_at(t->GetTimer()->expires_at() + boost::posix_time::milliseconds(timeoutMilliSeconds));
        t->GetTimer()->async_wait(boost::bind(&TimerWorker::TimeoutHandler, this, weakT, timeoutMilliSeconds, execTimes,
                                              fn, boost::asio::placeholders::error));
    }

    {
        absl::WriterMutexLock lock(&this->timerMu);
        auto ret = timerStore_.emplace(t->ID(), t);
        if (!ret.second) {
            YRLOG_DEBUG("timer {} already existed.", t->ID());
        }
    }
}

void TimerWorker::EarseTimer(std::shared_ptr<YR::utility::Timer> t)
{
    if (!t) {
        return;
    }
    absl::WriterMutexLock lock(&this->timerMu);
    if (timerStore_.find(t->ID()) != timerStore_.end()) {
        timerStore_.erase(t->ID());
    }
}

void TimerWorker::CancelTimer(std::shared_ptr<YR::utility::Timer> t)
{
    if (!t) {
        return;
    }
    (void)t->GetTimer()->cancel();
    this->EarseTimer(t);
}

void TimerWorker::Stop()
{
    {
        absl::WriterMutexLock lock(&this->timerMu);
        for (const auto &pair : timerStore_) {
            pair.second->Cancel();
        }
        timerStore_.clear();
    }

    {
        absl::WriterMutexLock lock(&this->mu);
        isRunning = false;
        if (work) {
            work->reset();
        }
        if (!io.stopped()) {
            io.stop();
        }
    }

    if (th.joinable()) {
        th.join();
    }
}

void InitGlobalTimer(void)
{
    if (timerWorker == nullptr) {
        timerWorker = std::make_shared<TimerWorker>();
    }
}

void CloseGlobalTimer(void)
{
    if (timerWorker != nullptr) {
        timerWorker->Stop();
    }
    timerWorker.reset();
}

std::shared_ptr<YR::utility::Timer> ExecuteByGlobalTimer(std::function<void()> fn, size_t timeoutMilliSeconds,
                                                         int execTimes)
{
    if (execTimes == 0) {
        return nullptr;
    }
    if (timerWorker == nullptr) {
        YRLOG_ERROR("timerWorker is nullptr.");
        return nullptr;
    }
    return timerWorker->CreateTimer(timeoutMilliSeconds, execTimes, fn);
}

void ExecuteByGlobalTimer(std::function<void()> fn, size_t timeoutMilliSeconds, std::shared_ptr<YR::utility::Timer> t)
{
    if (timerWorker == nullptr) {
        YRLOG_ERROR("timerWorker is nullptr.");
        return;
    }
    timerWorker->ExecuteByTimer(t, timeoutMilliSeconds, fn);
}

void CancelGlobalTimer(std::shared_ptr<YR::utility::Timer> t)
{
    timerWorker->CancelTimer(t);
}
}  // namespace utility
}  // namespace YR