#include "KingSystem/System/VFR.h"
#include <mc/seadCoreInfo.h>

namespace ksys {

SEAD_SINGLETON_DISPOSER_IMPL(VFR)

VFR::VFR() {
    for (int i = 0; i < NumCores; ++i) {
        mRawDeltaFrames[i] = &mStorage[0].raw_delta_frames[i];
        mDeltaFrames[i] = &mStorage[0].delta_frames[i];
        mRawDeltaTimes[i] = &mStorage[0].raw_delta_times[i];
        mDeltaTimes[i] = &mStorage[0].delta_times[i];
        mIntervals[i] = &mIntervalA;
        mIntervalRatios[i] = &mIntervalRatioA;
    }
}

VFR::~VFR() {
    mTimeSpeedMultipliers.freeBuffer();
}

void VFR::setDelta(u32 core, f32 delta) {
    delta = sead::Mathf::clampMin(delta, 0.01f);
    *mRawDeltaFrames[core] = delta;
    *mDeltaFrames[core] = delta * *mIntervalRatios[core];
    *mRawDeltaTimes[core] = delta * mFrameTime;
    *mDeltaTimes[core] = *mDeltaFrames[core] * mFrameTime;
}

void VFR::setMinDelta(u32 core, const sead::BitFlag32& mask) {
    f32 min = 1.0;
    for (s32 i = 0; i < mTimeSpeedMultipliers.size(); ++i) {
        if (mask.isOnBit(i))
            min = sead::Mathf::min(min, mTimeSpeedMultipliers[i].value);
    }
    setDelta(core, min);
}

void VFR::resetTimeMultipliers() {
    for (auto& entry : mTimeSpeedMultipliers) {
        entry.value = 1.0;
        entry.target_value = 1.0;
        entry.is_custom = false;
    }
    setMinDelta();
}

void VFR::setMinDelta() {
    setMinDelta(0, mMask);

    for (s32 i = 0; i < NumCores; ++i) {
        mStorage[0].delta_frames[i] = mStorage[0].delta_frames[0];
        mStorage[0].raw_delta_times[i] = mStorage[0].raw_delta_times[0];
        mStorage[0].delta_times[i] = mStorage[0].delta_times[0];
        mStorage[0].raw_delta_frames[i] = mStorage[0].raw_delta_frames[0];
    }
}

void VFR::copyAtoB() {
    for (s32 i = 0; i < NumCores; ++i) {
        mStorage[1].raw_delta_frames[i] = mStorage[0].raw_delta_frames[i];
        mStorage[1].delta_frames[i] = mStorage[0].delta_frames[i];
        mStorage[1].raw_delta_times[i] = mStorage[0].raw_delta_times[i];
        mStorage[1].delta_times[i] = mStorage[0].delta_times[i];
        mIntervalB = mIntervalA;
        mIntervalRatioB = mIntervalRatioA;
    }
}

void VFR::init(u32 interval, int num_speed_multipliers, sead::Heap* heap, u32 mask) {
    mInterval = interval;
    mFrameRate = 60 / interval;
    mIntervalA = interval;
    mIntervalB = interval;
    mFrameTime = 1.0f / mFrameRate;
    mTimeSpeedMultipliers.allocBufferAssert(num_speed_multipliers, heap);
    mMask = mask;
    setMinDelta();
    copyAtoB();
}

void VFR::setIntervalOverride(u32 interval) {
    if (!mHasIntervalOverride) {
        mHasIntervalOverride = true;
        mIntervalOverride = interval;
    }
}

void VFR::clearIntervalOverride() {
    mHasIntervalOverride = false;
}

bool VFR::hasIntervalOverride() const {
    return mHasIntervalOverride;
}

u32 VFR::getIntervalOverride() const {
    return mIntervalOverride;
}

VFR::TimeSpeedMultiplier::TimeSpeedMultiplier() = default;

VFR::TimeSpeedMultiplier::~TimeSpeedMultiplier() = default;

void VFR::TimeSpeedMultiplier::update(f32 multiplier) {
    if (target_value == value)
        return;

    is_custom = target_value != 1.0;
    if (target_value < value) {
        value = target_value;
    } else {
        sead::Mathf::chase(&value, target_value, sead::Mathf::clampMin(value * multiplier, 0.01));
    }
}

void VFR::updateInterval(u32 new_interval) {
    copyAtoB();

    if (mHasIntervalOverride)
        new_interval = mIntervalOverride;

    if (new_interval != 0 && mIntervalA != new_interval) {
        mIntervalA = new_interval;
        mHasIntervalChanged = true;
        mNewIntervalRatio = f32(new_interval) * (1.0f / mInterval);
    } else {
        mHasIntervalChanged = false;
    }

    if (mNewIntervalRatio != mIntervalRatioA)
        mIntervalRatioA = mNewIntervalRatio;

    for (s32 i = 0; i < mTimeSpeedMultipliers.size(); ++i) {
        mTimeSpeedMultipliers[i].update(mIntervalRatioA);
    }

    setMinDelta();
}

void VFR::useBufferB() {
    const u32 core = sead::CoreInfo::getCurrentCoreId();
    mRawDeltaFrames[core] = &mStorage[1].raw_delta_frames[core];
    mDeltaFrames[core] = &mStorage[1].delta_frames[core];
    mRawDeltaTimes[core] = &mStorage[1].raw_delta_times[core];
    mDeltaTimes[core] = &mStorage[1].delta_times[core];
    mIntervals[core] = &mIntervalB;
    mIntervalRatios[core] = &mIntervalRatioB;
}

void VFR::useBufferA() {
    const u32 core = sead::CoreInfo::getCurrentCoreId();
    mRawDeltaFrames[core] = &mStorage[0].raw_delta_frames[core];
    mDeltaFrames[core] = &mStorage[0].delta_frames[core];
    mRawDeltaTimes[core] = &mStorage[0].raw_delta_times[core];
    mDeltaTimes[core] = &mStorage[0].delta_times[core];
    mIntervals[core] = &mIntervalA;
    mIntervalRatios[core] = &mIntervalRatioA;
}

f32 VFR::getDeltaAndSetMin(f32* raw_delta_frames, u32 include_mask, u32 exclude_mask) {
    const u32 core = sead::CoreInfo::getCurrentCoreId();
    *raw_delta_frames = *mRawDeltaFrames[core];
    const f32 delta = *mDeltaFrames[core];
    setMinDelta(core, (mMask.getDirect() | include_mask) & ~exclude_mask);
    return delta;
}

bool VFR::hasCustomTimeMultiplier() const {
    for (const auto& entry : mTimeSpeedMultipliers) {
        if (entry.value != 1.0)
            return true;
    }
    return false;
}

VFR::ScopedDeltaSetter::ScopedDeltaSetter() = default;

VFR::ScopedDeltaSetter::ScopedDeltaSetter(u32 include_mask, u32 exclude_mask)
    : ScopedDeltaSetter() {
    set(include_mask, exclude_mask);
}

void VFR::ScopedDeltaSetter::set(u32 include_mask, u32 exclude_mask) {
    auto* vfr = VFR::instance();
    if (!vfr)
        return;

    f32 raw_delta;
    const auto delta = vfr->getDeltaAndSetMin(&raw_delta, include_mask, exclude_mask);
    const auto time = vfr->getDeltaFrame();
    if (delta != time) {
        mPreviousDelta = raw_delta;
        if (delta > 0.0)
            mTimeRate = time / delta;
    }
}

VFR::ScopedDeltaSetter::~ScopedDeltaSetter() {
    if (VFR::instance() && mPreviousDelta > 0.0)
        VFR::instance()->setDelta(mPreviousDelta);
}

}  // namespace ksys
