/*
 * Copyright 2015 Rockchip Electronics Co. LTD
 *
 * 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.
 */

#define MODULE_TAG "mpp_time"

#include <errno.h>
#include <string.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>

#include "mpp_mem.h"
#include "mpp_time.h"
#include "mpp_debug.h"
#include "mpp_common.h"
#include "mpp_thread.h"

#if _WIN32
#include <sys/types.h>
#include <sys/timeb.h>

RK_S64 mpp_time()
{
    struct timeb tb;
    ftime(&tb);
    return ((RK_S64)tb.time * 1000 + (RK_S64)tb.millitm) * 1000;
}

#else
#include <time.h>

RK_S64 mpp_time()
{
    struct timespec time = {0, 0};
    clock_gettime(CLOCK_MONOTONIC, &time);
    return (RK_S64)time.tv_sec * 1000000 + (RK_S64)time.tv_nsec / 1000;
}

#endif

void mpp_time_diff(RK_S64 start, RK_S64 end, RK_S64 limit, const char *fmt)
{
    RK_S64 diff = end - start;
    if (diff >= limit)
        mpp_dbg(MPP_DBG_TIMING, "%s timing %lld us\n", fmt, diff);
}

typedef struct MppClockImpl_t {
    const char *check;
    char    name[16];
    RK_U32  enable;
    RK_S64  base;
    RK_S64  time;
    RK_S64  sum;
    RK_S64  count;
} MppClockImpl;

static const char *clock_name = "mpp_clock";

MPP_RET check_is_mpp_clock(void *clock)
{
    if (clock && ((MppClockImpl*)clock)->check == clock_name)
        return MPP_OK;

    mpp_err_f("pointer %p failed on check\n", clock);
    mpp_abort();
    return MPP_NOK;
}

MppClock mpp_clock_get(const char *name)
{
    MppClockImpl *impl = mpp_calloc(MppClockImpl, 1);
    if (impl) {
        impl->check = clock_name;
        snprintf(impl->name, sizeof(impl->name) - 1, name, NULL);
    } else
        mpp_err_f("malloc failed\n");

    return impl;
}

void mpp_clock_put(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return ;
    }

    mpp_free(clock);
}

void mpp_clock_enable(MppClock clock, RK_U32 enable)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
    } else {
        MppClockImpl *p = (MppClockImpl *)clock;
        p->enable = (enable) ? (1) : (0);
    }
}

RK_S64 mpp_clock_start(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return 0;
    }

    MppClockImpl *p = (MppClockImpl *)clock;

    if (!p->enable)
        return 0;

    p->base = mpp_time();
    p->time = 0;
    return p->base;
}

RK_S64 mpp_clock_pause(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return 0;
    }

    MppClockImpl *p = (MppClockImpl *)clock;

    if (!p->enable)
        return 0;

    RK_S64 time = mpp_time();

    if (!p->time) {
        // first pause after start
        p->sum += time - p->base;
        p->count++;
    }
    p->time = time;
    return p->time - p->base;
}

RK_S64 mpp_clock_reset(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
    } else {
        MppClockImpl *p = (MppClockImpl *)clock;

        p->base = 0;
        p->time = 0;
        p->sum = 0;
        p->count = 0;
    }

    return 0;
}

RK_S64 mpp_clock_get_sum(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return 0;
    }

    MppClockImpl *p = (MppClockImpl *)clock;
    return (p->enable) ? (p->sum) : (0);
}

RK_S64 mpp_clock_get_count(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return 0;
    }

    MppClockImpl *p = (MppClockImpl *)clock;
    return (p->enable) ? (p->count) : (0);
}

const char *mpp_clock_get_name(MppClock clock)
{
    if (NULL == clock || check_is_mpp_clock(clock)) {
        mpp_err_f("invalid clock %p\n", clock);
        return NULL;
    }

    MppClockImpl *p = (MppClockImpl *)clock;
    return p->name;
}

typedef struct MppTimerImpl_t {
    const char          *check;
    char                name[16];

    RK_S32              enabled;
    RK_S32              initial;
    RK_S32              interval;
    RK_S32              timer_fd;
    RK_S32              epoll_fd;

    MppThread           *thd;
    MppThreadFunc       func;
    void                *ctx;
} MppTimerImpl;

static const char *timer_name = "mpp_timer";

MPP_RET check_is_mpp_timer(void *timer)
{
    if (timer && ((MppTimerImpl*)timer)->check == timer_name)
        return MPP_OK;

    mpp_err_f("pointer %p failed on check\n", timer);
    mpp_abort();
    return MPP_NOK;
}

