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

#include "ipc_skeleton.h"
#include "token_setproc.h"

#include "device.h"
#include "devicestatus_define.h"
#include "utility.h"

#undef LOG_TAG
#define LOG_TAG "DSoftbusHandler"

namespace OHOS {
namespace Msdp {
namespace DeviceStatus {
namespace Cooperate {
constexpr int32_t MAX_INPUT_DEV_NUM { 100 };
constexpr int32_t INVALID_DEVICE_ID { -1 };

DSoftbusHandler::DSoftbusHandler(IContext *env)
    : env_(env)
{
    handles_ = {
        { static_cast<int32_t>(MessageId::DSOFTBUS_START_COOPERATE),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnStartCooperate(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_STOP_COOPERATE),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnStopCooperate(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_COME_BACK),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnComeBack(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_RELAY_COOPERATE),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnRelayCooperate(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_RELAY_COOPERATE_FINISHED),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnRelayCooperateFinish(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_SUBSCRIBE_MOUSE_LOCATION),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnSubscribeMouseLocation(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_UNSUBSCRIBE_MOUSE_LOCATION),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnUnSubscribeMouseLocation(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_REPLY_SUBSCRIBE_MOUSE_LOCATION),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnReplySubscribeLocation(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_REPLY_UNSUBSCRIBE_MOUSE_LOCATION),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnReplyUnSubscribeLocation(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_MOUSE_LOCATION),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnRemoteMouseLocation(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_INPUT_DEV_SYNC),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnRemoteInputDevice(networkId, packet);}},
        { static_cast<int32_t>(MessageId::DSOFTBUS_INPUT_DEV_HOT_PLUG),
        [this] (const std::string &networkId, NetPacket &packet) {
            this->OnRemoteHotPlug(networkId, packet);}}
    };
    observer_ = std::make_shared<DSoftbusObserver>(*this);
    CHKPV(env_);
    env_->GetDSoftbus().AddObserver(observer_);
}

DSoftbusHandler::~DSoftbusHandler()
{
    CHKPV(env_);
    env_->GetDSoftbus().RemoveObserver(observer_);
}

void DSoftbusHandler::AttachSender(Channel<CooperateEvent>::Sender sender)
{
    CALL_DEBUG_ENTER;
    std::lock_guard guard(lock_);
    sender_ = sender;
}

int32_t DSoftbusHandler::OpenSession(const std::string &networkId)
{
    CALL_DEBUG_ENTER;
    auto tokenId = OHOS::IPCSkeleton::GetCallingTokenID();
    int ret = SetFirstCallerTokenID(tokenId);
    if (ret != RET_OK) {
        FI_HILOGW("Failed to SetFirstCallerTokenID, ret:%{public}d", ret);
    }
    return env_->GetDSoftbus().OpenSession(networkId);
}

void DSoftbusHandler::CloseSession(const std::string &networkId)
{
    CALL_DEBUG_ENTER;
    env_->GetDSoftbus().CloseSession(networkId);
}

void DSoftbusHandler::CloseAllSessions()
{
    CALL_DEBUG_ENTER;
    env_->GetDSoftbus().CloseAllSessions();
}

int32_t DSoftbusHandler::StartCooperate(const std::string &networkId, const DSoftbusStartCooperate &event)
{
    CALL_DEBUG_ENTER;
    NetPacket packet(MessageId::DSOFTBUS_START_COOPERATE);
    packet << event.originNetworkId << event.cursorPos.x
        << event.cursorPos.y << event.success << event.extra.priv;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to write data packet");
        return RET_ERR;
    }
    int32_t ret = env_->GetDSoftbus().SendPacket(networkId, packet);
    if (ret != RET_OK) {
        OnCommunicationFailure(networkId);
    }
    return ret;
}

int32_t DSoftbusHandler::StopCooperate(const std::string &networkId, const DSoftbusStopCooperate &event)
{
    CALL_DEBUG_ENTER;
    NetPacket packet(MessageId::DSOFTBUS_STOP_COOPERATE);
    int32_t ret = env_->GetDSoftbus().SendPacket(networkId, packet);
    if (ret != RET_OK) {
        OnCommunicationFailure(networkId);
    }
    return ret;
}

int32_t DSoftbusHandler::ComeBack(const std::string &networkId, const DSoftbusComeBack &event)
{
    CALL_DEBUG_ENTER;
    NetPacket packet(MessageId::DSOFTBUS_COME_BACK);
    packet << event.originNetworkId << event.cursorPos.x << event.cursorPos.y << event.extra.priv;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to write data packet");
        return RET_ERR;
    }
    int32_t ret = env_->GetDSoftbus().SendPacket(networkId, packet);
    if (ret != RET_OK) {
        OnCommunicationFailure(networkId);
    }
    return ret;
}

int32_t DSoftbusHandler::RelayCooperate(const std::string &networkId, const DSoftbusRelayCooperate &event)
{
    CALL_DEBUG_ENTER;
    NetPacket packet(MessageId::DSOFTBUS_RELAY_COOPERATE);
    packet << event.targetNetworkId;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to write data packet");
        return RET_ERR;
    }
    int32_t ret = env_->GetDSoftbus().SendPacket(networkId, packet);
    if (ret != RET_OK) {
        OnCommunicationFailure(networkId);
    }
    return ret;
}

