/*
 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "distributed_bms_host.h"

#include "app_log_wrapper.h"
#include "appexecfwk_errors.h"
#include "bundle_constants.h"
#include "bundle_memory_guard.h"
#include "dbms_scope_guard.h"
#include "distributed_bundle_ipc_interface_code.h"
#include "remote_ability_info.h"

namespace OHOS {
namespace AppExecFwk {
namespace {
constexpr int32_t GET_REMOTE_ABILITY_INFO_MAX_SIZE = 10;
constexpr int32_t MIN_SIZE = 0;
}

DistributedBmsHost::DistributedBmsHost()
{
    APP_LOGI("DistributedBmsHost instance is created");
}

DistributedBmsHost::~DistributedBmsHost()
{
    APP_LOGI("DistributedBmsHost instance is destroyed");
}

int DistributedBmsHost::OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option)
{
    BundleMemoryGuard memoryGuard;
    APP_LOGI("DistributedBmsHost receives message from client, code = %{public}u, flags = %{public}d", code,
        option.GetFlags());
    std::u16string descripter = DistributedBmsHost::GetDescriptor();
    std::u16string remoteDescripter = data.ReadInterfaceToken();
    if (descripter != remoteDescripter) {
        APP_LOGE("verify interface token failed");
        return ERR_INVALID_STATE;
    }
    switch (code) {
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_REMOTE_ABILITY_INFO):
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_REMOTE_ABILITY_INFO_WITH_LOCALE):
            return HandleGetRemoteAbilityInfo(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_REMOTE_ABILITY_INFOS):
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_REMOTE_ABILITY_INFOS_WITH_LOCALE):
            return HandleGetRemoteAbilityInfos(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_ABILITY_INFO):
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_ABILITY_INFO_WITH_LOCALE):
            return HandleGetAbilityInfo(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_ABILITY_INFOS):
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_ABILITY_INFOS_WITH_LOCALE):
            return HandleGetAbilityInfos(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_DISTRIBUTED_BUNDLE_INFO):
            return HandleGetDistributedBundleInfo(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_DISTRIBUTED_BUNDLE_NAME):
            return HandleGetDistributedBundleName(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_REMOTE_BUNDLE_VERSION_CODE):
            return HandleGetRemoteBundleVersionCode(data, reply);
        case static_cast<uint32_t>(DistributedInterfaceCode::GET_BUNDLE_VERSION_CODE):
            return HandleGetBundleVersionCode(data, reply);
        default:
            APP_LOGW("DistributedBmsHost receives unknown code, code = %{public}d", code);
            return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
    }
    return NO_ERROR;
}

int DistributedBmsHost::HandleGetRemoteAbilityInfo(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get remote ability info");
    std::unique_ptr<ElementName> elementName(data.ReadParcelable<ElementName>());
    if (!elementName) {
        APP_LOGE("ReadParcelable<elementName> failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    std::string localeInfo = data.ReadString();
    RemoteAbilityInfo remoteAbilityInfo;
    int ret = GetRemoteAbilityInfo(*elementName, localeInfo, remoteAbilityInfo);
    if (ret != NO_ERROR) {
        APP_LOGE("GetRemoteAbilityInfo result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteBool(true)) {
        APP_LOGE("GetRemoteAbilityInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    if (!reply.WriteParcelable(&remoteAbilityInfo)) {
        APP_LOGE("GetRemoteAbilityInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

int DistributedBmsHost::HandleGetRemoteAbilityInfos(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get remote ability infos");
    std::vector<ElementName> elementNames;
    if (!GetParcelableInfos<ElementName>(data, elementNames)) {
        APP_LOGE("GetRemoteAbilityInfos get parcelable infos failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    std::string localeInfo = data.ReadString();
    std::vector<RemoteAbilityInfo> remoteAbilityInfos;
    int ret = GetRemoteAbilityInfos(elementNames, localeInfo, remoteAbilityInfos);
    if (ret != NO_ERROR) {
        APP_LOGE("GetRemoteAbilityInfos result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteBool(true)) {
        APP_LOGE("GetRemoteAbilityInfos write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    if (!WriteParcelableVector<RemoteAbilityInfo>(remoteAbilityInfos, reply)) {
        APP_LOGE("GetRemoteAbilityInfos write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}


int DistributedBmsHost::HandleGetAbilityInfo(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get ability info");
    std::unique_ptr<ElementName> elementName(data.ReadParcelable<ElementName>());
    if (!elementName) {
        APP_LOGE("ReadParcelable<elementName> failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    std::string localeInfo = data.ReadString();
    bool hasInfo = data.ReadBool();
    DistributedBmsAclInfo *info = nullptr;
    DbmsScopeGuard deletePtrGuard([&info] {
        if (info != nullptr) {
            delete info;
            info = nullptr;
        }
    });
    if (hasInfo) {
        info = data.ReadParcelable<DistributedBmsAclInfo>();
        if (info == nullptr) {
            APP_LOGE("HandleGetAbilityInfos get parcelable info failed");
            return ERR_APPEXECFWK_PARCEL_ERROR;
        }
    }
    RemoteAbilityInfo remoteAbilityInfo;
    int ret = GetAbilityInfo(*elementName, localeInfo, remoteAbilityInfo, info);
    if (ret != NO_ERROR) {
        APP_LOGE("GetAbilityInfo result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteBool(true)) {
        APP_LOGE("GetRemoteAbilityInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    if (!reply.WriteParcelable(&remoteAbilityInfo)) {
        APP_LOGE("GetRemoteAbilityInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

int DistributedBmsHost::HandleGetAbilityInfos(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get ability infos");
    std::vector<ElementName> elementNames;
    if (!GetParcelableInfos<ElementName>(data, elementNames)) {
        APP_LOGE("GetRemoteAbilityInfos get parcelable infos failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    std::string localeInfo = data.ReadString();
    bool hasInfo = data.ReadBool();
    DistributedBmsAclInfo *info = nullptr;
    DbmsScopeGuard deletePtrGuard([&info] {
        if (info != nullptr) {
            delete info;
            info = nullptr;
        }
    });
    if (hasInfo) {
        info = data.ReadParcelable<DistributedBmsAclInfo>();
        if (info == nullptr) {
            APP_LOGE("HandleGetAbilityInfos get parcelable info failed");
            return ERR_APPEXECFWK_PARCEL_ERROR;
        }
    }
    std::vector<RemoteAbilityInfo> remoteAbilityInfos;
    int ret = GetAbilityInfos(elementNames, localeInfo, remoteAbilityInfos, info);
    if (ret != NO_ERROR) {
        APP_LOGE("GetAbilityInfos result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteBool(true)) {
        APP_LOGE("GetAbilityInfos write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    if (!WriteParcelableVector<RemoteAbilityInfo>(remoteAbilityInfos, reply)) {
        APP_LOGE("GetAbilityInfos write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

int DistributedBmsHost::HandleGetDistributedBundleInfo(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get distributedBundleInfo");
    std::string networkId = data.ReadString();
    std::string bundleName = data.ReadString();
    DistributedBundleInfo distributedBundleInfo;
    bool ret = GetDistributedBundleInfo(networkId, bundleName, distributedBundleInfo);
    if (!ret) {
        APP_LOGE("GetDistributedBundleInfo failed");
        return INVALID_OPERATION;
    }
    if (!reply.WriteBool(true)) {
        APP_LOGE("GetDistributedBundleInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    if (!reply.WriteParcelable(&distributedBundleInfo)) {
        APP_LOGE("GetDistributedBundleInfo write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

int32_t DistributedBmsHost::HandleGetDistributedBundleName(Parcel &data, Parcel &reply)
{
    APP_LOGD("DistributedBmsHost handle get distributedBundleName");
    std::string networkId = data.ReadString();
    uint32_t accessTokenId = data.ReadUint32();
    std::string bundleName;
    int32_t ret = GetDistributedBundleName(networkId, accessTokenId, bundleName);
    if (ret == NO_ERROR && !reply.WriteString(bundleName)) {
        APP_LOGE("write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return ret;
}

int32_t DistributedBmsHost::HandleGetRemoteBundleVersionCode(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get remote bundle version code");
    std::string deviceId = data.ReadString();
    std::string bundleName = data.ReadString();
    uint32_t versionCode = 0;
    int32_t ret = GetRemoteBundleVersionCode(deviceId, bundleName, versionCode);
    if (ret != NO_ERROR) {
        APP_LOGE("GetRemoteBundleVersionCode result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteUint32(versionCode)) {
        APP_LOGE("GetRemoteBundleVersionCode write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

int32_t DistributedBmsHost::HandleGetBundleVersionCode(Parcel &data, Parcel &reply)
{
    APP_LOGI("DistributedBmsHost handle get bundle version code");
    std::string bundleName = data.ReadString();
    std::unique_ptr<DistributedBmsAclInfo> info(data.ReadParcelable<DistributedBmsAclInfo>());
    if (!info) {
        APP_LOGE("HandleGetBundleVersionCode get parcelable info failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    uint32_t versionCode = 0;
    int32_t ret = GetBundleVersionCode(bundleName, versionCode, *info);
    if (ret != NO_ERROR) {
        APP_LOGE("GetBundleVersionCode result:%{public}d", ret);
        return ret;
    }
    if (!reply.WriteUint32(versionCode)) {
        APP_LOGE("GetBundleVersionCode write failed");
        return ERR_APPEXECFWK_PARCEL_ERROR;
    }
    return NO_ERROR;
}

template<typename T>
bool DistributedBmsHost::WriteParcelableVector(std::vector<T> &parcelableVector, Parcel &reply)
{
    if (!reply.WriteInt32(parcelableVector.size())) {
        APP_LOGE("write ParcelableVector failed");
        return false;
    }

    for (auto &parcelable : parcelableVector) {
        if (!reply.WriteParcelable(&parcelable)) {
            APP_LOGE("write ParcelableVector failed");
            return false;
        }
    }
    return true;
}

template<typename T>
bool DistributedBmsHost::GetParcelableInfos(Parcel &data, std::vector<T> &parcelableInfos)
{
    int32_t infoSize = data.ReadInt32();
    if (infoSize > GET_REMOTE_ABILITY_INFO_MAX_SIZE || infoSize < MIN_SIZE) {
        APP_LOGE("GetParcelableInfos elements num exceeds the limit %{public}d", infoSize);
        return false;
    }
    for (int32_t i = 0; i < infoSize; i++) {
        std::unique_ptr<T> info(data.ReadParcelable<T>());
        if (!info) {
            APP_LOGE("Read Parcelable infos failed");
            return false;
        }
        parcelableInfos.emplace_back(*info);
    }
    APP_LOGD("get parcelable infos success");
    return true;
}
}  // namespace AppExecFwk
}  // namespace OHOS