/*
 * Copyright (c) 2022-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 "softbus_adapter.h"

#include <securec.h>
#include <unistd.h>

#include "softbus_bus_center.h"
#include "softbus_common.h"

#include "dscreen_errcode.h"
#include "dscreen_hisysevent.h"
#include "dscreen_util.h"

namespace OHOS {
namespace DistributedHardware {
IMPLEMENT_SINGLE_INSTANCE(SoftbusAdapter);
static void ScreenOnSoftbusSessionOpened(int32_t sessionId, PeerSocketInfo info)
{
    SoftbusAdapter::GetInstance().OnSoftbusSessionOpened(sessionId, info);
}

static void ScreenOnSoftbusSessionClosed(int32_t sessionId, ShutdownReason reason)
{
    SoftbusAdapter::GetInstance().OnSoftbusSessionClosed(sessionId, reason);
}

static void ScreenOnBytesReceived(int32_t sessionId, const void *data, uint32_t dataLen)
{
    SoftbusAdapter::GetInstance().OnBytesReceived(sessionId, data, dataLen);
}

static void ScreenOnStreamReceived(int32_t sessionId, const StreamData *data, const StreamData *ext,
    const StreamFrameInfo *frameInfo)
{
    SoftbusAdapter::GetInstance().OnStreamReceived(sessionId, data, ext, frameInfo);
}

static void ScreenOnMessageReceived(int sessionId, const void *data, unsigned int dataLen)
{
    SoftbusAdapter::GetInstance().OnMessageReceived(sessionId, data, dataLen);
}

SoftbusAdapter::SoftbusAdapter()
{
    DHLOGI("SoftbusAdapter");
    sessListener_.OnBind = ScreenOnSoftbusSessionOpened;
    sessListener_.OnShutdown = ScreenOnSoftbusSessionClosed;
    sessListener_.OnBytes = ScreenOnBytesReceived;
    sessListener_.OnStream = ScreenOnStreamReceived;
    sessListener_.OnMessage = ScreenOnMessageReceived;
    sessListener_.OnFile = nullptr;
    sessListener_.OnQos = nullptr;
    sessListener_.OnError = nullptr;
    sessListener_.OnNegotiate = nullptr;
}

SoftbusAdapter::~SoftbusAdapter()
{
    DHLOGI("~SoftbusAdapter");
}

int32_t SoftbusAdapter::RegisterSoftbusListener(const std::shared_ptr<ISoftbusListener> &listener,
    const std::string &sessionName, const std::string &peerDevId)
{
    if (listener == nullptr) {
        DHLOGE("%{public}s: listener is nullptr.", DSCREEN_LOG_TAG);
        return ERR_DH_SCREEN_ADAPTER_REGISTER_SOFTBUS_LISTENER_FAIL;
    }
    DHLOGI("%{public}s: RegisterListener sess:%{public}s id:%{public}s.", DSCREEN_LOG_TAG, sessionName.c_str(),
        GetAnonyString(peerDevId).c_str());
    std::string strListenerKey = sessionName + "_" + peerDevId;
    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    if (mapListeners_.find(strListenerKey) != mapListeners_.end()) {
        DHLOGE("%{public}s: Session listener already register.", DSCREEN_LOG_TAG);
        return ERR_DH_SCREEN_ADAPTER_REGISTER_SOFTBUS_LISTENER_FAIL;
    }
    mapListeners_.insert(std::make_pair(strListenerKey, listener));

    return DH_SUCCESS;
}

int32_t SoftbusAdapter::UnRegisterSoftbusListener(const std::string &sessionName, const std::string &peerDevId)
{
    DHLOGI("%{public}s: UnRegisterListener sess:%{public}s id:%{public}s.", DSCREEN_LOG_TAG, sessionName.c_str(),
        GetAnonyString(peerDevId).c_str());
    std::string strListenerKey = sessionName + "_" + peerDevId;

    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    mapListeners_.erase(strListenerKey);

    return DH_SUCCESS;
}

int32_t SoftbusAdapter::CreateSoftbusSessionServer(const std::string &pkgname, const std::string &sessionName,
    const std::string &peerDevId)
{
    DHLOGI("%{public}s: CreateSessionServer sess:%{public}s id:%{public}s.", DSCREEN_LOG_TAG, sessionName.c_str(),
        GetAnonyString(peerDevId).c_str());
    {
        std::lock_guard<std::mutex> lock(serverIdMapMutex_);
        std::string idMapValue = sessionName + "_" + peerDevId;
        for (auto it = serverIdMap_.begin(); it != serverIdMap_.end(); it++) {
            if (((it->second).find(idMapValue) != std::string::npos)) {
                DHLOGI("%{public}s: Session already create.", sessionName.c_str());
                return DH_SUCCESS;
            }
        }
    }

    SocketInfo serverInfo = {
        .name = const_cast<char*>(sessionName.c_str()),
        .pkgName = const_cast<char*>(pkgname.c_str()),
        .dataType = DATA_TYPE_VIDEO_STREAM,
    };
    int32_t socketId = Socket(serverInfo);
    if (socketId < 0) {
        DHLOGE("Create Socket fail socketId:%{public}" PRId32, socketId);
        return ERR_DH_SCREEN_ADAPTER_BAD_VALUE;
    }
    QosTV qos[] = {
        {.qos = QOS_TYPE_MIN_BW,        .value = 40 * 1024 * 1024},
        {.qos = QOS_TYPE_MAX_LATENCY,        .value = 8000},
        {.qos = QOS_TYPE_MIN_LATENCY,        .value = 2000},
    };

    int32_t ret = Listen(socketId, qos, sizeof(qos) / sizeof(qos[0]), &sessListener_);
    if (ret != DH_SUCCESS) {
        DHLOGE("Listen socket error for sessionName:%{public}s", sessionName.c_str());
        return ERR_DH_SCREEN_ADAPTER_BAD_VALUE;
    }
    {
        std::lock_guard<std::mutex> lock(serverIdMapMutex_);
        serverIdMap_.insert(std::make_pair(socketId, sessionName + "_" + peerDevId));
    }
    DHLOGI("%{public}s: CreateSessionServer success sessionId. %{public}" PRId32, DSCREEN_LOG_TAG, socketId);
    return DH_SUCCESS;
}

int32_t SoftbusAdapter::RemoveSoftbusSessionServer(const std::string &pkgname, const std::string &sessionName,
    const std::string &peerDevId)
{
    (void)pkgname;
    if (sessionName.empty() || peerDevId.empty()) {
        return ERR_DH_SCREEN_TRANS_NULL_VALUE;
    }
    DHLOGI("%{public}s: RemoveSessionServer sess:%{public}s id:%{public}s", DSCREEN_LOG_TAG, sessionName.c_str(),
        GetAnonyString(peerDevId).c_str());
    int32_t serverSocketId = INVALID_SESSION_ID;
    {
        std::lock_guard<std::mutex> lock(serverIdMapMutex_);
        std::string idMapValue = sessionName + "_" + peerDevId;
        for (auto it = serverIdMap_.begin(); it != serverIdMap_.end();) {
            if (((it->second).find(idMapValue) != std::string::npos)) {
                serverSocketId = it->first;
                it = serverIdMap_.erase(it);
            } else {
                ++it;
            }
        }
    }
    Shutdown(serverSocketId);
    DHLOGI("%{public}s: RemoveSessionServer success.", DSCREEN_LOG_TAG);
    return DH_SUCCESS;
}

int32_t SoftbusAdapter::OpenSoftbusSession(const std::string &mySessionName, const std::string &peerSessionName,
    const std::string &peerDevId)
{
    DHLOGI("%{public}s: OpenSoftbusSession mysess:%{public}s peersess:%{public}s id:%{public}s.", DSCREEN_LOG_TAG,
        mySessionName.c_str(), peerSessionName.c_str(), GetAnonyString(peerDevId).c_str());

    QosTV qos[] = {
        {.qos = QOS_TYPE_MIN_BW,        .value = 40 * 1024 * 1024},
        {.qos = QOS_TYPE_MAX_LATENCY,        .value = 8000},
        {.qos = QOS_TYPE_MIN_LATENCY,        .value = 2000},
    };
    std::string localSesionName = mySessionName + "_" + std::to_string(GetCurrentTimeUs());
    SocketInfo clientInfo = {
        .name = const_cast<char*>((localSesionName.c_str())),
        .peerName = const_cast<char*>(peerSessionName.c_str()),
        .peerNetworkId = const_cast<char*>(peerDevId.c_str()),
        .pkgName = const_cast<char*>(PKG_NAME.c_str()),
        .dataType = DATA_TYPE_VIDEO_STREAM,
    };
    int32_t socketId = Socket(clientInfo);
    if (socketId < 0) {
        DHLOGE("Create OpenSoftbusChannel Socket error");
        return ERR_DH_SCREEN_ADAPTER_PARA_ERROR;
    }
    int32_t ret = Bind(socketId, qos, sizeof(qos) / sizeof(qos[0]), &sessListener_);
    if (ret != DH_SUCCESS) {
        DHLOGE("Bind SocketClient error");
        return ERR_DH_SCREEN_ADAPTER_PARA_ERROR;
    }
    {
        std::lock_guard<std::mutex> lock(idMapMutex_);
        devId2SessIdMap_.insert(std::make_pair(socketId, mySessionName + "_" + peerDevId));
    }
    std::shared_ptr<ISoftbusListener> &listener = GetSoftbusListenerByName(socketId);
    if (listener == nullptr) {
        DHLOGE("Get softbus listener failed.");
        return ERR_DH_SCREEN_TRANS_ERROR;
    }
    PeerSocketInfo info;
    ret = OnSoftbusSessionOpened(socketId, info);
    if (ret != DH_SUCCESS) {
        return ret;
    }
    DHLOGI("%{public}s: OpenSoftbusSession success sessionId: %{public}" PRId32, DSCREEN_LOG_TAG, socketId);
    return socketId;
}

int32_t SoftbusAdapter::CloseSoftbusSession(const int32_t sessionId)
{
    DHLOGI("%{public}s: CloseSoftbusSession, sessid:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    Shutdown(sessionId);
    {
        std::lock_guard<std::mutex> lock(idMapMutex_);
        devId2SessIdMap_.erase(sessionId);
    }
    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    mapSessListeners_.erase(sessionId);

    DHLOGI("%{public}s: CloseSoftbusSession success.", DSCREEN_LOG_TAG);
    return DH_SUCCESS;
}

int32_t SoftbusAdapter::SendSoftbusBytes(int32_t sessionId, const void *data, int32_t dataLen) const
{
    DHLOGD("%{public}s: SendSoftbusBytes, sessid:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    int32_t ret = SendBytes(sessionId, data, dataLen);
    if (ret != DH_SUCCESS) {
        DHLOGE("%{public}s: SendBytes failed ret:%{public}" PRId32, DSCREEN_LOG_TAG, ret);
        return ERR_DH_SCREEN_TRANS_ERROR;
    }

    return DH_SUCCESS;
}

int32_t SoftbusAdapter::SendSoftbusStream(int32_t sessionId, const StreamData *data, const StreamData *ext,
    const StreamFrameInfo *param) const
{
    DHLOGD("%{public}s: SendSoftbusStream, sessid:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    int32_t ret = SendStream(sessionId, data, ext, param);
    if (ret != DH_SUCCESS) {
        DHLOGE("%{public}s: SendStream failed ret:%{public}" PRId32, DSCREEN_LOG_TAG, ret);
        return ERR_DH_SCREEN_TRANS_ERROR;
    }

    return DH_SUCCESS;
}

std::shared_ptr<ISoftbusListener> &SoftbusAdapter::GetSoftbusListenerByName(int32_t sessionId)
{
    DHLOGD("%{public}s: GetSoftbusListenerByName, sessionId:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    std::string strListenerKey = "";
    {
        std::lock_guard<std::mutex> lock(idMapMutex_);
        for (auto it = devId2SessIdMap_.begin(); it != devId2SessIdMap_.end(); it++) {
            if (it->first == sessionId) {
                strListenerKey = it->second;
                break;
            }
        }
    }
    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    if (mapListeners_.find(strListenerKey) == mapListeners_.end()) {
        DHLOGE("%{public}s: Find listener failed.", DSCREEN_LOG_TAG);
        return nullListener_;
    }
    return mapListeners_[strListenerKey];
}

std::shared_ptr<ISoftbusListener> &SoftbusAdapter::GetSoftbusListenerById(int32_t sessionId)
{
    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    if (mapSessListeners_.find(sessionId) == mapSessListeners_.end()) {
        DHLOGE("%{public}s: Find listener failed.", DSCREEN_LOG_TAG);
        return nullListener_;
    }

    return mapSessListeners_[sessionId];
}

int32_t SoftbusAdapter::OnSoftbusSessionOpened(int32_t sessionId, PeerSocketInfo info)
{
    DHLOGI("%{public}s: OnSoftbusSessionOpened session:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    {
        std::lock_guard<std::mutex> lock(serverIdMapMutex_);
        for (auto it = serverIdMap_.begin(); it != serverIdMap_.end(); it++) {
            if (info.networkId == nullptr) {
                break;
            }
            std::string peerDevId(info.networkId);
            if ((it->second).find(peerDevId) != std::string::npos) {
                std::lock_guard<std::mutex> sessionLock(idMapMutex_);
                devId2SessIdMap_.insert(std::make_pair(sessionId, it->second));
                break;
            }
        }
    }

    std::shared_ptr<ISoftbusListener> &listener = GetSoftbusListenerByName(sessionId);
    if (listener == nullptr) {
        DHLOGE("Get softbus listener failed.");
        return ERR_DH_SCREEN_TRANS_ERROR;
    }
    listener->OnSessionOpened(sessionId, info);

    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    mapSessListeners_.insert(std::make_pair(sessionId, listener));

    return DH_SUCCESS;
}

void SoftbusAdapter::OnSoftbusSessionClosed(int32_t sessionId, ShutdownReason reason)
{
    DHLOGI("%{public}s: OnSessionClosed sessionId:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    std::shared_ptr<ISoftbusListener> &listener = GetSoftbusListenerById(sessionId);
    if (listener == nullptr) {
        DHLOGE("Get softbus listener failed.");
        return;
    }
    listener->OnSessionClosed(sessionId, reason);
    {
        std::lock_guard<std::mutex> lock(idMapMutex_);
        devId2SessIdMap_.erase(sessionId);
    }
    std::lock_guard<std::mutex> lisLock(listenerMtx_);
    mapSessListeners_.erase(sessionId);
}

void SoftbusAdapter::OnBytesReceived(int32_t sessionId, const void *data, uint32_t dataLen)
{
    DHLOGD("%{public}s: OnBytesReceived, sessionId:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    if (data == nullptr) {
        DHLOGE("BytesData is null.");
        return;
    }
    if (dataLen == 0 || dataLen > DSCREEN_MAX_RECV_DATA_LEN) {
        DHLOGE("BytesData length is too large, dataLen:%{public}" PRIu32, dataLen);
        return;
    }

    std::shared_ptr<ISoftbusListener> &listener = GetSoftbusListenerByName(sessionId);
    if (listener == nullptr) {
        DHLOGE("Get softbus listener failed.");
        return;
    }
    listener->OnBytesReceived(sessionId, data, dataLen);
}

void SoftbusAdapter::OnStreamReceived(int32_t sessionId, const StreamData *data, const StreamData *ext,
    const StreamFrameInfo *frameInfo)
{
    DHLOGD("%{public}s OnStreamReceived, sessionId:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
    if (data == nullptr) {
        DHLOGE("StreamData is null.");
        return;
    }
    if (data->bufLen <= 0 || data->bufLen > static_cast<int32_t>(DSCREEN_MAX_RECV_DATA_LEN)) {
        DHLOGE("StreamData length is too large, dataLen:%{public}" PRId32, data->bufLen);
        return;
    }

    std::shared_ptr<ISoftbusListener> &listener = GetSoftbusListenerByName(sessionId);
    if (listener == nullptr) {
        DHLOGE("Get softbus listener failed.");
        return;
    }
    listener->OnStreamReceived(sessionId, data, ext, frameInfo);
}

void SoftbusAdapter::OnMessageReceived(int sessionId, const void *data, unsigned int dataLen) const
{
    (void)data;
    (void)dataLen;
    DHLOGD("%{public}s OnMessageReceived, sessionId:%{public}" PRId32, DSCREEN_LOG_TAG, sessionId);
}
} // namespace DistributedHardware
} // namespace OHOS