/*
 * Copyright (c) 2011-2020 Mellanox Technologies Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the Mellanox Technologies Ltd nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#define __STDC_LIMIT_MACROS // for INT64_MAX in __cplusplus

#include "ticks.h"

#include <string>
#include <stdio.h>
#include <errno.h>
#include <string.h> // strerror()
#include <stdint.h> // INT64_MAX
#include <math.h>

#include "clock.h"
#ifndef WIN32
#include <unistd.h> // for usleep
#endif
/**
 * RDTSC extensions
 */
#define TSCVAL_INITIALIZER (0)

/**
 * Read RDTSC register
 */

/**
 * Calibrate RDTSC with CPU speed
 * @return number of tsc ticks per second
 */
ticks_t get_tsc_rate_per_second() {
    static ticks_t tsc_per_second = TSCVAL_INITIALIZER;
    if (!tsc_per_second) {
        uint64_t delta_usec;
        timespec ts_before, ts_after, ts_delta;
        ticks_t tsc_before, tsc_after, tsc_delta;

        // Measure the time actually slept because usleep() is very inaccurate.
        os_ts_gettimeofclock(&ts_before);
        tsc_before = os_gettimeoftsc();
        usleep(100000); // 0.1 sec
        os_ts_gettimeofclock(&ts_after);
        tsc_after = os_gettimeoftsc();

        // Calc delta's
        tsc_delta = tsc_after - tsc_before;
        ts_sub(&ts_after, &ts_before, &ts_delta);
        delta_usec = ts_to_usec(&ts_delta);

        // Calc rate
        tsc_per_second = tsc_delta * USEC_PER_SEC / delta_usec;
    }
    return tsc_per_second;
}

// static variables initialization
const int64_t TicksImplRdtsc::TICKS_PER_SEC = get_tsc_rate_per_second();
const int64_t TicksImplRdtsc::TICKS_PER_MSEC = (TicksImplRdtsc::TICKS_PER_SEC + 500) / 1000;
const int64_t TicksImplRdtsc::MAX_MSEC_CONVERT =
    TICKS_PER_MSEC > NSEC_IN_MSEC ? INT64_MAX / TICKS_PER_MSEC : INT64_MAX / NSEC_IN_MSEC;
const ticks_t TicksImplRdtsc::BASE_TICKS = os_gettimeoftsc();

const TicksDuration TicksDuration::TICKS0(0, 0);        // call the non inline CTOR from slow path
const TicksDuration TicksDuration::TICKS1USEC(1000, 0); // call the non inline CTOR from slow path
const TicksDuration TicksDuration::TICKS1MSEC(1000 * 1000,
                                              0); // call the non inline CTOR from slow path
const TicksDuration TicksDuration::TICKS1SEC(1000 * 1000 * 1000,
                                             0); // call the non inline CTOR from slow path
const TicksDuration TicksDuration::TICKS1MIN(TICKS1SEC * 60);
const TicksDuration TicksDuration::TICKS1HOUR(TICKS1MIN * 60);
const TicksDuration TicksDuration::TICKS1DAY(TICKS1HOUR * 24);
const TicksDuration TicksDuration::TICKS1WEEK(TICKS1DAY * 7);

const TicksTime TicksTime::TICKS0;

TicksBase::Mode TicksBase::ms_mode = TicksBase::RDTSC;

//------------------------------------------------------------------------------
// provide non inline functions/CTORs for reducing code size outside fast path and for quieting the
// compiler
TicksBase::TicksBase(ticks_t _ticks, int) : m_ticks(_ticks) {}
ticks_t TicksBase::nsec2ticksNonInline(int64_t _val) {
    return ms_mode == RDTSC ? TicksImplRdtsc::nsec2ticks(_val) : TicksImplClock::nsec2ticks(_val);
}
TicksDuration::TicksDuration(int64_t _nsec, int) : TicksBase(nsec2ticks(_nsec), 0) {}
TicksTime &TicksTime::setNowNonInline() {
    m_ticks = getCurrentTicks();
    return *this;
}

//------------------------------------------------------------------------------
/*static*/ bool TicksBase::init(Mode _mode) { /* inited to rdtsc mode or clock mode */
    static bool isInited = false;
    if (isInited) return false;

    ms_mode = _mode;
    return true;
}

//==============================================================================
class TicksException : public std::exception {
public:
    TicksException(const char *_what) : m_what(_what) {}
    virtual ~TicksException() throw() {}
    virtual const char *what() const throw() { return m_what.c_str(); }
    const std::string m_what;
};

//------------------------------------------------------------------------------
/*static*/ void TicksImplClock::doThrow(const char *_func, const char *_file, int _line) {
    const size_t LEN = 256;
    char buf[LEN + 1];
    snprintf(buf, LEN, "%s:%d: %s() has failed:  (errno=%d %s)\n", _file, _line, _func, errno,
             strerror(errno));
    buf[LEN] = '\0';
    throw TicksException(buf);
}

//------------------------------------------------------------------------------
/*static*/ TicksDuration TicksDuration::stdDev(TicksDuration *pArr, size_t size) {
    if (size <= 1) return TICKS0;

    double sum = 0;
    double sum_sqr = 0;
    double ticks = 0;

    for (size_t i = 0; i < size; i++) {
        ticks = (double)pArr[i].m_ticks;
        sum += ticks;
        sum_sqr += ticks * ticks;
    }
    double avg = sum / size;
    double variance = (sum_sqr - size * avg * avg) / (size - 1);
    double stdDev = sqrt(variance);
    return TicksDuration(ticks_t(stdDev + 0.5), true);
}