int32_t DSoftbusHandler::RelayCooperateFinish(const std::string &networkId, const DSoftbusRelayCooperateFinished &event)
{
    CALL_DEBUG_ENTER;
    NetPacket packet(MessageId::DSOFTBUS_RELAY_COOPERATE_FINISHED);
    packet << event.targetNetworkId << event.normal;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to write data packet");
        return RET_ERR;
    }
    int32_t ret = env_->GetDSoftbus().SendPacket(networkId, packet);
    if (ret != RET_OK) {
        OnCommunicationFailure(networkId);
    }
    return ret;
}

std::string DSoftbusHandler::GetLocalNetworkId()
{
    return IDSoftbusAdapter::GetLocalNetworkId();
}

void DSoftbusHandler::OnBind(const std::string &networkId)
{
    FI_HILOGI("Bind to \'%{public}s\'", Utility::Anonymize(networkId).c_str());
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_SESSION_OPENED,
        DSoftbusSessionOpened {
            .networkId = networkId
        }));
}

void DSoftbusHandler::OnShutdown(const std::string &networkId)
{
    FI_HILOGI("Connection with \'%{public}s\' shutdown", Utility::Anonymize(networkId).c_str());
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_SESSION_CLOSED,
        DSoftbusSessionClosed {
            .networkId = networkId
        }));
}

void DSoftbusHandler::OnConnected(const std::string &networkId)
{
    FI_HILOGI("Connection to \'%{public}s\' successfully", Utility::Anonymize(networkId).c_str());
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_SESSION_OPENED,
        DSoftbusSessionOpened {
            .networkId = networkId
        }));
}

bool DSoftbusHandler::OnPacket(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    int32_t messageId = static_cast<int32_t>(packet.GetMsgId());
    auto it = handles_.find(messageId);
    if (it != handles_.end()) {
        (it->second)(networkId, packet);
        return true;
    }
    FI_HILOGD("Unsupported messageId:%{public}d from %{public}s", messageId,
        Utility::Anonymize(networkId).c_str());
    return false;
}

void DSoftbusHandler::SendEvent(const CooperateEvent &event)
{
    std::lock_guard guard(lock_);
    auto ret = sender_.Send(event);
    if (ret != Channel<CooperateEvent>::NO_ERROR) {
        FI_HILOGE("Failed to send event via channel, error:%{public}d", ret);
    }
}

void DSoftbusHandler::OnCommunicationFailure(const std::string &networkId)
{
    env_->GetDSoftbus().CloseSession(networkId);
    FI_HILOGI("Notify communication failure with peer(%{public}s)", Utility::Anonymize(networkId).c_str());
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_SESSION_CLOSED,
        DSoftbusSessionClosed {
            .networkId = networkId
        }));
}

void DSoftbusHandler::OnStartCooperate(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusStartCooperate event {
        .networkId = networkId,
    };
    packet >> event.originNetworkId >> event.cursorPos.x
        >> event.cursorPos.y >> event.success;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    packet >> event.extra.priv;
    if (packet.ChkRWError()) {
        event.extra.priv = 0;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_START_COOPERATE,
        event));
}

void DSoftbusHandler::OnStopCooperate(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusStopCooperate event {
        .networkId = networkId,
        .normal = true,
    };
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_STOP_COOPERATE,
        event));
}

void DSoftbusHandler::OnComeBack(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusComeBack event {
        .networkId = networkId,
        .success = true,
    };
    packet >> event.originNetworkId >> event.cursorPos.x >> event.cursorPos.y;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    packet >> event.extra.priv;
    if (packet.ChkRWError()) {
        event.extra.priv = 0;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_COME_BACK,
        event));
}

void DSoftbusHandler::OnRelayCooperate(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusRelayCooperate event {
        .networkId = networkId,
        .normal = true,
    };
    packet >> event.targetNetworkId;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_RELAY_COOPERATE,
        event));
}

void DSoftbusHandler::OnRelayCooperateFinish(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusRelayCooperate event {
        .networkId = networkId,
    };
    packet >> event.targetNetworkId >> event.normal;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_RELAY_COOPERATE_FINISHED,
        event));
}

