/*
 * Copyright © 2023 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 "binder_performance_booster.h"

#include <stdexcept>
#include <vector>

#include <unistd.h>

#include "src/core/log.h"

namespace
{

char const* const log_tag = "BinderPerformanceBooster";

}

repowerd::BinderPerformanceBooster::BinderPerformanceBooster(
    std::shared_ptr<Log> const& log)
    : active_interface(nullptr),
      log{log},
      mSm(nullptr),
      mRemote(nullptr),
      mClient(nullptr),
      mSceneRemote(nullptr),
      mSceneClient(nullptr),
      mRequiresReconnect(true)
{
    if (access("/dev/binder", F_OK) != 0 && access("/dev/binderfs/binder", F_OK) != 0) {
        throw std::runtime_error("No Binder interface available.");
    }

    DeviceInfo device_info;

    mSceneOnName = device_info.get("RepowerdPowerHalSceneOnName", "");
    mSceneOffName = device_info.get("RepowerdPowerHalSceneOffName", "");

    const auto binder_path = device_info.get("RepowerdPowerHalBinderPath", "");
    const auto interface = device_info.get("RepowerdPowerHalInterface", "");
    const auto fqname = device_info.get("RepowerdPowerHalFqName", "");
    const auto scene_interface = device_info.get("RepowerdPowerHalSceneInterface", "");
    const auto scene_fqname = device_info.get("RepowerdPowerHalSceneFqName", "");
    const auto scene_acquire_method = device_info.get("RepowerdPowerHalSceneAcquireMethod", "");
    const auto scene_release_method = device_info.get("RepowerdPowerHalSceneReleaseMethod", "");

    // Required fields
    if (binder_path.empty() || interface.empty() || fqname.empty()) {
        throw std::runtime_error("Required deviceinfo fields missing.");
    }

    interface_from_config = std::make_unique<PowerHalInterface>(
        binder_path,
        interface,
        fqname,
        scene_interface,
        scene_fqname,
        std::stoi(scene_acquire_method),
        std::stoi(scene_release_method)
    );
}

repowerd::BinderPerformanceBooster::~BinderPerformanceBooster()
{
    disconnect();
}

void repowerd::BinderPerformanceBooster::connect()
{
    std::lock_guard<std::mutex> lock(connect_mutex);

    if (active_interface && !mRequiresReconnect)
        return;

    const std::vector<PowerHalInterface*> interfaces { interface_from_config.get(), &aidl_interface, &sprd_interface, &fallback_interface };

    for (const auto interface : interfaces) {
        if (!interface)
            continue;

        mSm = gbinder_servicemanager_new(interface->device.c_str());
        if (!mSm) {
            continue;
        }

        mRemote = gbinder_servicemanager_get_service_sync(mSm, interface->fqname.c_str(), NULL);
        if (!mRemote) {
            gbinder_servicemanager_unref(mSm);
            continue;
        }

        mClient = gbinder_client_new(mRemote, interface->interface.c_str());
        if (!mClient) {
            gbinder_remote_object_unref(mRemote);
            gbinder_servicemanager_unref(mSm);
            continue;
        }

        // Initialize optional scene functionality
        if (!interface->scene_interface.empty() && !interface->scene_fqname.empty()) {
            // Try exercising device-specific HALs functionality
            mSceneRemote = gbinder_servicemanager_get_service_sync(mSm, interface->scene_fqname.c_str(), NULL);
            if (mSceneRemote) {
                mSceneClient = gbinder_client_new(mSceneRemote, interface->scene_interface.c_str());
            }

            if (!mSceneClient) {
                gbinder_remote_object_unref(mSceneRemote);
            }
        }

        active_interface = interface;
        mRequiresReconnect = false;
        break;
    }

    if (!active_interface) {
        log->log(log_tag, "No PowerHAL interface found");
    }
}

void repowerd::BinderPerformanceBooster::disconnect()
{
    std::lock_guard<std::mutex> lock(connect_mutex);

    if (mSceneClient) {
        gbinder_client_unref(mSceneClient);
        mSceneClient = nullptr;
    }
    if (mClient) {
        gbinder_client_unref(mClient);
        mClient = nullptr;
    }
    if (mSceneRemote) {
        gbinder_remote_object_unref(mSceneRemote);
        mSceneRemote = nullptr;
    }
    if (mRemote) {
        gbinder_remote_object_unref(mRemote);
        mRemote = nullptr;
    }
    if (mSm) {
        gbinder_servicemanager_unref(mSm);
        mSm = nullptr;
    }
}

void repowerd::BinderPerformanceBooster::set_interactive(const bool enabled)
{
    connect();

    {
        std::lock_guard<std::mutex> lock(connect_mutex);
        if (!mClient || !active_interface)
            return;

        int status;
        GBinderLocalRequest *req = gbinder_client_new_request(mClient);
        GBinderRemoteReply *reply;
        GBinderWriter writer;

        gbinder_local_request_init_writer(req, &writer);
        gbinder_writer_append_bool(&writer, enabled);
        reply = gbinder_client_transact_sync_reply(mClient,
                                                   1 /* setInteractive */, req, &status);
        if (reply)
            gbinder_remote_reply_unref(reply);

        if (status)
            mRequiresReconnect = true;

        gbinder_local_request_unref(req);
    }
}

