/*
 * 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 <vector>

#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)
{
    DeviceInfo device_info;
    const std::vector<PowerHalInterface*> interfaces { &sprd_interface, &fallback_interface };

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

    for (const auto interface : interfaces) {
        mSm = gbinder_servicemanager_new(interface->device.c_str());
        if (!mSm) {
            continue;
        }

        // Try device-specific HALs first
        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;
        break;
    }

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

repowerd::BinderPerformanceBooster::~BinderPerformanceBooster()
{
    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)
{
    if (!mClient)
        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);
    gbinder_local_request_unref(req);
}

void repowerd::BinderPerformanceBooster::set_scenario(const HalPowerHint hint, const bool enabled)
{
    if (!mClient)
        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);
    gbinder_local_request_unref(req);
}

void repowerd::BinderPerformanceBooster::acquire_power_scene(const std::string& name)
{
    if (!mSceneClient || name.empty())
        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)
{
    if (!mSceneClient || name.empty())
        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()
{
    log->log(log_tag, "enable_interactive_mode()");

    set_interactive(true);
    set_scenario(LOW_POWER, false);
    set_scenario(SUSTAINED_PERFORMANCE, true);
    release_power_scene(mSceneOffName);
    acquire_power_scene(mSceneOnName);
}

void repowerd::BinderPerformanceBooster::disable_interactive_mode()
{
    log->log(log_tag, "disable_interactive_mode()");

    set_interactive(false);
    set_scenario(SUSTAINED_PERFORMANCE, false);
    set_scenario(LOW_POWER, true);
    release_power_scene(mSceneOnName);
    acquire_power_scene(mSceneOffName);
}
