/*
 *          Copyright Andrey Semashev 2007 - 2015.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE_1_0.txt or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
/*!
 * \file   timestamp.cpp
 * \author Andrey Semashev
 * \date   31.07.2011
 *
 * \brief  This header is the Boost.Log library implementation, see the library documentation
 *         at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
 */

#include <boost/log/detail/timestamp.hpp>

#if defined(BOOST_WINDOWS) && !defined(__CYGWIN__)
#include <boost/detail/interlocked.hpp>
#include "windows_version.hpp"
#include <windows.h>
#else
#include <unistd.h> // for config macros
#if defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)
#include <mach/mach_time.h>
#include <mach/kern_return.h>
#include <boost/log/utility/once_block.hpp>
#endif
#include <time.h>
#include <errno.h>
#include <boost/throw_exception.hpp>
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#endif
#include <boost/log/detail/header.hpp>

namespace boost {

BOOST_LOG_OPEN_NAMESPACE

namespace aux {

#if defined(BOOST_WINDOWS) && !defined(__CYGWIN__)

#if _WIN32_WINNT >= 0x0600

// Directly use API from Vista and later
BOOST_LOG_API get_tick_count_t get_tick_count = &GetTickCount64;

#else // _WIN32_WINNT >= 0x0600

BOOST_LOG_ANONYMOUS_NAMESPACE {

#if defined(_MSC_VER) && !defined(_M_CEE_PURE)

#   if defined(_M_IX86)
#       if defined(_M_IX86_FP) && _M_IX86_FP >= 2
//! Atomically loads and stores the 64-bit value through SSE2 instructions
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    __asm
    {
        mov eax, from
        mov edx, to
        movq xmm4, qword ptr [eax]
        movq qword ptr [edx], xmm4
    };
}
#       else // defined(_M_IX86_FP) && _M_IX86_FP >= 2
//! Atomically loads and stores the 64-bit value through FPU instructions
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    __asm
    {
        mov eax, from
        mov edx, to
        fild qword ptr [eax]
        fistp qword ptr [edx]
    };
}
#       endif // defined(_M_IX86_FP) && _M_IX86_FP >= 2
#   elif defined(_M_AMD64) || defined(_M_IA64)
//! Atomically loads and stores the 64-bit value
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    *to = *from;
}
#   else
#       define BOOST_LOG_GENERIC_MOVE64 1
#   endif

#elif defined(__GNUC__)

#   if defined(__i386__)
#       if defined(__SSE2__)
//! Atomically loads and stores the 64-bit value through SSE2 instructions
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    __asm__ __volatile__
    (
        "movq %1, %%xmm4\n\t"
        "movq %%xmm4, %0\n\t"
            : "=m" (*to)
            : "m" (*from)
            : "memory", "xmm4"
    );
}
#       else // defined(__SSE2__)
//! Atomically loads and stores the 64-bit value through FPU instructions
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    __asm__ __volatile__
    (
        "fildq %1\n\t"
        "fistpq %0"
            : "=m" (*to)
            : "m" (*from)
            : "memory"
    );
}
#       endif // defined(__SSE2__)
#   elif defined(__x86_64__)
//! Atomically loads and stores the 64-bit value
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    *to = *from;
}
#   else
#       define BOOST_LOG_GENERIC_MOVE64 1
#   endif

#else

#   define BOOST_LOG_GENERIC_MOVE64 1

#endif

#if defined(BOOST_LOG_GENERIC_MOVE64)

BOOST_ALIGNMENT(16) long g_spin_lock = 0;

//! Atomically loads and stores the 64-bit value
BOOST_FORCEINLINE void move64(const uint64_t* from, uint64_t* to)
{
    while (BOOST_INTERLOCKED_COMPARE_EXCHANGE(&g_spin_lock, 1, 0) != 0);
    *to = *from;
    BOOST_INTERLOCKED_EXCHANGE(&g_spin_lock, 0);
}

#endif // defined(BOOST_LOG_GENERIC_MOVE64)

BOOST_ALIGNMENT(16) uint64_t g_ticks = 0;

union ticks_caster
{
    uint64_t as_uint64;
    struct
    {
        uint32_t ticks;
        uint32_t counter;
    }
    as_components;
};

//! Artifical implementation of GetTickCount64
uint64_t __stdcall get_tick_count64()
{
    ticks_caster state;
    move64(&g_ticks, &state.as_uint64);

    uint32_t new_ticks = GetTickCount();

    state.as_components.counter += new_ticks < state.as_components.ticks;
    state.as_components.ticks = new_ticks;

    move64(&state.as_uint64, &g_ticks);
    return state.as_uint64;
}

