#include "tile/base/chrono.h"

#include "tile/base/internal/utility.h"
#include "tile/base/logging.h"
#include "tile/base/make_unique.h"

#include <sys/select.h>
#include <sys/time.h>

#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#endif

namespace tile {
namespace {

template<typename T>
struct Epoch {};

template<>
struct Epoch<std::chrono::steady_clock> {
    static constexpr auto value = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(0));
};

template<>
struct Epoch<std::chrono::system_clock> {
    static constexpr auto value =
        std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1716543273));
};

template<typename Clock>
inline typename Clock::time_point
ReadClock(clockid_t type)
{
    timespec ts;
    clock_gettime(type, &ts);
    const long long ns = ts.tv_sec * 1000LL * 1000LL * 1000LL + ts.tv_nsec;
    auto duration      = std::chrono::duration_cast<typename Clock::duration>(std::chrono::nanoseconds(ns));
    return typename Clock::time_point(duration);
}

void
Sleep(long ns)
{
    struct timeval timeout;
    timeout.tv_sec  = 0;
    timeout.tv_usec = (ns + 999) / 1000;

    select(0, NULL, NULL, NULL, &timeout);
}

}// namespace

namespace detail {
namespace chrono {
struct alignas(hardware_destructive_interference_size) AsynchronouslyUpdatedTimestamps {
    std::atomic<std::chrono::nanoseconds> steady_clock_time_since_epoch;
    std::atomic<std::chrono::nanoseconds> system_clock_time_since_epoch;
};

static AsynchronouslyUpdatedTimestamps async_updated_timestamps;

void
UpdateCoarseTimestamps()
{
    async_updated_timestamps.steady_clock_time_since_epoch.store(
        ReadSteadyClock().time_since_epoch(),
        // std::chrono::steady_clock::now().time_since_epoch(),
        std::memory_order_relaxed);
    async_updated_timestamps.system_clock_time_since_epoch.store(
        ReadSystemClock().time_since_epoch(),
        // std::chrono::system_clock::now().time_since_epoch(),
        std::memory_order_relaxed);
}

constexpr std::chrono::microseconds CoarseClockInitializer::kAccuracy;

CoarseClockInitializer *
CoarseClockInitializer::Instance()
{
    static NeverDestroyedSingleton<CoarseClockInitializer> cci;
    return cci.Get();
}

void
CoarseClockInitializer::Start()
{
    if (!internal::TestAndSet(running_, false, true)) {
        TILE_LOG_WARNING("CoarseClockInitializer is already running");
        return;
    }

    UpdateCoarseTimestamps();

    worker_ = make_unique<std::thread>([this] {
        const auto accuracy_as_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(kAccuracy).count();
        TILE_CHECK_GE(accuracy_as_ns, 2000, "accuracy is too small, MUST GE 2000ns, current is {}", kAccuracy);

        while (running_.load(std::memory_order_relaxed)) {
            // std::this_thread::sleep_for(std::chrono::nanoseconds(500));
            UpdateCoarseTimestamps();
            // Sleep(accuracy_as_ns / 2);
            std::this_thread::sleep_for(kAccuracy / 2);
        }
    });
}

void
CoarseClockInitializer::Stop()
{
    running_.store(false, std::memory_order_relaxed);
}

void
CoarseClockInitializer::Join()
{
    if (worker_) {
        worker_->join();
        worker_.reset();
    }
}

CoarseClockInitializer::CoarseClockInitializer() {}

CoarseClockInitializer::~CoarseClockInitializer() {}

}// namespace chrono
}// namespace detail

std::chrono::steady_clock::time_point
ReadSteadyClock()
{
    // return ReadClock<std::chrono::steady_clock>(CLOCK_MONOTONIC);
    return std::chrono::steady_clock::now();
}

std::chrono::system_clock::time_point
ReadSystemClock()
{
    // return ReadClock<std::chrono::system_clock>(CLOCK_REALTIME);
    return std::chrono::system_clock::now();
}

std::chrono::steady_clock::time_point
ReadCoarseSteadyClock()
{
    auto coarse_duration =
        detail::chrono::async_updated_timestamps.steady_clock_time_since_epoch.load(std::memory_order_relaxed);
    auto target_duration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(coarse_duration);
    return std::chrono::steady_clock::time_point(target_duration);
}

std::chrono::system_clock::time_point
ReadCoarseSystemClock()
{
    auto coarse_duration =
        detail::chrono::async_updated_timestamps.system_clock_time_since_epoch.load(std::memory_order_relaxed);

    auto target_duration = std::chrono::duration_cast<std::chrono::system_clock::duration>(coarse_duration);

    return std::chrono::system_clock::time_point(target_duration);
}
}// namespace tile
