/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sched/interval.h"
#include "util/ffrt_facade.h"
#include "dfx/trace/ffrt_trace.h"

namespace ffrt {
void Deadline::Update(uint64_t deadlineUs)
{
    if (deadlineUs != ToUs()) {
        deadlineNs = deadlineUs < 1 ? 1 : deadlineUs * 1000;
    }

    absDeadlineNs = deadlineNs + AbsNowNs();

    FFRT_LOGI("Deadline %llu Update %llu Abs %llu", deadlineUs, deadlineNs, absDeadlineNs);
}

uint64_t Deadline::AbsNowNs()
{
    return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch())
        .count();
}

PerfCtrl::PerfCtrl(const QoS& qos) : qos(qos)
{
    if (qos == qos_inherit) {
        FFRT_SYSEVENT_LOGW("Invalid Thread Group");
        return;
    }

    tg = FFRTFacade::GetExecuteUnit().BindTG(this->qos);
}

PerfCtrl::~PerfCtrl()
{
    if (tg) {
        tg = nullptr;
        FFRTFacade::GetExecuteUnit().UnbindTG(qos);
    }
}

void PerfCtrl::Update(bool force)
{
    if (!force && predUtil == curUtil) {
        FFRT_LOGW("Predict Util Same as Current Util %llu", predUtil);
        return;
    }

    curUtil = predUtil;

    if (tg) {
        tg->UpdateUitl(curUtil);
    }
}

void PerfCtrl::Update(uint64_t deadlineNs, uint64_t load, bool force)
{
    if (deadlineNs == 0) {
        deadlineNs = 1;
    }
    predUtil = (load << SCHED_CAPACITY_SHIFT) / deadlineNs;
    if (predUtil > SCHED_MAX_CAPACITY) {
        FFRT_SYSEVENT_LOGW("Predict Util %llu Exceeds Max Capacity", predUtil);
        predUtil = SCHED_MAX_CAPACITY;
    }

    FFRT_LOGI("Update Load %llu, Deadline %llu, Util %llu\n", load, deadlineNs, predUtil);

    Update(force);
}

void IntervalLoadPredictor::UpdateTotalLoad(uint64_t load)
{
    totalLoad.UpdateLoad(load);
}

void IntervalLoadPredictor::UpdateCPLoad(uint64_t load)
{
    if (cpLoadIndex + 1 > cpLoad.size()) {
        cpLoad.resize(cpLoadIndex + 1);
    }

    cpLoad[cpLoadIndex++].UpdateLoad(load);
}

uint64_t IntervalLoadPredictor::GetTotalLoad()
{
    return totalLoad.GetPredictLoad();
}

uint64_t IntervalLoadPredictor::GetCPLoad()
{
    uint64_t load = cpLoad[cpLoadIndex].GetPredictLoad();
    if (load == 0) {
        return 0UL;
    }

    uint64_t predictLoad = totalLoad.GetPredictLoad();
    return (predictLoad < load) ? 0 : (predictLoad - load);
}

DefaultInterval::DefaultInterval(uint64_t deadlineUs, const QoS& qos) : Interval(deadlineUs, qos), lt(*this), ctrl(qos)
{
    ctrl.SetWindowSize(Ddl().ToNs());
}

DefaultInterval::~DefaultInterval()
{
    std::lock_guard lock(mutex);
    ctrl.Update(1, 0, true);
}

int DefaultInterval::Begin()
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalBegin);
    std::lock_guard lock(mutex);

    if (Enabled()) {
        FFRT_SYSEVENT_LOGE("interval already begin\n");
        return -1;
    }

    if (ctrl.isBusy()) {
        FFRT_SYSEVENT_LOGE("qos interval is busy, please retry later\n");
        return -1;
    }

    enabled = true;

    lt.Begin();

    ctrl.Update(Ddl().ToNs(), lp.GetTotalLoad(), true);
    lp.ResetCPIndex();

    return 0;
}

void DefaultInterval::Update(uint64_t deadlineUs)
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalUpdate);
    std::lock_guard lock(mutex);

    if (!Enabled()) {
        return;
    }

    Ddl().Update(deadlineUs);
    ctrl.SetWindowSize(Ddl().ToNs());
}

void DefaultInterval::End()
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalEnd);
    std::lock_guard lock(mutex);

    if (!Enabled()) {
        return;
    }

    enabled = false;

    lp.UpdateTotalLoad(lt.GetLoad());

    lt.End();
}

void DefaultInterval::CheckPoint()
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalCheckPoint);
    std::lock_guard lock(mutex);

    if (!Enabled()) {
        return;
    }

    ctrl.Update(Ddl().LeftNs(), lp.GetCPLoad());
    lp.UpdateCPLoad(lt.GetLoad());
}

void DefaultInterval::Join()
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalJoin);
    std::lock_guard lock(mutex);
    if (!ctrl.Join()) {
        FFRT_SYSEVENT_LOGE("Failed to Join Thread %d", ThreadGroup::GetTID());
    }
}

void DefaultInterval::Leave()
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalLeave);
    std::lock_guard lock(mutex);
    if (!ctrl.Leave()) {
        FFRT_SYSEVENT_LOGE("Failed to Leave Thread %d", ThreadGroup::GetTID());
    }
}

void DefaultInterval::UpdateTaskSwitch(TaskSwitchState state)
{
    FFRT_TRACE_SCOPE(TRACE_LEVEL1, IntervalUpdateTaskSwitch);
    std::lock_guard lock(mutex);

    switch (state) {
        case TaskSwitchState::BEGIN:
            ctrl.Update(true);
            break;
        case TaskSwitchState::UPDATE:
            ctrl.Update();
            break;
        case TaskSwitchState::END:
            ctrl.Clear();
            ctrl.Update(true);
            break;
        default:
            break;
    }

    lt.Record(state);
}
} // namespace ffrt