uint64_t __stdcall get_tick_count_init()
{
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    if (hKernel32)
    {
        get_tick_count_t p = (get_tick_count_t)GetProcAddress(hKernel32, "GetTickCount64");
        if (p)
        {
            // Use native API
            get_tick_count = p;
            return p();
        }
    }

    // No native API available
    get_tick_count = &get_tick_count64;
    return get_tick_count64();
}

} // namespace

BOOST_LOG_API get_tick_count_t get_tick_count = &get_tick_count_init;

#endif // _WIN32_WINNT >= 0x0600

#elif (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0)  /* POSIX timers supported */ \
      || defined(__GNU__)  /* GNU Hurd does not support POSIX timers fully but does provide clock_gettime() */

BOOST_LOG_API int64_t duration::milliseconds() const
{
    // Timestamps are always in nanoseconds
    return m_ticks / 1000000LL;
}

BOOST_LOG_ANONYMOUS_NAMESPACE {

/*!
 * \c get_timestamp implementation based on POSIX realtime clock.
 * Note that this implementation is only used as a last resort since
 * this timer can be manually set and may jump due to DST change.
 */
timestamp get_timestamp_realtime_clock()
{
    timespec ts;
    if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
    {
        const int err = errno;
        BOOST_THROW_EXCEPTION(boost::system::system_error(
            err, boost::system::system_category(), "Failed to acquire current time"));
    }

    return timestamp(static_cast< uint64_t >(ts.tv_sec) * 1000000000ULL + ts.tv_nsec);
}

#   if defined(_POSIX_MONOTONIC_CLOCK)

//! \c get_timestamp implementation based on POSIX monotonic clock
timestamp get_timestamp_monotonic_clock()
{
    timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
    {
        const int err = errno;
        if (err == EINVAL)
        {
            // The current platform does not support monotonic timer.
            // Fall back to realtime clock, which is not exactly what we need
            // but is better than nothing.
            get_timestamp = &get_timestamp_realtime_clock;
            return get_timestamp_realtime_clock();
        }
        BOOST_THROW_EXCEPTION(boost::system::system_error(
            err, boost::system::system_category(), "Failed to acquire current time"));
    }

    return timestamp(static_cast< uint64_t >(ts.tv_sec) * 1000000000ULL + ts.tv_nsec);
}

#       define BOOST_LOG_DEFAULT_GET_TIMESTAMP get_timestamp_monotonic_clock

#   else // if defined(_POSIX_MONOTONIC_CLOCK)
#       define BOOST_LOG_DEFAULT_GET_TIMESTAMP get_timestamp_realtime_clock
#   endif // if defined(_POSIX_MONOTONIC_CLOCK)

} // namespace

// Use POSIX API
BOOST_LOG_API get_timestamp_t get_timestamp = &BOOST_LOG_DEFAULT_GET_TIMESTAMP;

#   undef BOOST_LOG_DEFAULT_GET_TIMESTAMP

#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)

BOOST_LOG_API int64_t duration::milliseconds() const
{
    static mach_timebase_info_data_t timebase_info = {};
    BOOST_LOG_ONCE_BLOCK()
    {
        kern_return_t err = mach_timebase_info(&timebase_info);
        if (err != KERN_SUCCESS)
        {
            BOOST_THROW_EXCEPTION(boost::system::system_error(
                err, boost::system::system_category(), "Failed to initialize timebase info"));
        }
    }

    // Often the timebase rational equals 1, we can optimize for this case
    if (timebase_info.numer == timebase_info.denom)
    {
        // Timestamps are in nanoseconds
        return m_ticks / 1000000LL;
    }
    else
    {
        return (m_ticks * timebase_info.numer) / (1000000LL * timebase_info.denom);
    }
}

BOOST_LOG_ANONYMOUS_NAMESPACE {

//! \c get_timestamp implementation based on MacOS X absolute time
timestamp get_timestamp_mach()
{
    return timestamp(mach_absolute_time());
}

} // namespace

// Use MacOS X API
BOOST_LOG_API get_timestamp_t get_timestamp = &get_timestamp_mach;

#else

#   error Boost.Log: Timestamp generation is not supported for your platform

#endif

} // namespace aux

BOOST_LOG_CLOSE_NAMESPACE // namespace log

} // namespace boost

#include <boost/log/detail/footer.hpp>
