/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
 +----------------------------------------------------------------------+
 */

#include "swoole_api.h"
#include "swoole_reactor.h"
#include "swoole_timer.h"
#include "swoole_util.h"
#if !defined(HAVE_CLOCK_GETTIME) && defined(__MACH__)
#include <mach/clock.h>
#include <mach/mach_time.h>
#include <sys/sysctl.h>

#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)

static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;

int swoole_clock_gettime(int which_clock, struct timespec *t) {
    // be more careful in a multithreaded environement
    if (!orwl_timestart) {
        mach_timebase_info_data_t tb = {0};
        mach_timebase_info(&tb);
        orwl_timebase = tb.numer;
        orwl_timebase /= tb.denom;
        orwl_timestart = mach_absolute_time();
    }
    double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
    t->tv_sec = diff * ORWL_NANO;
    t->tv_nsec = diff - (t->tv_sec * ORWL_GIGA);
    return 0;
}
#endif

namespace swoole {

Timer::Timer() : heap(1024, Heap::MIN_HEAP) {
    _current_id = -1;
    next_msec_ = -1;
    _next_id = 1;
    round = 0;
    now(&base_time);
}

bool Timer::init() {
    if (now(&base_time) < 0) {
        return false;
    }
    if (SwooleTG.reactor) {
        return init_reactor(SwooleTG.reactor);
    } else {
        return init_system_timer();
    }
}

bool Timer::init_reactor(Reactor *reactor) {
    reactor_ = reactor;
    set = [](Timer *timer, long exec_msec) -> int {
        timer->reactor_->timeout_msec = exec_msec;
        return SW_OK;
    };
    close = [](Timer *timer) { timer->set(timer, -1); };

    reactor->set_end_callback(Reactor::PRIORITY_TIMER, [this](Reactor *) { select(); });

    reactor->set_exit_condition(Reactor::EXIT_CONDITION_TIMER,
                                [this](Reactor *reactor, int &event_num) -> bool { return count() == 0; });

    reactor->add_destroy_callback([](void *) {
        if (swoole_timer_is_available()) {
            swoole_timer_free();
        }
    });

    return true;
}

void Timer::reinit(Reactor *reactor) {
    init_reactor(reactor);
    reactor->timeout_msec = next_msec_;
}

Timer::~Timer() {
    if (close) {
        close(this);
    }
    for (auto iter = map.begin(); iter != map.end(); iter++) {
        auto tnode = iter->second;
        delete tnode;
    }
}

TimerNode *Timer::add(long _msec, bool persistent, void *data, const TimerCallback &callback) {
    if (sw_unlikely(_msec <= 0)) {
        swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "msec value[%ld] is invalid", _msec);
        return nullptr;
    }

    int64_t now_msec = get_relative_msec();
    if (sw_unlikely(now_msec < 0)) {
        return nullptr;
    }

    TimerNode *tnode = new TimerNode();
    tnode->data = data;
    tnode->type = TimerNode::TYPE_KERNEL;
    tnode->exec_msec = now_msec + _msec;
    tnode->interval = persistent ? _msec : 0;
    tnode->removed = false;
    tnode->callback = callback;
    tnode->round = round;
    tnode->destructor = nullptr;

    if (next_msec_ < 0 || next_msec_ > _msec) {
        set(this, _msec);
        next_msec_ = _msec;
    }

    tnode->id = _next_id++;
    if (sw_unlikely(tnode->id < 0)) {
        tnode->id = 1;
        _next_id = 2;
    }

    tnode->heap_node = heap.push(tnode->exec_msec, tnode);
    if (sw_unlikely(tnode->heap_node == nullptr)) {
        delete tnode;
        return nullptr;
    }
    map.emplace(std::make_pair(tnode->id, tnode));
    swTraceLog(SW_TRACE_TIMER,
               "id=%ld, exec_msec=%" PRId64 ", msec=%ld, round=%" PRIu64 ", exist=%u",
               tnode->id,
               tnode->exec_msec,
               _msec,
               tnode->round,
               count());
    return tnode;
}

bool Timer::remove(TimerNode *tnode) {
    if (sw_unlikely(!tnode || tnode->removed)) {
        return false;
    }
    if (sw_unlikely(_current_id > 0 && tnode->id == _current_id)) {
        tnode->removed = true;
        swTraceLog(SW_TRACE_TIMER,
                   "set-remove: id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%u",
                   tnode->id,
                   tnode->exec_msec,
                   tnode->round,
                   count());
        return true;
    }
    if (sw_unlikely(!map.erase(tnode->id))) {
        return false;
    }
    if (tnode->heap_node) {
        heap.remove(tnode->heap_node);
    }
    if (tnode->destructor) {
        tnode->destructor(tnode);
    }
    swTraceLog(SW_TRACE_TIMER,
               "id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%u",
               tnode->id,
               tnode->exec_msec,
               tnode->round,
               count());
    delete tnode;
    return true;
}

int Timer::select() {
    int64_t now_msec = get_relative_msec();
    if (sw_unlikely(now_msec < 0)) {
        return SW_ERR;
    }

    TimerNode *tnode = nullptr;
    HeapNode *tmp;

    swTraceLog(SW_TRACE_TIMER, "timer msec=%" PRId64 ", round=%" PRId64, now_msec, round);

    while ((tmp = heap.top())) {
        tnode = (TimerNode *) tmp->data;
        if (tnode->exec_msec > now_msec || tnode->round == round) {
            break;
        }

        _current_id = tnode->id;
        if (!tnode->removed) {
            swTraceLog(SW_TRACE_TIMER,
                       "id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%u",
                       tnode->id,
                       tnode->exec_msec,
                       tnode->round,
                       count() - 1);
            tnode->callback(this, tnode);
        }
        _current_id = -1;

        // persistent timer
        if (tnode->interval > 0 && !tnode->removed) {
            while (tnode->exec_msec <= now_msec) {
                tnode->exec_msec += tnode->interval;
            }
            heap.change_priority(tnode->exec_msec, tmp);
            continue;
        }

        heap.pop();
        map.erase(tnode->id);
        delete tnode;
    }

    if (!tnode || !tmp) {
        next_msec_ = -1;
        set(this, -1);
    } else {
        long next_msec = tnode->exec_msec - now_msec;
        if (next_msec <= 0) {
            next_msec = 1;
        }
        set(this, next_msec);
    }
    round++;

    return SW_OK;
}

#if defined(SW_USE_MONOTONIC_TIME) && defined(CLOCK_MONOTONIC)
int Timer::now(struct timeval *time) {
    struct timespec _now;
    if (clock_gettime(CLOCK_MONOTONIC, &_now) < 0) {
        swSysWarn("clock_gettime(CLOCK_MONOTONIC) failed");
        return SW_ERR;
    }
    time->tv_sec = _now.tv_sec;
    time->tv_usec = _now.tv_nsec / 1000;
#else
if (gettimeofday(time, nullptr) < 0) {
    swSysWarn("gettimeofday() failed");
    return SW_ERR;
}
#endif
    return SW_OK;
}  // namespace swoole

};  // namespace swoole
