/*
 * Copyright © 2025 UBports Foundation
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3,
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: Alfred Neumayer <dev.beidl@gmail.com>
 */

#include "qti_perf_performance_booster.h"

#include <vector>

#include "src/core/log.h"

#include <hybris/properties/properties.h>
#include <hybris/common/dlfcn.h>
#include <dlfcn.h>

#include <climits>
#include <limits>
#include <stdexcept>

// Taken from: https://github.com/DerpFest-AOSP/vendor_qcom_opensource_power/blob/15/hint-data.h#L42
#define AOSP_DELTA (0x1200)
#define POWER_HINT_INTERACTION 0x00000002
#define POWER_HINT_VIDEO_ENCODE 0x00000003
#define POWER_HINT_LOW_POWER 0x00000005
#define POWER_HINT_SUSTAINED_PERFORMANCE 0x00000006
#define POWER_HINT_LAUNCH 0x00000008
#define SUSTAINED_PERF_HINT (AOSP_DELTA + POWER_HINT_SUSTAINED_PERFORMANCE)
#define INTERACTION_HINT (AOSP_DELTA + POWER_HINT_INTERACTION)
#define LAUNCH_HINT (AOSP_DELTA + POWER_HINT_LAUNCH)
#define VIDEO_ENCODE_HINT (AOSP_DELTA + POWER_HINT_VIDEO_ENCODE)
#define LOW_POWER_HINT (AOSP_DELTA + POWER_HINT_LOW_POWER)
// From: https://github.com/DerpFest-AOSP/vendor_qcom_opensource_power/blob/15/performance.h#L45
#define VENDOR_HINT_SCROLL_BOOST 0x00001080
#define VENDOR_HINT_FIRST_LAUNCH_BOOST 0x00001081

namespace
{

char const* const log_tag = "QtiPerfPerformanceBooster";
char const* const pkg = "QTI PowerHAL";
enum SCROLL_BOOST_TYPE {
    SCROLL_VERTICAL = 1,
    SCROLL_HORIZONTAL = 2,
    SCROLL_PANEL_VIEW = 3,
    SCROLL_PREFILING = 4,
};
}

repowerd::QtiPerfPerformanceBooster::QtiPerfPerformanceBooster(
    std::shared_ptr<Log> const& log)
    : log{log},
      dlhandle{nullptr},
      perf_lock_rel{nullptr},
      perf_hint{nullptr}
{
    sustained_lock_handle = -1;
    interaction_lock_handle = -1;

#if __LP64__
    const auto dir = "/vendor/lib64/";
#else
    const auto dir = "/vendor/lib/";
#endif

    char cprop[PROP_VALUE_MAX];
    property_get("ro.vendor.extension_library", cprop, "");
    const auto prop = std::string(cprop);

    const auto libfile = dir + prop;
    dlhandle = hybris_dlopen(libfile.c_str(), RTLD_NOW);

    if (!dlhandle) {
        log->logWarning(log_tag, "QtiPerfPerformanceBooster configured but library '%s' not loaded, bailing.",
                        libfile.c_str());
        throw std::runtime_error("Failed to create QtiPerfPerformanceBooster");
    }

    perf_lock_rel = (int (*)(long unsigned int))hybris_dlsym(dlhandle, "perf_lock_rel");
    perf_hint = (int (*)(int, const char*, int, int))hybris_dlsym(dlhandle, "perf_hint");

    log->logDebug(log_tag, "QtiPerf handles: rel:%p hint:%p", (void*)perf_lock_rel, (void*)perf_hint);
}

repowerd::QtiPerfPerformanceBooster::~QtiPerfPerformanceBooster()
{
    release_sustained();
    release_interactive();

    if (dlhandle)
        hybris_dlclose(dlhandle);
}

void repowerd::QtiPerfPerformanceBooster::release_sustained()
{
    if (!perf_lock_rel || sustained_lock_handle == -1)
        return;

    perf_lock_rel(sustained_lock_handle);
    sustained_lock_handle = -1;
    log->logDebug(log_tag, "Lock handle released");
}

void repowerd::QtiPerfPerformanceBooster::release_interactive()
{
    if (!perf_lock_rel || interaction_lock_handle == -1)
        return;

    perf_lock_rel(interaction_lock_handle);
    interaction_lock_handle = -1;
    log->logDebug(log_tag, "Interaction lock handle released");
}

void repowerd::QtiPerfPerformanceBooster::hint_sustained()
{
    if (!perf_hint || sustained_lock_handle != -1)
        return;

    sustained_lock_handle = perf_hint(SUSTAINED_PERF_HINT, pkg, 0, -1);
    log->logDebug(log_tag, "Lock handle: %d", sustained_lock_handle);
}

void repowerd::QtiPerfPerformanceBooster::hint_interactive()
{
    if (!perf_hint || interaction_lock_handle != -1)
        return;

    interaction_lock_handle = perf_hint(VENDOR_HINT_SCROLL_BOOST, pkg, 3000, SCROLL_VERTICAL);
    log->logDebug(log_tag, "Interaction lock handle: %d", interaction_lock_handle);
}

void repowerd::QtiPerfPerformanceBooster::set_performance_mode(PerformanceMode mode)
{
    log->logDebug(log_tag, "set_performance_mode(%d)", mode);

    switch (mode) {
    case PerformanceBooster::suspend:
    case PerformanceBooster::screen_off:
        release_sustained();
        release_interactive();
        break;
    case PerformanceBooster::sustained:
        release_interactive();
        hint_sustained();
        break;
    case PerformanceBooster::interactive:
        release_sustained();
        hint_interactive();
        break;
    default:
        break;
    }
}