void repowerd::BinderPerformanceBooster::set_scenario(const HalPowerHint hint, const bool enabled)
{
    connect();

    {
        std::lock_guard<std::mutex> lock(connect_mutex);
        if (!mClient || !active_interface)
            return;

        int status;
        GBinderLocalRequest *req = gbinder_client_new_request(mClient);
        GBinderRemoteReply *reply;
        GBinderWriter writer;

        gbinder_local_request_init_writer(req, &writer);
        gbinder_writer_append_int32(&writer, static_cast<uint32_t>(hint));
        gbinder_writer_append_int32(&writer, enabled ? 1 : 0);
        reply = gbinder_client_transact_sync_reply(mClient,
                                                   2 /* powerHint */, req, &status);

        if (reply)
            gbinder_remote_reply_unref(reply);

        if (status)
            mRequiresReconnect = true;

        gbinder_local_request_unref(req);
    }
}

void repowerd::BinderPerformanceBooster::acquire_power_scene(const std::string& name)
{
    connect();

    {
        std::lock_guard<std::mutex> lock(connect_mutex);
        if (!mSceneClient || name.empty() || !active_interface)
            return;

        GBinderLocalRequest *req = gbinder_client_new_request(mSceneClient);
        GBinderWriter writer;

        gbinder_local_request_init_writer(req, &writer);
        gbinder_writer_append_string8(&writer, "repowerd");
        gbinder_writer_append_string8(&writer, name.c_str());

        gbinder_client_transact_sync_oneway(mSceneClient,
                                            active_interface->scene_acquire_method, req);
        gbinder_local_request_unref(req);
    }
}

void repowerd::BinderPerformanceBooster::release_power_scene(const std::string& name)
{
    connect();

    {
        std::lock_guard<std::mutex> lock(connect_mutex);
        if (!mSceneClient || name.empty() || !active_interface)
            return;

        GBinderLocalRequest *req = gbinder_client_new_request(mSceneClient);
        GBinderWriter writer;

        gbinder_local_request_init_writer(req, &writer);
        gbinder_writer_append_string8(&writer, name.c_str());

        gbinder_client_transact_sync_oneway(mSceneClient,
                                            active_interface->scene_release_method, req);
        gbinder_local_request_unref(req);
    }
}

void repowerd::BinderPerformanceBooster::enable_interactive_mode()
{
    set_interactive(true);
}

void repowerd::BinderPerformanceBooster::disable_interactive_mode()
{
    set_interactive(false);
}

void repowerd::BinderPerformanceBooster::enable_sustained_mode()
{
    set_scenario(SUSTAINED_PERFORMANCE, true);
    release_power_scene(mSceneOffName);
    acquire_power_scene(mSceneOnName);
}

void repowerd::BinderPerformanceBooster::disable_sustained_mode()
{
    set_scenario(SUSTAINED_PERFORMANCE, false);
    release_power_scene(mSceneOnName);
    acquire_power_scene(mSceneOffName);
}

void repowerd::BinderPerformanceBooster::enable_suspend_mode()
{
    set_scenario(LOW_POWER, true);
}

void repowerd::BinderPerformanceBooster::disable_suspend_mode()
{
    set_scenario(LOW_POWER, false);
}


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

    switch (mode) {
    case PerformanceBooster::suspend:
        disable_interactive_mode();
        disable_sustained_mode();
        enable_suspend_mode();
        break;
    case PerformanceBooster::screen_off:
        disable_suspend_mode();
        disable_sustained_mode();
        disable_interactive_mode();
        break;
    case PerformanceBooster::sustained:
        disable_suspend_mode();
        enable_sustained_mode();
        disable_interactive_mode();
        break;
    case PerformanceBooster::interactive:
        disable_suspend_mode();
        enable_sustained_mode();
        enable_interactive_mode();
        break;
    }
}