void DSoftbusHandler::OnSubscribeMouseLocation(const std::string &networKId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusSubscribeMouseLocation event;
    packet >> event.networkId >> event.remoteNetworkId;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_SUBSCRIBE_MOUSE_LOCATION,
        event));
}

void DSoftbusHandler::OnUnSubscribeMouseLocation(const std::string& networKId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusUnSubscribeMouseLocation event;
    packet >> event.networkId >> event.remoteNetworkId;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_UNSUBSCRIBE_MOUSE_LOCATION,
        event));
}

void DSoftbusHandler::OnReplySubscribeLocation(const std::string& networKId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusReplySubscribeMouseLocation event;
    packet >> event.networkId >> event.remoteNetworkId >> event.result;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_REPLY_SUBSCRIBE_MOUSE_LOCATION,
        event));
}

void DSoftbusHandler::OnReplyUnSubscribeLocation(const std::string& networKId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusReplyUnSubscribeMouseLocation event;
    packet >> event.networkId >> event.remoteNetworkId >> event.result;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_REPLY_UNSUBSCRIBE_MOUSE_LOCATION,
        event));
}

void DSoftbusHandler::OnRemoteMouseLocation(const std::string& networKId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusSyncMouseLocation event;
    packet >> event.networkId >> event.remoteNetworkId >> event.mouseLocation.displayX >>
        event.mouseLocation.displayY >> event.mouseLocation.displayWidth >> event.mouseLocation.displayHeight;
    if (packet.ChkRWError()) {
        FI_HILOGE("Failed to read data packet");
        return;
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_MOUSE_LOCATION,
        event));
}

void DSoftbusHandler::OnRemoteInputDevice(const std::string& networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusSyncInputDevice event;
    int32_t devNum { -1 };
    packet >> devNum;
    event.networkId = networkId;
    FI_HILOGI("devNum:%{public}d", devNum);
    if (devNum <= 0 || devNum >= MAX_INPUT_DEV_NUM) {
        FI_HILOGE("Invalid devNum:%{public}d", devNum);
        return;
    }
    for (int32_t i = 0; i < devNum; i++) {
        auto device = std::make_shared<Device>(INVALID_DEVICE_ID);
        if (DeserializeDevice(device, packet) != RET_OK) {
            FI_HILOGE("DeserializeDevice failed");
            return;
        }
        event.devices.push_back(device);
    }
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_INPUT_DEV_SYNC,
        event));
}

void DSoftbusHandler::OnRemoteHotPlug(const std::string &networkId, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    DSoftbusHotPlugEvent event;
    packet >> event.type;
    FI_HILOGI("Hot plug type:%{public}d", event.type);
    auto device = std::make_shared<Device>(INVALID_DEVICE_ID);
    if (DeserializeDevice(device, packet) != RET_OK) {
        FI_HILOGE("DeserializeDevice failed");
        return;
    }
    event.device = device;
    SendEvent(CooperateEvent(
        CooperateEventType::DSOFTBUS_INPUT_DEV_HOT_PLUG,
        event));
}

int32_t DSoftbusHandler::DeserializeDevice(std::shared_ptr<IDevice> device, NetPacket &packet)
{
    CALL_DEBUG_ENTER;
    CHKPR(device, RET_ERR);
    int32_t data;
    std::string str;
    packet >> data;
    device->SetId(data);
    packet >> str;
    device->SetDevPath(str);
    packet >> str;
    device->SetSysPath(str);
    packet >> data;
    device->SetBus(data);
    packet >> data;
    device->SetVendor(data);
    packet >> data;
    device->SetProduct(data);
    packet >> data;
    device->SetVersion(data);
    packet >> str;
    device->SetName(str);
    packet >> str;
    device->SetPhys(str);
    packet >> str;
    device->SetUniq(str);
    bool isPointerDevice { false };
    packet >> isPointerDevice;
    if (isPointerDevice) {
        device->AddCapability(IDevice::Capability::DEVICE_CAP_POINTER);
    }
    bool isKeyboard { false };
    packet >> isKeyboard;
    if (isKeyboard) {
        device->AddCapability(IDevice::Capability::DEVICE_CAP_KEYBOARD);
    }
    int32_t keyboardType { static_cast<int32_t> (IDevice::KeyboardType::KEYBOARD_TYPE_NONE) };
    packet >> keyboardType;
    device->SetKeyboardType(static_cast<IDevice::KeyboardType>(keyboardType));
    if (packet.ChkRWError()) {
        FI_HILOGE("Packet read type failed");
        return RET_ERR;
    }
    return RET_OK;
}
} // namespace Cooperate
} // namespace DeviceStatus
} // namespace Msdp
} // namespace OHOS