/*
 * Copyright (C) 2023-2024 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 <thread>
#include <pthread.h>
#include <dlfcn.h>

#include "distributed_call_manager.h"
#include "audio_control_manager.h"
#include "telephony_log_wrapper.h"
#include "nlohmann/json.hpp"

using json = nlohmann::json;

namespace OHOS {
namespace Telephony {
namespace {
const size_t INT32_MIN_ID_LENGTH = 3;
const size_t INT32_SHORT_ID_LENGTH = 20;
const size_t INT32_PLAINTEXT_LENGTH = 4;
const int32_t DCALL_SWITCH_DEVICE_TYPE_SOURCE = 0;
const int32_t DCALL_SWITCH_DEVICE_TYPE_SINK = 1;
const int32_t DISTRIBUTED_CALL_SOURCE_SA_ID = 9855;
const int32_t WAIT_DCALL_INIT_100MS = 100 * 1000;
const std::string CALLBACK_NAME = "telephony";
const std::string DISTRIBUTED_AUDIO_DEV_CAR = "dCar";
const std::string DISTRIBUTED_AUDIO_DEV_PHONE = "dPhone";
const std::string DISTRIBUTED_AUDIO_DEV_PAD = "dPad";
const std::string SWITCH_ON_DCALL_THREAD_NAME = "switch on dcall";

const int32_t DISTRIBUTED_COMMUNICATION_CALL_SA_ID = 66198;

std::string GetAnonyString(const std::string &value)
{
    std::string res;
    std::string tmpStr("******");
    size_t strLen = value.length();
    if (strLen < INT32_MIN_ID_LENGTH) {
        return tmpStr;
    }
    if (strLen <= INT32_SHORT_ID_LENGTH) {
        res += value[0];
        res += tmpStr;
        res += value[strLen - 1];
    } else {
        res.append(value, 0, INT32_PLAINTEXT_LENGTH);
        res += tmpStr;
        res.append(value, strLen - INT32_PLAINTEXT_LENGTH, INT32_PLAINTEXT_LENGTH);
    }
    return res;
}

bool IsDistributedAudioDevice(const AudioDevice& device)
{
    if ((device.deviceType == AudioDeviceType::DEVICE_DISTRIBUTED_PHONE) ||
        (device.deviceType == AudioDeviceType::DEVICE_DISTRIBUTED_PAD) ||
        (device.deviceType == AudioDeviceType::DEVICE_DISTRIBUTED_AUTOMOTIVE)) {
        return true;
    }
    return false;
}
}

DistributedCallManager::DistributedCallManager()
{
    TELEPHONY_LOGI("DistributedCallManager constructed.");
}

DistributedCallManager::~DistributedCallManager()
{
    TELEPHONY_LOGI("DistributedCallManager destructed.");
}

void DistributedCallManager::Init()
{
    TELEPHONY_LOGI("Init start.");
    statusChangeListener_ = new (std::nothrow) DCallSystemAbilityListener();
    if (statusChangeListener_ == nullptr) {
        TELEPHONY_LOGE("failed to create statusChangeListener");
        return;
    }
    auto managerPtr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    if (managerPtr == nullptr) {
        TELEPHONY_LOGE("get system ability manager error");
        return;
    }
    int32_t ret = managerPtr->SubscribeSystemAbility(DISTRIBUTED_CALL_SOURCE_SA_ID, statusChangeListener_);
    if (ret != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGE("failed to subscribe dcall service SA: %{public}d", DISTRIBUTED_CALL_SOURCE_SA_ID);
        return;
    }
    InitDistributedCommunicationCall();
    TELEPHONY_LOGI("Init end.");
}

bool DistributedCallManager::CreateDAudioDevice(const std::string& devId, AudioDevice& device)
{
    if (!devId.length()) {
        TELEPHONY_LOGE("dcall devId is invalid");
        return false;
    }
    if (dcallProxy_ == nullptr) {
        TELEPHONY_LOGE("dcallProxy_ is nullptr");
        return false;
    }
    OHOS::DistributedHardware::DCallDeviceInfo devInfo;
    int32_t ret = dcallProxy_->GetDCallDeviceInfo(devId, devInfo);
    if (ret != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGI("get dcall device info failed.");
        return false;
    }
    std::string devTypeName;
    std::string devName = devInfo.devName;
    if (devInfo.devType == OHOS::DistributedHardware::DCallDeviceType::DISTRIBUTED_DEVICE_PHONE) {
        devTypeName = DISTRIBUTED_AUDIO_DEV_PHONE;
        device.deviceType = AudioDeviceType::DEVICE_DISTRIBUTED_PHONE;
    } else if (devInfo.devType == OHOS::DistributedHardware::DCallDeviceType::DISTRIBUTED_DEVICE_PAD) {
        devTypeName = DISTRIBUTED_AUDIO_DEV_PAD;
        device.deviceType = AudioDeviceType::DEVICE_DISTRIBUTED_PAD;
    } else {
        devTypeName = DISTRIBUTED_AUDIO_DEV_CAR;
        device.deviceType = AudioDeviceType::DEVICE_DISTRIBUTED_AUTOMOTIVE;
    }
    json addressJson;
    addressJson["devName"] = devName;
    addressJson["devId"] = devId;
    std::string addressStr = addressJson.dump();
    if (memcpy_s(device.address, kMaxAddressLen, addressStr.c_str(), addressStr.length()) != EOK) {
        TELEPHONY_LOGE("memcpy_s failed.");
        return false;
    }
    TELEPHONY_LOGI("create distributed audio device succeed.");
    return true;
}

std::string DistributedCallManager::GetDevIdFromAudioDevice(const AudioDevice& device)
{
    std::string devId = "";
    if (!IsDistributedAudioDevice(device)) {
        TELEPHONY_LOGE("not distributed audio device, device type: %{public}d", device.deviceType);
        return devId;
    }
    std::string address = device.address;
    if (!address.length()) {
        TELEPHONY_LOGE("invalid address");
        return devId;
    }
    json addressJson = json::parse(address, nullptr, false);
    if (addressJson.is_null() || addressJson.is_discarded()) {
        TELEPHONY_LOGE("json value is null or discarded.");
        return devId;
    }
    if (!addressJson.contains("devId")) {
        TELEPHONY_LOGE("json not contain devId.");
        return devId;
    }
    if (!addressJson["devId"].is_string()) {
        TELEPHONY_LOGE("json has no devId string.");
        return devId;
    }
    devId = addressJson["devId"];
    TELEPHONY_LOGI("devId: %{public}s", GetAnonyString(devId).c_str());
    return devId;
}

int32_t DistributedCallManager::AddDCallDevice(const std::string& devId)
{
    TELEPHONY_LOGI("add dcall device, devId: %{public}s.", GetAnonyString(devId).c_str());
    std::lock_guard<std::mutex> lock(onlineDeviceMtx_);

    auto iter = onlineDCallDevices_.find(devId);
    if (iter != onlineDCallDevices_.end()) {
        TELEPHONY_LOGW("device is already exist, devId: %{public}s", GetAnonyString(devId).c_str());
        return TELEPHONY_SUCCESS;
    }

    AudioDevice device;
    if (!CreateDAudioDevice(devId, device)) {
        TELEPHONY_LOGE("failed to create distributed audio device, devId: %{public}s", GetAnonyString(devId).c_str());
        return TELEPHONY_ERR_FAIL;
    }

    DelayedSingleton<AudioDeviceManager>::GetInstance()->AddAudioDeviceList(device.address, device.deviceType, "");
    onlineDCallDevices_.emplace(devId, device);

    if (!dCallDeviceSwitchedOn_.load() && isCallActived_.load()) {
        if (device.deviceType == AudioDeviceType::DEVICE_DISTRIBUTED_AUTOMOTIVE) {
            TELEPHONY_LOGI("switch call to auto motive as it is online");
            SwitchOnDCallDeviceAsync(device);
        }
    }
    return TELEPHONY_SUCCESS;
}

int32_t DistributedCallManager::RemoveDCallDevice(const std::string& devId)
{
    TELEPHONY_LOGI("remove dcall device, devId: %{public}s.", GetAnonyString(devId).c_str());
    std::lock_guard<std::mutex> lock(onlineDeviceMtx_);
    auto iter = onlineDCallDevices_.find(devId);
    if (iter != onlineDCallDevices_.end()) {
        std::string devId = GetDevIdFromAudioDevice(iter->second);
        std::string curDevId = GetConnectedDCallDeviceId();
        TELEPHONY_LOGI("removed devId: %{public}s, current devId: %{public}s",
            GetAnonyString(devId).c_str(), GetAnonyString(curDevId).c_str());
        DelayedSingleton<AudioDeviceManager>::GetInstance()->RemoveAudioDeviceList(
            iter->second.address, iter->second.deviceType);
        onlineDCallDevices_.erase(iter);
        if (curDevId == devId) {
            TELEPHONY_LOGI("current dcall device is removed, now reinit audio device.");
            dCallDeviceSwitchedOn_.store(false);
            ClearConnectedDCallDevice();
            DelayedSingleton<AudioDeviceManager>::GetInstance()->InitAudioDevice();
        }
    }
    return TELEPHONY_SUCCESS;
}

void DistributedCallManager::ClearDCallDevices()
{
    TELEPHONY_LOGI("clear dcall device.");
    std::lock_guard<std::mutex> lock(onlineDeviceMtx_);
    onlineDCallDevices_.clear();
}

void DistributedCallManager::NotifyOnlineDCallDevices(std::vector<std::string> devices)
{
    TELEPHONY_LOGI("notify online dcall devices start, size: %{public}d", static_cast<int32_t>(devices.size()));
    for (auto item : devices) {
        AddDCallDevice(item);
        TELEPHONY_LOGI("notify dcall device, devId: %{public}s", GetAnonyString(item).c_str());
    }
    TELEPHONY_LOGI("notify online dcall devices end.");
}

std::string DistributedCallManager::GetConnectedDCallDeviceAddr()
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    std::string addr = connectedAudioDevice_.address;
    return addr;
}

AudioDeviceType DistributedCallManager::GetConnectedDCallDeviceType()
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    AudioDeviceType type = connectedAudioDevice_.deviceType;
    return type;
}

std::string DistributedCallManager::GetConnectedDCallDeviceId()
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    std::string devId = "";
    if (dCallDeviceSwitchedOn_.load()) {
        devId = GetDevIdFromAudioDevice(connectedAudioDevice_);
    }
    return devId;
}

void DistributedCallManager::GetConnectedDCallDevice(AudioDevice& device)
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    device.deviceType = connectedAudioDevice_.deviceType;
    if (memcpy_s(device.address, kMaxAddressLen, connectedAudioDevice_.address, kMaxAddressLen) != EOK) {
        TELEPHONY_LOGE("memcpy_s failed.");
    }
}

void DistributedCallManager::SetConnectedDCallDevice(const AudioDevice& device)
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    connectedAudioDevice_.deviceType = device.deviceType;
    if (memcpy_s(connectedAudioDevice_.address, kMaxAddressLen, device.address, kMaxAddressLen) != EOK) {
        TELEPHONY_LOGE("memcpy_s failed.");
    }
}

void DistributedCallManager::ClearConnectedDCallDevice()
{
    std::lock_guard<std::mutex> lock(connectedDevMtx_);
    connectedAudioDevice_.deviceType = AudioDeviceType::DEVICE_UNKNOWN;
    if (memset_s(connectedAudioDevice_.address, kMaxAddressLen, 0, kMaxAddressLen) != EOK) {
        TELEPHONY_LOGE("memset_s failed.");
    }
}

bool DistributedCallManager::SwitchOnDCallDeviceSync(const AudioDevice& device)
{
    TELEPHONY_LOGI("switch dcall device on sync");
    if (!IsDistributedAudioDevice(device)) {
        TELEPHONY_LOGE("not distributed audio device, device type: %{public}d", device.deviceType);
        return false;
    }
    std::string devId = GetDevIdFromAudioDevice(device);
    if (!devId.length()) {
        TELEPHONY_LOGE("dcall devId is invalid");
        return false;
    }
    TELEPHONY_LOGI("switch dcall device on start, devId: %{public}s", GetAnonyString(devId).c_str());
    if (dcallProxy_ == nullptr) {
        TELEPHONY_LOGE("dcallProxy_ is nullptr");
        return false;
    }
    int32_t ret = dcallProxy_->SwitchDevice(devId, DCALL_SWITCH_DEVICE_TYPE_SINK);
    if (ret == TELEPHONY_SUCCESS) {
        dCallDeviceSwitchedOn_.store(true);
        SetConnectedDCallDevice(device);
        DelayedSingleton<AudioDeviceManager>::GetInstance()->SetCurrentAudioDevice(device.deviceType);
        TELEPHONY_LOGI("switch dcall device on succeed.");
        return true;
    }
    TELEPHONY_LOGI("switch dcall device on failed, ret: %{public}d.", ret);
    return false;
}

void DistributedCallManager::SetCallState(bool isActive)
{
    isCallActived_.store(isActive);
}

void DistributedCallManager::DealDisconnectCall()
{
    dCallDeviceSwitchedOn_.store(false);
    ClearConnectedDCallDevice();
}

void DistributedCallManager::SwitchOnDCallDeviceAsync(const AudioDevice& device)
{
    TELEPHONY_LOGI("switch dcall device on async");
    std::thread switchThread = std::thread([this, device]() { this->SwitchOnDCallDeviceSync(device); });
    pthread_setname_np(switchThread.native_handle(), SWITCH_ON_DCALL_THREAD_NAME.c_str());
    switchThread.detach();
}

void DistributedCallManager::SwitchOffDCallDeviceSync()
{
    TELEPHONY_LOGI("switch dcall device off sync");
    if (!dCallDeviceSwitchedOn_.load()) {
        TELEPHONY_LOGE("distributed audio device not connected.");
        return;
    }
    std::string devId = GetConnectedDCallDeviceId();
    if (!devId.length()) {
        TELEPHONY_LOGE("dcall devId is invalid");
        return;
    }
    TELEPHONY_LOGI("switch dcall device off start, devId: %{public}s", GetAnonyString(devId).c_str());
    if (dcallProxy_ == nullptr) {
        TELEPHONY_LOGE("dcallProxy_ is nullptr");
        return;
    }
    int32_t ret = dcallProxy_->SwitchDevice(devId, DCALL_SWITCH_DEVICE_TYPE_SOURCE);
    if (ret == TELEPHONY_SUCCESS) {
        dCallDeviceSwitchedOn_.store(false);
        ClearConnectedDCallDevice();
        TELEPHONY_LOGI("switch dcall device off succeed.");
    } else {
        TELEPHONY_LOGE("switch dcall device off failed, %{public}d", ret);
    }
}

bool DistributedCallManager::IsDCallDeviceSwitchedOn()
{
    return dCallDeviceSwitchedOn_.load();
}

int32_t DistributedCallManager::OnDCallDeviceOnline(const std::string &devId)
{
    TELEPHONY_LOGI("dcall device is online, devId: %{public}s", GetAnonyString(devId).c_str());
    return AddDCallDevice(devId);
}

int32_t DistributedCallManager::OnDCallDeviceOffline(const std::string &devId)
{
    TELEPHONY_LOGI("dcall device is offline, devId: %{public}s", GetAnonyString(devId).c_str());
    return RemoveDCallDevice(devId);
}

int32_t DistributedCallManager::DistributedCallDeviceListener::OnDCallDeviceOnline(const std::string &devId)
{
    TELEPHONY_LOGI("dcall device is online, devId: %{public}s", GetAnonyString(devId).c_str());
    return DelayedSingleton<DistributedCallManager>::GetInstance()->OnDCallDeviceOnline(devId);
}

int32_t DistributedCallManager::DistributedCallDeviceListener::OnDCallDeviceOffline(const std::string &devId)
{
    TELEPHONY_LOGI("dcall device is offline, devId: %{public}s", GetAnonyString(devId).c_str());
    return DelayedSingleton<DistributedCallManager>::GetInstance()->OnDCallDeviceOffline(devId);
}

void DistributedCallManager::OnDCallSystemAbilityAdded(const std::string &deviceId)
{
    TELEPHONY_LOGI("dcall source service is added, deviceId: %{public}s", GetAnonyString(deviceId).c_str());
    // wait 100ms for dcall-sa to complete init.
    usleep(WAIT_DCALL_INIT_100MS);
    dcallProxy_ = std::make_shared<DistributedCallProxy>();
    if (dcallProxy_ == nullptr) {
        TELEPHONY_LOGE("fail to create dcall proxy obj");
        return;
    }
    if (dcallProxy_->Init() != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGE("init dcall proxy failed");
        return;
    }
    dcallDeviceListener_ = std::make_shared<DistributedCallDeviceListener>();
    if (dcallDeviceListener_ == nullptr) {
        TELEPHONY_LOGE("dcallDeviceListener_ is nullptr");
        return;
    }
    if (dcallProxy_->RegisterDeviceCallback(CALLBACK_NAME, dcallDeviceListener_) != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGE("register dcall callback failed");
        return;
    }
    std::vector<std::string> dcallDevices;
    if (dcallProxy_->GetOnlineDeviceList(dcallDevices) != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGE("get dcall device list failed");
        return;
    }
    if (dcallDevices.size() > 0) {
        NotifyOnlineDCallDevices(dcallDevices);
    }
    TELEPHONY_LOGI("OnDCallSystemAbilityAdded end.");
}

void DistributedCallManager::OnDCallSystemAbilityRemoved(const std::string &deviceId)
{
    TELEPHONY_LOGI("dcall source service is removed, deviceId: %{public}s", GetAnonyString(deviceId).c_str());
    dcallDeviceListener_ = nullptr;
    dcallProxy_ = nullptr;
    dCallDeviceSwitchedOn_.store(false);
    ClearDCallDevices();
    ClearConnectedDCallDevice();
    DelayedSingleton<AudioDeviceManager>::GetInstance()->ResetDistributedCallDevicesList();
    DelayedSingleton<AudioDeviceManager>::GetInstance()->InitAudioDevice();
    TELEPHONY_LOGI("OnDCallSystemAbilityRemoved end.");
}

void DCallSystemAbilityListener::OnAddSystemAbility(int32_t systemAbilityId, const std::string &deviceId)
{
    TELEPHONY_LOGI("SA: %{public}d is added!", systemAbilityId);
    if (!CheckInputSysAbilityId(systemAbilityId)) {
        TELEPHONY_LOGE("added SA is invalid!");
        return;
    }
    if (systemAbilityId != DISTRIBUTED_CALL_SOURCE_SA_ID) {
        TELEPHONY_LOGE("added SA is not dcall source service, ignored.");
        return;
    }
    TELEPHONY_LOGI("notify dcall source service added event to distributed call manager");
    DelayedSingleton<DistributedCallManager>::GetInstance()->OnDCallSystemAbilityAdded(deviceId);
}

void DCallSystemAbilityListener::OnRemoveSystemAbility(int32_t systemAbilityId, const std::string &deviceId)
{
    TELEPHONY_LOGI("SA: %{public}d is removed!", systemAbilityId);
    if (!CheckInputSysAbilityId(systemAbilityId)) {
        TELEPHONY_LOGE("removed SA is invalid!");
        return;
    }
    if (systemAbilityId != DISTRIBUTED_CALL_SOURCE_SA_ID) {
        TELEPHONY_LOGE("removed SA is not dcall source service, ignored.");
        return;
    }
    TELEPHONY_LOGI("notify dcall source service removed event to distributed call manager");
    DelayedSingleton<DistributedCallManager>::GetInstance()->OnDCallSystemAbilityRemoved(deviceId);
}

void DistributedCallManager::InitDistributedCommunicationCall()
{
    TELEPHONY_LOGI("Init distributed communication call");
    dcCallSaListener_ = new (std::nothrow) DcCallSystemAbilityListener();
    if (dcCallSaListener_ == nullptr) {
        TELEPHONY_LOGE("init dc-call fail, create sa linstener fail");
        return;
    }
    auto saManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    if (saManager == nullptr) {
        TELEPHONY_LOGE("init dc-call fail, get system ability manager fail");
        return;
    }
    int32_t ret = saManager->SubscribeSystemAbility(DISTRIBUTED_COMMUNICATION_CALL_SA_ID, dcCallSaListener_);
    if (ret != TELEPHONY_SUCCESS) {
        TELEPHONY_LOGE("failed to subscribe dc-call service SA: %{public}d", DISTRIBUTED_COMMUNICATION_CALL_SA_ID);
        return;
    }
}

void DistributedCallManager::OnDcCallSystemAbilityAdded()
{
    TELEPHONY_LOGI("dc-call service added");
    auto handle = dlopen("libtelephony_ext_innerkits.z.so", RTLD_NOW);
    if (handle == nullptr) {
        TELEPHONY_LOGE("open so failed");
        return;
    }
    typedef int32_t (*REGISTER_DC_CALL)();
    auto regFunc = (REGISTER_DC_CALL)dlsym(handle, "RegisterDcCall");
    if (regFunc == nullptr) {
        TELEPHONY_LOGE("get reg function failed");
        dlclose(handle);
        return;
    }
    auto ret = regFunc();
    TELEPHONY_LOGI("reg dc-call service result %{public}d", ret);
    dlclose(handle);
}

void DistributedCallManager::OnDcCallSystemAbilityRemoved()
{
    TELEPHONY_LOGI("dc-call service removed");
}

void DcCallSystemAbilityListener::OnAddSystemAbility(int32_t systemAbilityId, const std::string &deviceId)
{
    if (!CheckInputSysAbilityId(systemAbilityId)) {
        TELEPHONY_LOGE("invalid sa");
        return;
    }
    if (systemAbilityId != DISTRIBUTED_COMMUNICATION_CALL_SA_ID) {
        TELEPHONY_LOGE("added SA is not dc-call service, ignored");
        return;
    }
    DelayedSingleton<DistributedCallManager>::GetInstance()->OnDcCallSystemAbilityAdded();
}

void DcCallSystemAbilityListener::OnRemoveSystemAbility(int32_t systemAbilityId, const std::string &deviceId)
{
    if (!CheckInputSysAbilityId(systemAbilityId)) {
        TELEPHONY_LOGE("invalid sa");
        return;
    }
    if (systemAbilityId != DISTRIBUTED_COMMUNICATION_CALL_SA_ID) {
        TELEPHONY_LOGE("removed SA is not dc-call service, ignored");
        return;
    }
    DelayedSingleton<DistributedCallManager>::GetInstance()->OnDcCallSystemAbilityRemoved();
}

} // namespace Telephony
} // namespace OHOS