/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */
#include "kernel_version_adapt.h"
#include "ka_task_pub.h"
#include "ka_system_pub.h"
#include "securec.h"
#include "ka_fs_pub.h"
#include "ka_compiler_pub.h"

#include "queue_module.h"
#include "queue_proc_fs.h"

#define PROC_FS_NAME_LEN 32
#define PROC_FS_R_MODE 0444
#define PROC_FS_RW_MODE 0644

static ka_proc_dir_entry_t *queue_top_entry = NULL;
static ka_proc_dir_entry_t *queue_process_entry = NULL;

static int queue_normal_status_show(ka_seq_file_t *seq, void *offset)
{
#ifndef EMU_ST
    struct queue_qid_status *status = (struct queue_qid_status *)seq->private;

    queue_show_one_qid_status(seq, status);
    return 0;
#endif
}

static int queue_normal_status_open(ka_inode_t *inode, ka_file_t *file)
{
    return ka_single_open(inode, file, queue_normal_status_show);
}

STATIC_PROCFS_FILE_FUNC_OPS_OPEN(normal_status_ops, queue_normal_status_open);

static int queue_abnormal_status_show(ka_seq_file_t *seq, void *offset)
{
    ka_fs_seq_printf(seq, "abnormal_queue_status_show\n");
    queue_show_all_qid_status(seq, RECORD_EXCEPT);

    return 0;
}

static int queue_abnormal_status_open(ka_inode_t *inode, ka_file_t *file)
{
    return ka_single_open(inode, file, queue_abnormal_status_show);
}

STATIC_PROCFS_FILE_FUNC_OPS_OPEN(abnormal_status_ops, queue_abnormal_status_open);

#ifndef EMU_ST
static int queue_perf_switch_show(ka_seq_file_t *seq, void *offset)
{
    ka_fs_seq_printf(seq, "perf_queue_switch_show\n");
    queue_show_perf_switch(seq);

    return 0;
}

static int queue_perf_switch_open(ka_inode_t *inode, ka_file_t *file)
{
    return ka_single_open(inode, file, queue_perf_switch_show);
}

#define PERF_SWITCH_MAX_LEN 10
static ssize_t queue_perf_switch_write(ka_file_t *file, const char __ka_user *buffer, size_t count, loff_t *ppos)
{
    char kstr[PERF_SWITCH_MAX_LEN] = {0};
    char *time_threshold_ptr = NULL;
    char *perf_switch_ptr = NULL;
    u32 time_threshold = 0;
    bool set_perf_switch = false;
    int ret;

    if (ka_task_get_current()->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
        queue_err("No permission to set perf switch.\n");
        return -EINVAL;
    }

    if ((count == 0) || (count > PERF_SWITCH_MAX_LEN)) {
        queue_err("Input count out of range. (count=%lu; max=%u)\n", count, PERF_SWITCH_MAX_LEN);
        return -EINVAL;
    }

    if (ka_base_copy_from_user(kstr, (void *)buffer, count)) {
        queue_err("Copy from user fail.\n");
        return -EINVAL;
    }
    kstr[count - 1] = '\0';

    perf_switch_ptr = strtok_s(kstr, " ", &time_threshold_ptr);
    if (perf_switch_ptr != NULL) {
        ret = ka_base_kstrtobool(perf_switch_ptr, &set_perf_switch);
        if (ret) {
            queue_err("Kstr to bool failed. (ret=%d)\n", ret);
            return -EINVAL;
        }
        ret = ka_base_kstrtouint(time_threshold_ptr, 10, &time_threshold);
        if (ret != 0) {
            time_threshold = 0;
        }
    }

    if (set_perf_switch == false) {
        queue_free_one_type_qid_status(RECORD_PERF);
        time_threshold = 0;
    }
    queue_set_perf_switch(set_perf_switch, time_threshold);

    return (ssize_t)count;
}

STATIC_PROCFS_FILE_FUNC_OPS_WRITE(perf_switch_ops, queue_perf_switch_open, queue_perf_switch_write);

static int queue_perf_status_show(ka_seq_file_t *seq, void *offset)
{
    ka_fs_seq_printf(seq, "perf_queue_status_show\n");
    queue_show_all_qid_status(seq, RECORD_PERF);

    return 0;
}

static int queue_perf_status_open(ka_inode_t *inode, ka_file_t *file)
{
    return ka_single_open(inode, file, queue_perf_status_show);
}

STATIC_PROCFS_FILE_FUNC_OPS_OPEN(perf_status_ops, queue_perf_status_open);
#endif

static void proc_fs_format_qid_dir_name(u32 qid, char *name, u32 len)
{
    if (sprintf_s(name, len, "qid-%u", qid) <= 0) {
        queue_info("Qid sprintf_s unsuccessful. (qid=%u)\n", qid);
    }
}

