/*========================== begin_copyright_notice ============================
Copyright (C) 2021-2025 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

/*!
 * @file Utility for filtering profiled kernel dispatches per enqueue, draw, device, commandlist, PSO
 */

#include "api/gt_basic_defs.h"
#include "api/gt_knob.h"
#include "kernel_exec_config.h"
#include <string>

namespace gtpin
{

/* ===================================================================================================== */
// Configuration options
/* ===================================================================================================== */
KnobVector<std::string> knobEnqueue("enqueue", {}, "Enqueues and range of enqueues to profile");
KnobVector<int>         knobDraw(   "draw",    {}, "Draws to profile");
KnobVector<int>         knobDevice( "device",  {}, "Devices to profile");
KnobVector<int>         knobCl(     "cl",      {}, "Command lists to profile");
KnobVector<int>         knobPso(    "pso",     {}, "PSO IDs to profile");
KnobVector<int>         knobFrame(  "frame",   {}, "Frame IDs to profile");

KnobVector<std::string> knobSkipEnqueue("skip_enqueue", {}, "Enqueues to skip (don't profile)");
KnobVector<int>         knobSkipDraw(   "skip_draw",    {}, "Draws to skip (don't profile)");
KnobVector<int>         knobSkipDevice( "skip_device",  {}, "Devices to skip (don't profile)");
KnobVector<int>         knobSkipCl(     "skip_cl",      {}, "Command lists to skip (don't profile)");
KnobVector<int>         knobSkipPso(    "skip_pso",     {}, "PSO IDs to skip (don't profile)");
KnobVector<int>         knobSkipFrame(  "skip_frame",   {}, "Frame IDs to skip (don't profile)");

/* ===================================================================================================== */
// KernelExecConfig implementation
/* ===================================================================================================== */
KernelExecConfig& KernelExecConfig::Instance()
{
    static KernelExecConfig instance;
    return instance;
}

KernelExecConfig::KernelExecConfig() : isEmptyFilter(true)
{
    auto getKnobValues = [&](std::vector<uint32_t>& values, const Knob<int>& knob) -> bool
    {
        for (uint32_t i = 0; i != knob.NumValues(); ++i) { values.push_back(knob.GetValue(i)); }
        return values.empty();
    };


    /*!
     * The filters `--enqueue` and `--skip_enqueue` support :
     * 
     * 1- specific enqueue(s) indexe(s) to be profiled (or skipped)
     * Example:
     * --enqueue 5      => profile only when enqueue_id=5
     * --enqueue 5,7    => profile only when enqueue_id=5 or enqueue_id=7
     * 
     * 2- range of enqueues to be profiled (or skipped)
     * Example: 
     * --enqueue 5-7        => profile when  5 =< enqueue_id =< 7
     * --enqueue 3,5-7,10   => profile when enqueue_id=3 or when 5 =< enqueue_id =< 7 or when enqueue_id=10
     * --enqueue -9         => profile when enqueue_id is <= 9
     * --enqueue 9-         => profile when enqueue_id id >= 9
     * 
     * 3- kernel name specification :
     * Example:
     * --enqueue k1:5-9         => profile only the enqueues with indexes between 5 and 9 for the kernel named `k1`.
     * --enqueue k1:3,5-9,11    => profile only the enqueues with indexes between 5 and 9 for the kernel named `k1`.
     *
     * Similarly, for `--skip_enqueue k1:5-9`, the enqueues with indexes between 5 and 9
     * for the kernel `k1` will be skipped (will not be profiled).
    */
    auto getKnobRanges = [&](std::vector<kernelEnqueueInfo>& values, const Knob<std::string>& knob) -> bool
    {
        for (uint32_t i = 0; i != knob.NumValues(); ++i)
        { 
            std::stringstream input(knob.GetValue(i));
            std::string range;
            std::string kernelName = "";
            auto pos = input.str().find(':');
            if (pos == std::string::npos)
            {
                input.seekg(0);
            }
            else
            {
                kernelName = input.str().substr(0, pos);
                input.seekg(pos + 1);
            }
            while (std::getline(input, range, ',')) // split by comma 
            {
                pos = range.find('-');
                if (pos == std::string::npos) // a specific number 
                {
                    values.push_back(std::make_tuple(kernelName, std::stoi(range), std::stoi(range)));
                }
                else if (pos == 0) // {-X}up to X  
                {
                    auto max = std::stoi(range.substr(pos + 1));
                    values.push_back(std::make_tuple(kernelName, 0, max));
                }
                else if (pos == range.length()-1 ) // {X-} X and above
                {
                    auto min = std::stoi(range.substr(0, pos));
                    values.push_back(std::make_tuple(kernelName, min, UINT32_MAX));
                }
                else
                {
                    auto min = std::stoi(range.substr(0, pos));
                    auto max = std::stoi(range.substr(pos + 1));
                    values.push_back(std::make_tuple(kernelName, min, max));
                }
            }
        }
        return values.empty();
    };
    
    isEmptyFilter = getKnobRanges(enqueues,     knobEnqueue)        && isEmptyFilter;
    isEmptyFilter = getKnobValues(draw,         knobDraw)           && isEmptyFilter;
    isEmptyFilter = getKnobValues(device,       knobDevice)         && isEmptyFilter;
    isEmptyFilter = getKnobValues(cl,           knobCl)             && isEmptyFilter;
    isEmptyFilter = getKnobValues(pso,          knobPso)            && isEmptyFilter;
    isEmptyFilter = getKnobValues(frame,        knobFrame)          && isEmptyFilter;

    isEmptyFilter = getKnobRanges(skipEnqueue,  knobSkipEnqueue)    && isEmptyFilter;
    isEmptyFilter = getKnobValues(skipDraw,     knobSkipDraw)       && isEmptyFilter;
    isEmptyFilter = getKnobValues(skipDevice,   knobSkipDevice)     && isEmptyFilter;
    isEmptyFilter = getKnobValues(skipCl,       knobSkipCl)         && isEmptyFilter;
    isEmptyFilter = getKnobValues(skipPso,      knobSkipPso)        && isEmptyFilter;
    isEmptyFilter = getKnobValues(skipFrame,    knobSkipFrame)      && isEmptyFilter;
}

bool KernelExecConfig::IsProfileEnabled(const GtKernelExecDesc& execDesc, GPU_PLATFORM platform, const char* kernelName) const
{
    if (isEmptyFilter)
    {
        return true;
    }

    auto checkValue = [&](uint32_t value, const std::vector<uint32_t>& pass, const std::vector<uint32_t>& skip) -> bool
    {
        return ((pass.empty() || (std::find(pass.cbegin(), pass.cend(), value) != pass.cend())) &&
               (std::find(skip.cbegin(), skip.cend(), value) == skip.cend()));
    };

    auto checkRanges = [&](uint32_t value,
                                    const std::vector<kernelEnqueueInfo>& ranges) -> bool
    {
        for (auto& range : ranges)
        {
            if ((!kernelName || std::get<0>(range).empty() ||
                (kernelName && std::string(kernelName).find(std::get<0>(range)) != std::string::npos)) &&
                (value >= std::get<1>(range) && value <= std::get<2>(range)))
                return true;
        }
        return false;
    };


    switch (platform)
    {
    case GPU_PLATFORM_OCL:
        return  (enqueues.empty()    || checkRanges(execDesc.oclExecDesc.enqueue_index, enqueues)) &&
                (skipEnqueue.empty() || !checkRanges(execDesc.oclExecDesc.enqueue_index, skipEnqueue)) &&
                checkValue(execDesc.oclExecDesc.device_index, device, skipDevice);

    case GPU_PLATFORM_L0:
        return  (enqueues.empty()    || checkRanges(execDesc.l0ExecDesc.enqueue_index, enqueues)) &&
                (skipEnqueue.empty() || !checkRanges(execDesc.l0ExecDesc.enqueue_index, skipEnqueue)) &&
                checkValue(execDesc.l0ExecDesc.device_index, device, skipDevice);

    case GPU_PLATFORM_DX11:
        return  checkValue(execDesc.dx11ExecDesc.draw_index, draw, skipDraw) &&
                checkValue(execDesc.dx11ExecDesc.device_index, device, skipDevice) &&
                checkValue(execDesc.dx11ExecDesc.cl_id, cl, skipCl) &&
                 checkValue(execDesc.dx11ExecDesc.frame_id, frame, skipFrame);

    case GPU_PLATFORM_VULKAN:
    case GPU_PLATFORM_DX12:
        return  checkValue(execDesc.dx12ExecDesc.draw_index, draw, skipDraw) &&
                checkValue(execDesc.dx12ExecDesc.device_index,  device, skipDevice) &&
                checkValue(execDesc.dx12ExecDesc.cl_id,  cl, skipCl) &&
                checkValue(execDesc.dx12ExecDesc.pso_id,  pso, skipPso) &&
                checkValue(execDesc.dx12ExecDesc.frame_id, frame, skipFrame);
    default:
        return true;
    }
}

}
