/*
 * Copyright 2023 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 "timerwheel.h"
#include <QTimer>

namespace timerwheel {

TimerWheel::TimerWheel(QObject *parent)
    : QObject(parent)
    , m_timer(new QTimer(this))
    , m_currentSlot(0)
    , m_currentTimerCount(0)
{
    connect(m_timer, &QTimer::timeout, this, &TimerWheel::tick);
}

quint64 TimerWheel::addTimer(int timeout,
                             AppData appData,
                             const std::function<void (AppData)> &callback)
{
    int ticks = 0;
    if (timeout <= kSi) {
        ticks = 1;
    } else {
        ticks = timeout / kSi;
    }

    int rotation = timeout / kSlotNumber;
    int tickSlot = (m_currentSlot + (ticks % kSlotNumber)) % kSlotNumber;

    Timer *timer = new Timer;
    timer->appData = appData;
    timer->callback = callback;
    timer->rotation = rotation;
    timer->tickSlot = tickSlot;

    m_slotTimers[tickSlot].push_back(timer);
    if (!m_timer->isActive()) {
        m_timer->start(kSi * 1000);
    }

    if (m_currentTimerCount == UINT64_MAX) {
        m_currentTimerCount = 1;
    } else {
        ++m_currentTimerCount;
    }
    timer->timerId = m_currentTimerCount;

    return m_currentTimerCount;
}

void TimerWheel::tick()
{
    if (!m_slotTimers.contains(m_currentSlot)) {
        m_currentSlot = ++m_currentSlot % kSlotNumber;
        return;
    }

    auto &timers = m_slotTimers[m_currentSlot];
    for (auto &timer : timers) {
        if (timer->rotation > 0) {
            -- timer->rotation;
        } else {
            timer->callback(timer->appData);
            timers.removeOne(timer);
        }
    }

    m_currentSlot = ++m_currentSlot % kSlotNumber;
}

bool TimerWheel::deleteTimer(quint64 timerId)
{
    auto it = m_slotTimers.begin();
    while (it != m_slotTimers.end()) {
        for (auto &timer : *it) {
            if (timer->timerId == timerId) {
                it->removeOne(timer);
                return true;
            }
        }
        ++ it;
    }
    return false;
}

} // namspace timerwheel