static ka_proc_dir_entry_t *proc_fs_mk_qid_dir(u32 qid, ka_proc_dir_entry_t *parent)
{
    char name[PROC_FS_NAME_LEN];

    if (parent == NULL) {
        return NULL;
    }
    proc_fs_format_qid_dir_name(qid, name, PROC_FS_NAME_LEN);
    return ka_fs_proc_mkdir((const char *)name, parent);
}

void queue_proc_fs_add_qid(struct queue_qid_status *status, ka_proc_dir_entry_t *parent)
{
    ka_proc_dir_entry_t *entry = NULL;

    if (status == NULL) {
        return;
    }

    if (ka_base_atomic_cmpxchg(&status->qid_dir_exit, QID_DIR_NO_EXIT, QID_DIR_IS_EXIT) != QID_DIR_NO_EXIT) {
        return;
    }

    entry = proc_fs_mk_qid_dir(status->qid, parent);
    if (entry == NULL) {
        ka_base_atomic_set(&status->qid_dir_exit, QID_DIR_NO_EXIT);
        queue_info("Create qid entry dir unsuccessful. (qid=%u)\n", status->qid);
        return;
    }

    (void)ka_fs_proc_create_data("queue_status", PROC_FS_R_MODE, entry, &normal_status_ops, status);
}

#ifndef EMU_ST
static void proc_fs_rm_qid_dir(u32 qid, ka_proc_dir_entry_t *parent)
{
    char name[PROC_FS_NAME_LEN];

    if (parent == NULL) {
        return;
    }

    proc_fs_format_qid_dir_name(qid, name, PROC_FS_NAME_LEN);
    (void)ka_fs_remove_proc_subtree((const char *)name, parent);
}

void queue_proc_fs_del_qid(struct queue_qid_status *status, ka_proc_dir_entry_t *parent)
{
    proc_fs_rm_qid_dir(status->qid, parent);
    ka_base_atomic_set(&status->qid_dir_exit, QID_DIR_NO_EXIT);
}
#endif
static void proc_fs_format_process_dir_name(ka_pid_t pid, char *name, u32 len)
{
    if (sprintf_s(name, len, "%d", pid) <= 0) {
        queue_info("Pid sprintf_s unsuccessful. (pid=%d)\n", pid);
    }
}

static ka_proc_dir_entry_t *proc_fs_mk_process_dir(ka_pid_t pid, ka_proc_dir_entry_t *parent)
{
    char name[PROC_FS_NAME_LEN];

    if (parent == NULL) {
        return NULL;
    }
    proc_fs_format_process_dir_name(pid, name, PROC_FS_NAME_LEN);
    return ka_fs_proc_mkdir((const char *)name, parent);
}

static void proc_fs_rm_process_dir(ka_pid_t pid, ka_proc_dir_entry_t *parent)
{
    char name[PROC_FS_NAME_LEN];

    if (parent == NULL) {
        return;
    }

    proc_fs_format_process_dir_name(pid, name, PROC_FS_NAME_LEN);
    (void)ka_fs_remove_proc_subtree((const char *)name, parent);
}

void queue_proc_fs_add_process(struct queue_context *ctx)
{
    ctx->entry = proc_fs_mk_process_dir(ctx->pid, queue_process_entry);
    if (ctx->entry == NULL) {
        queue_info("Create process entry dir unsuccessful. (pid=%u)\n", ctx->pid);
        return;
    }
}

void queue_proc_fs_del_process(struct queue_context *ctx)
{
    proc_fs_rm_process_dir(ctx->pid, queue_process_entry);
}

void queue_proc_fs_init(void)
{
    ka_proc_dir_entry_t *abnormal_entry = NULL;
    ka_proc_dir_entry_t *perf_entry = NULL;

    queue_status_record_mng_init();
    queue_top_entry = ka_fs_proc_mkdir("queue", NULL);
    if (queue_top_entry != NULL) {
        queue_process_entry = ka_fs_proc_mkdir("process", queue_top_entry);
        abnormal_entry = ka_fs_proc_mkdir("except_collect", queue_top_entry);
        if (abnormal_entry != NULL) {
            (void)ka_fs_proc_create_data("except_queue_status", PROC_FS_R_MODE, abnormal_entry, &abnormal_status_ops, NULL);
        }
        perf_entry = ka_fs_proc_mkdir("perf_collect", queue_top_entry);
        if (perf_entry != NULL) {
#ifndef EMU_ST
            (void)ka_fs_proc_create_data("perf_switch", PROC_FS_RW_MODE, perf_entry, &perf_switch_ops, NULL);
            (void)ka_fs_proc_create_data("perf_queue_status", PROC_FS_R_MODE, perf_entry, &perf_status_ops, NULL);
#endif
        }
    }
}

void queue_proc_fs_uninit(void)
{
    if (queue_top_entry != NULL) {
        (void)ka_fs_remove_proc_subtree("queue", NULL);
    }
    queue_free_all_type_qid_status();
}