/*
 * 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include "sched/workgroup_internal.h"
#include "dfx/log/ffrt_log_api.h"
#include "task_client_adapter.h"


#if (defined(QOS_WORKER_FRAME_RTG) || defined(QOS_FRAME_RTG))
constexpr int HWC_UID = 3039;
constexpr int ROOT_UID = 0;
constexpr int RS_RTG_ID = 10;

namespace ffrt {
static int wgId = -1;
static WorkGroup* rsWorkGroup = nullptr;
static int wgCount = 0;
static std::mutex wgLock;

#if (defined(QOS_WORKER_FRAME_RTG))

void WorkgroupInit(struct WorkGroup* wg, uint64_t interval, int rtgId, int qos)
{
    wg->started = false;
    wg->interval = interval;
    wg->rtgId = rtgId;
    wg->qos = qos;
    wgId = rtgId;

    for (int i = 0; i < MAX_WG_THREADS; i++) {
        wg->tids[i] = -1;
    }
}

int FindThreadInWorkGroup(WorkGroup *workGroup, int tid)
{
    if (workGroup == nullptr) {
        FFRT_SYSEVENT_LOGE("[RSWorkGroup] find thread %{public}d in workGroup failed, workGroup is null", tid);
        return -1;
    }
    for (int i = 0;i < MAX_WG_THREADS; i++) {
        if (workGroup->tids[i] == tid) {
            return i;
        }
    }
    return -1;
}

bool InsertThreadInWorkGroup(WorkGroup *workGroup, int tid)
{
    if (workGroup == nullptr) {
        FFRT_SYSEVENT_LOGE("[RSWorkGroup] join thread %{public}d into workGroup failed, workGroup is null", tid);
        return false;
    }
    for (int i = 0; i < MAX_WG_THREADS; i++) {
        if (workGroup->tids[i] == -1) {
            workGroup->tids[i] = tid;
            return true;
        }
    }
    return false;
}

WorkGroup* CreateRSWorkGroup(uint64_t interval, int qos)
{
    IntervalReply rs;
    rs.rtgId = -1;
    rs.tid = -1;
    {
        std::lock_guard<std::mutex> lck(wgLock);
        if (rsWorkGroup == nullptr) {
            CTC_QUERY_INTERVAL(QUERY_RENDER_SERVICE_RENDER, rs);
            if (rs.rtgId > 0) {
                rsWorkGroup = new (std::nothrow) struct WorkGroup();
                if (rsWorkGroup == nullptr) {
                    FFRT_SYSEVENT_LOGE("[RSWorkGroup] rsWorkGroup malloc failed!");
                    return nullptr;
                }
                WorkgroupInit(rsWorkGroup, interval, rs.rtgId, qos);
                wgCount++;
            }
        }
    }
    return rsWorkGroup;
}

bool LeaveRSWorkGroup(int tid, int qos)
{
    std::lock_guard<std::mutex> lck(wgLock);
    if (rsWorkGroup == nullptr || rsWorkGroup->qos != qos) {
        FFRT_LOGD("[RSWorkGroup] LeaveRSWorkGroup rsWorkGroup is null ,tid:%{public}d", tid);
        return false;
    }
    int existIndex = FindThreadInWorkGroup(rsWorkGroup, tid);
    if (existIndex != -1) {
        rsWorkGroup->tids[existIndex] = -1;
    }
    FFRT_LOGI("[RSWorkGroup] LeaveRSWorkGroup ,tid: %{public}d, existIndex: %{public}d", tid, existIndex);
    return true;
}

bool JoinRSWorkGroup(int tid, int qos)
{
    std::lock_guard<std::mutex> lck(wgLock);
    if (rsWorkGroup == nullptr || rsWorkGroup->qos != qos) {
        FFRT_LOGD("[RSWorkGroup] join thread %{public}d into RSWorkGroup failed; Create RSWorkGroup first",
            tid);
        return false;
    }
    int existIndex = FindThreadInWorkGroup(rsWorkGroup, tid);
    if (existIndex == -1) {
        IntervalReply rs;
        rs.rtgId = -1;
        rs.tid = tid;
        CTC_QUERY_INTERVAL(QUERY_RENDER_SERVICE, rs);
        if (rs.rtgId > 0) {
            bool success = InsertThreadInWorkGroup(rsWorkGroup, tid);
            if (!success) {
                return false;
            }
        }
    }
    FFRT_LOGI("[RSWorkGroup] update thread %{public}d success", tid);
    return true;
}

bool DestoryRSWorkGroup(int qos)
{
    std::lock_guard<std::mutex> lck(wgLock);
    if (rsWorkGroup != nullptr && rsWorkGroup->qos == qos) {
        delete rsWorkGroup;
        rsWorkGroup = nullptr;
        wgId = -1;
        return true;
    }
    return false;
}
#endif

#if defined(QOS_FRAME_RTG)
bool JoinWG(int tid, int qos)
{
    if (wgId < 0) {
        if (wgCount > 0) {
            FFRT_SYSEVENT_LOGE("[WorkGroup] interval is unavailable");
        }
        return false;
    }
    int uid = getuid();
    if (uid == RS_UID) {
        return JoinRSWorkGroup(tid, qos);
    }
    int addRet = AddThreadToRtgAdapter(tid, wgId, 0);
    if (addRet == 0) {
        FFRT_LOGI("[WorkGroup] update thread %{public}d success", tid);
    } else {
        FFRT_SYSEVENT_LOGE("[WorkGroup] update thread %{public}d failed, return %{public}d", tid, addRet);
    }
    return true;
}

bool LeaveWG(int tid, int qos)
{
    int uid = getuid();
    if (uid == RS_UID) {
        return LeaveRSWorkGroup(tid, qos);
    }
    return false;
}

struct WorkGroup* WorkgroupCreate(uint64_t interval, int qos)
{
    int rtgId = -1;
    int uid = getuid();
    int num = 0;
    
    if (uid == RS_UID) {
        CreateRSWorkGroup(interval);
        return rsWorkGroup;
    }

    if (rtgId < 0) {
        FFRT_SYSEVENT_LOGE("[WorkGroup] create rtg group %d failed", rtgId);
        return nullptr;
    }
    FFRT_LOGI("[WorkGroup] create rtg group %d success", rtgId);

    WorkGroup* wg = nullptr;
    wg = new (std::nothrow) struct WorkGroup();
    if (wg == nullptr) {
        FFRT_SYSEVENT_LOGE("[WorkGroup] workgroup malloc failed!");
        return nullptr;
    }
    WorkgroupInit(wg, interval, rtgId);
    {
        std::lock_guard<std::mutex> lck(wgLock);
        wgCount++;
    }
    return wg;
}

int WorkgroupClear(struct WorkGroup* wg)
{
    if (wg == nullptr) {
        FFRT_SYSEVENT_LOGE("[WorkGroup] input workgroup is null");
        return 0;
    }
    int uid = getuid();
    if (uid == RS_UID) {
        return DestoryRSWorkGroup(wg->qos);
    }
    int ret = -1;
    if (uid != RS_UID) {
        ret = DestroyRtgGrpAdapter(wg->rtgId);
        if (ret != 0) {
            FFRT_SYSEVENT_LOGE("[WorkGroup] destroy rtg group failed");
        } else {
            std::lock_guard<std::mutex> lck(wgLock);
            wgCount--;
        }
    }
    delete wg;
    wg = nullptr;
    return ret;
}

void WorkgroupStartInterval(struct WorkGroup* wg)
{
    if (wg == nullptr) {
        FFRT_SYSEVENT_LOGE("[WorkGroup] input workgroup is null");
        return;
    }

    if (wg->started) {
        FFRT_LOGW("[WorkGroup] already start");
        return;
    }

    if (BeginFrameFreqAdapter(0) == 0) {
        wg->started = true;
    } else {
        FFRT_LOGE("[WorkGroup] start rtg(%d) work interval failed", wg->rtgId);
    }
}

void WorkgroupStopInterval(struct WorkGroup* wg)
{
    if (wg == nullptr) {
        FFRT_LOGE("[WorkGroup] input workgroup is null");
        return;
    }

    if (!wg->started) {
        FFRT_LOGW("[WorkGroup] already stop");
        return;
    }

    int ret = EndFrameFreqAdapter(0);
    if (ret == 0) {
        wg->started = false;
    } else {
        FFRT_LOGE("[WorkGroup] stop rtg(%d) work interval failed", wg->rtgId);
    }
}

void WorkgroupJoin(struct WorkGroup* wg, int tid)
{
    if (wg == nullptr) {
        FFRT_LOGE("[WorkGroup] input workgroup is null");
        return;
    }
    int uid = getuid();
    FFRT_LOGI("[WorkGroup] %s uid = %d rtgid = %d", __func__, uid, wg->rtgId);
    if (uid == RS_UID) {
        IntervalReply rs;
        rs.tid = tid;
        CTC_QUERY_INTERVAL(QUERY_RENDER_SERVICE, rs);
        FFRT_LOGI("[WorkGroup] join thread %{public}ld", tid);
        return;
    }
    int addRet = AddThreadToRtgAdapter(tid, wg->rtgId, 0);
    if (addRet == 0) {
        FFRT_LOGI("[WorkGroup] join thread %{public}ld success", tid);
    } else {
        FFRT_LOGE("[WorkGroup] join fail with %{public}d threads for %{public}d", addRet, tid);
    }
}

#endif /* QOS_FRAME_RTG */
}

#endif