#include "cputimer.h"

#include <util/system/defaults.h>
#include <util/system/hp_timer.h>
#include <util/string/printf.h>
#include <util/stream/output.h>
#include <util/generic/singleton.h>

#if defined(_unix_)
    #include <unistd.h>
    #include <sched.h>
#elif defined(_win_)
    #include <util/system/winint.h>
#endif

TTimer::TTimer(const TStringBuf message) {
    static const int SMALL_DURATION_CHAR_LENGTH = 9;                     // strlen("0.123456s")
    Message_.Reserve(message.length() + SMALL_DURATION_CHAR_LENGTH + 1); // +"\n"
    Message_ << message;
    // Do not measure the allocations above.
    Start_ = TInstant::Now();
}

TTimer::~TTimer() {
    const TDuration duration = TInstant::Now() - Start_;
    Message_ << duration << "\n";
    Cerr << Message_.Str();
}

static ui64 ManuallySetCyclesPerSecond = 0;

static ui64 GetCyclesPerSecond() {
    if (ManuallySetCyclesPerSecond != 0) {
        return ManuallySetCyclesPerSecond;
    } else {
        return NHPTimer::GetCyclesPerSecond();
    }
}

void SetCyclesPerSecond(ui64 cycles) {
    ManuallySetCyclesPerSecond = cycles;
}

ui64 GetCyclesPerMillisecond() {
    return GetCyclesPerSecond() / 1000;
}

TDuration CyclesToDuration(ui64 cycles) {
    return TDuration::MicroSeconds(cycles * 1000000 / GetCyclesPerSecond());
}

TDuration CyclesToDurationSafe(ui64 cycles)
{
    constexpr ui64 cyclesLimit = std::numeric_limits<ui64>::max() / 1000000;
    if (cycles <= cyclesLimit) {
        return CyclesToDuration(cycles);
    }
    return TDuration::MicroSeconds(cycles / GetCyclesPerSecond() * 1000000);
}

ui64 DurationToCycles(TDuration duration) {
    return duration.MicroSeconds() * GetCyclesPerSecond() / 1000000;
}

ui64 DurationToCyclesSafe(TDuration duration)
{
    if (duration.MicroSeconds() <= std::numeric_limits<ui64>::max() / GetCyclesPerSecond()) {
        return DurationToCycles(duration);
    }
    return duration.MicroSeconds() / 1000000 * GetCyclesPerSecond();
}

TPrecisionTimer::TPrecisionTimer()
    : Start(::GetCycleCount())
{
}

ui64 TPrecisionTimer::GetCycleCount() const {
    return ::GetCycleCount() - Start;
}

TString FormatCycles(ui64 cycles) {
    ui64 milliseconds = cycles / GetCyclesPerMillisecond();
    ui32 ms = ui32(milliseconds % 1000);
    milliseconds /= 1000;
    ui32 secs = ui32(milliseconds % 60);
    milliseconds /= 60;
    ui32 mins = ui32(milliseconds);
    TString result;
    sprintf(result, "%" PRIu32 " m %.2" PRIu32 " s %.3" PRIu32 " ms", mins, secs, ms);
    return result;
}

TFormattedPrecisionTimer::TFormattedPrecisionTimer(const char* message, IOutputStream* out)
    : Message(message)
    , Out(out)
{
    Start = GetCycleCount();
}

TFormattedPrecisionTimer::~TFormattedPrecisionTimer() {
    const ui64 end = GetCycleCount();
    const ui64 diff = end - Start;

    *Out << Message << ": " << diff << " ticks " << FormatCycles(diff) << Endl;
}

TFuncTimer::TFuncTimer(const char* func)
    : Start_(TInstant::Now())
    , Func_(func)
{
    Cerr << "enter " << Func_ << Endl;
}

TFuncTimer::~TFuncTimer() {
    Cerr << "leave " << Func_ << " -> " << (TInstant::Now() - Start_) << Endl;
}

TTimeLogger::TTimeLogger(const TString& message, bool verbose)
    : Message(message)
    , Verbose(verbose)
    , OK(false)
    , Begin(time(nullptr))
    , BeginCycles(GetCycleCount())
{
    if (Verbose) {
        fprintf(stderr, "=========================================================\n");
        fprintf(stderr, "%s started: %.24s (%lu) (%d)\n", Message.data(), ctime(&Begin), (unsigned long)Begin, (int)getpid());
    }
}

double TTimeLogger::ElapsedTime() const {
    return time(nullptr) - Begin;
}

void TTimeLogger::SetOK() {
    OK = true;
}

TTimeLogger::~TTimeLogger() {
    time_t tim = time(nullptr);
    ui64 endCycles = GetCycleCount();
    if (Verbose) {
        const char* prefix = (OK) ? "" : "!";
        fprintf(stderr, "%s%s ended: %.24s (%lu) (%d) (took %lus = %s)\n",
                prefix, Message.data(), ctime(&tim), (unsigned long)tim, (int)getpid(),
                (unsigned long)tim - (unsigned long)Begin, FormatCycles(endCycles - BeginCycles).data());
        fprintf(stderr, "%s=========================================================\n", prefix);
    }
}