static void *mpp_timer_thread(void *ctx)
{
    struct itimerspec ts;
    RK_S32 ret = 0;
    MppTimerImpl *impl = (MppTimerImpl *)ctx;
    MppThread *thd = impl->thd;
    RK_S32 timer_fd = impl->timer_fd;

    // first expire time
    ts.it_value.tv_sec = impl->initial / 1000;
    ts.it_value.tv_nsec = (impl->initial % 1000) * 1000;

    // last expire time
    ts.it_interval.tv_sec = impl->interval / 1000;
    ts.it_interval.tv_nsec = (impl->interval % 1000) * 1000 * 1000;

    ret = timerfd_settime(timer_fd, 0, &ts, NULL);
    if (ret < 0) {
        mpp_err("timerfd_settime error, Error:[%d:%s]", errno, strerror(errno));
        return NULL;
    }

    while (1) {
        if (MPP_THREAD_RUNNING != thd->get_status())
            break;

        struct epoll_event events;

        memset(&events, 0, sizeof(events));

        /* wait epoll event */
        RK_S32 fd_cnt = epoll_wait(impl->epoll_fd, &events, 1, 500);
        if (fd_cnt && (events.events & EPOLLIN) && (events.data.fd == timer_fd)) {
            RK_U64 exp = 0;

            ssize_t cnt = read(timer_fd, &exp, sizeof(exp));
            mpp_assert(cnt == sizeof(exp));
            impl->func(impl->ctx);
        }
    }

    return NULL;
}

MppTimer mpp_timer_get(const char *name)
{
    RK_S32 timer_fd = -1;
    RK_S32 epoll_fd = -1;
    MppTimerImpl *impl = NULL;

    do {
        struct epoll_event event;

        impl = mpp_calloc(MppTimerImpl, 1);
        if (NULL == impl) {
            mpp_err_f("malloc failed\n");
            break;
        }

        timer_fd = timerfd_create(CLOCK_REALTIME, 0);
        if (timer_fd < 0)
            break;

        epoll_fd = epoll_create(1);
        if (epoll_fd < 0)
            break;

        memset(&event, 0, sizeof(event));
        event.data.fd = timer_fd;
        event.events = EPOLLIN | EPOLLET;

        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &event) < 0)
            break;

        impl->timer_fd = timer_fd;
        impl->epoll_fd = epoll_fd;
        /* default 1 second (1000ms) looper */
        impl->initial  = 1000;
        impl->interval = 1000;
        impl->check = timer_name;
        snprintf(impl->name, sizeof(impl->name) - 1, name, NULL);

        return impl;
    } while (0);

    mpp_err_f("failed to create timer\n");

    if (impl) {
        mpp_free(impl);
        impl = NULL;
    }

    if (timer_fd >= 0) {
        close(timer_fd);
        timer_fd = -1;
    }

    if (epoll_fd >= 0) {
        close(epoll_fd);
        epoll_fd = -1;
    }

    return NULL;
}

void mpp_timer_set_callback(MppTimer timer, MppThreadFunc func, void *ctx)
{
    if (NULL == timer || check_is_mpp_timer(timer)) {
        mpp_err_f("invalid timer %p\n", timer);
        return ;
    }

    if (NULL == func) {
        mpp_err_f("invalid NULL callback\n");
        return ;
    }

    MppTimerImpl *impl = (MppTimerImpl *)timer;
    impl->func = func;
    impl->ctx = ctx;
}

void mpp_timer_set_timing(MppTimer timer, RK_S32 initial, RK_S32 interval)
{
    if (NULL == timer || check_is_mpp_timer(timer)) {
        mpp_err_f("invalid timer %p\n", timer);
        return ;
    }

    MppTimerImpl *impl = (MppTimerImpl *)timer;
    impl->initial = initial;
    impl->interval = interval;
}

void mpp_timer_set_enable(MppTimer timer, RK_S32 enable)
{
    if (NULL == timer || check_is_mpp_timer(timer)) {
        mpp_err_f("invalid timer %p\n", timer);
        return ;
    }

    MppTimerImpl *impl = (MppTimerImpl *)timer;

    if (NULL == impl->func || impl->initial < 0 || impl->interval < 0) {
        mpp_err_f("invalid func %p initial %d interval %d\n",
                  impl->func, impl->initial, impl->interval);
        return ;
    }

    if (enable) {
        if (!impl->enabled && NULL == impl->thd) {
            MppThread *thd = new MppThread(mpp_timer_thread, impl, impl->name);
            if (thd) {
                impl->thd = thd;
                impl->enabled = 1;
                thd->start();
            }
        }
    } else {
        if (impl->enabled && impl->thd) {
            impl->thd->stop();
            impl->enabled = 0;
        }
    }
}

void mpp_timer_put(MppTimer timer)
{
    if (NULL == timer || check_is_mpp_timer(timer)) {
        mpp_err_f("invalid timer %p\n", timer);
        return ;
    }

    MppTimerImpl *impl = (MppTimerImpl *)timer;

    if (impl->enabled)
        mpp_timer_set_enable(timer, 0);

    if (impl->timer_fd >= 0) {
        close(impl->timer_fd);
        impl->timer_fd = -1;
    }

    if (impl->epoll_fd >= 0) {
        close(impl->epoll_fd);
        impl->epoll_fd = -1;
    }

    if (impl->thd) {
        delete impl->thd;
        impl->thd = NULL;
    }

    if (impl) {
        mpp_free(impl);
        impl = NULL;
    }
}

AutoTiming::AutoTiming(const char *name)
{
    mStart = mpp_time();
    mName = name;
}

AutoTiming::~AutoTiming()
{
    mEnd = mpp_time();
    mpp_log("%s timing %lld us\n", mName, mEnd - mStart);
}

#define STOPWATCH_TRACE_STR_LEN 64

typedef struct MppStopwatchNode_t {
    char                event[STOPWATCH_TRACE_STR_LEN];
    RK_S64              time;
} MppStopwatchNode;

typedef struct MppStopwatchImpl_t {
    const char          *check;
    char                name[STOPWATCH_TRACE_STR_LEN];

    RK_S32              max_count;
    RK_S32              filled_count;
    RK_S32              show_on_exit;
    RK_S32              log_len;
    RK_S64              time_elipsed;

    MppStopwatchNode    *nodes;
} MppStopwatchImpl;

static const char *stopwatch_name = "mpp_stopwatch";

MPP_RET check_is_mpp_stopwatch(void *stopwatch)
{
    if (stopwatch && ((MppStopwatchImpl*)stopwatch)->check == stopwatch_name)
        return MPP_OK;

    mpp_err_f("pointer %p failed on check\n", stopwatch);
    mpp_abort();
    return MPP_NOK;
}

MppStopwatch mpp_stopwatch_get(const char *name)
{
    MppStopwatchImpl *impl = mpp_calloc(MppStopwatchImpl, 1);
    MppStopwatchNode *nodes = mpp_calloc(MppStopwatchNode, 8);

    if (impl && nodes) {
        impl->check = stopwatch_name;
        snprintf(impl->name, sizeof(impl->name) - 1, name, NULL);
        impl->nodes = nodes;
        impl->max_count = 8;
    } else {
        mpp_err_f("malloc failed\n");
        MPP_FREE(impl);
        MPP_FREE(nodes);
    }

    return impl;
}

void mpp_stopwatch_set_show_on_exit(MppStopwatch stopwatch, RK_S32 show_on_exit)
{
    if (NULL == stopwatch || check_is_mpp_stopwatch(stopwatch)) {
        mpp_err_f("invalid stopwatch %p\n", stopwatch);
        return ;
    }

    MppStopwatchImpl *impl = (MppStopwatchImpl *)stopwatch;
    impl->show_on_exit = show_on_exit;
}

void mpp_stopwatch_record(MppStopwatch stopwatch, const char *event)
{
    /* do not print noisy log */
    if (NULL == stopwatch)
        return ;

    if (check_is_mpp_stopwatch(stopwatch)) {
        mpp_err_f("invalid stopwatch %p on %s\n", stopwatch, event);
        return ;
    }

    MppStopwatchImpl *impl = (MppStopwatchImpl *)stopwatch;
    if (impl->filled_count >= impl->max_count) {
        RK_S32 max_count = impl->max_count * 2;
        MppStopwatchNode *nodes = mpp_realloc(impl->nodes, MppStopwatchNode,
                                              max_count);
        if (nodes) {
            impl->nodes = nodes;
            impl->max_count = max_count;
        }
    }

    if (impl->filled_count < impl->max_count) {
        MppStopwatchNode *node = impl->nodes + impl->filled_count;

        node->time = mpp_time();
        if (event) {
            RK_S32 len = snprintf(node->event, sizeof(node->event) - 1,
                                  "%s", event);
            if (len > impl->log_len)
                impl->log_len = len;
        }
        impl->filled_count++;
    }
}

void mpp_stopwatch_put(MppStopwatch stopwatch)
{
    if (NULL == stopwatch || check_is_mpp_stopwatch(stopwatch)) {
        mpp_err_f("invalid stopwatch %p\n", stopwatch);
        return ;
    }

    MppStopwatchImpl *impl = (MppStopwatchImpl *)stopwatch;
    if (impl->show_on_exit && impl->nodes && impl->filled_count) {
        MppStopwatchNode *node = impl->nodes;
        RK_S64 last_time = node->time;
        RK_S32 i;
        char fmt[32];

        snprintf(fmt, sizeof(fmt) - 1, "%%s %%-%ds: %%6.2f\n", impl->log_len);
        node++;

        for (i = 1; i < impl->filled_count; i++) {
            mpp_log(fmt, impl->name, node->event,
                    (float)(node->time - last_time) / 1000);
            last_time = node->time;
            node++;
        }
    }
    MPP_FREE(impl->nodes);
    MPP_FREE(impl);
}

RK_S64 mpp_stopwatch_elapsed_time(MppStopwatch stopwatch)
{
    if (NULL == stopwatch || check_is_mpp_stopwatch(stopwatch)) {
        mpp_err_f("invalid stopwatch %p\n", stopwatch);
        return 0;
    }

    MppStopwatchImpl *impl = (MppStopwatchImpl *)stopwatch;
    if (impl->filled_count < 2)
        return 0;

    RK_S64 base_time = impl->nodes[0].time;
    RK_S64 curr_time = impl->nodes[impl->filled_count - 1].time;
    RK_S64 elapsed_time = curr_time - base_time;
    return elapsed_time;
}
