/*
 * 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 "device_manager.h"

#include <algorithm>
#include <cstring>
#include <regex>
#include <unistd.h>

#include <sys/epoll.h>
#include <sys/stat.h>

#ifdef OHOS_BUILD_ENABLE_COORDINATION
#include "coordination_util.h"
#endif // OHOS_BUILD_ENABLE_COORDINATION
#include "device.h"
#include "devicestatus_define.h"
#include "fi_log.h"
#include "napi_constants.h"

#undef LOG_TAG
#define LOG_TAG "DeviceManager"

namespace OHOS {
namespace Msdp {
namespace DeviceStatus {
namespace {
constexpr size_t EXPECTED_N_SUBMATCHES { 2 };
constexpr size_t EXPECTED_SUBMATCH { 1 };
const std::string FINGER_PRINT { "fingerprint_mouse" };
} // namespace

DeviceManager::HotplugHandler::HotplugHandler(DeviceManager &devMgr)
    : devMgr_(devMgr)
{}

void DeviceManager::HotplugHandler::AddDevice(const std::string &devNode)
{
    devMgr_.AddDevice(devNode);
}

void DeviceManager::HotplugHandler::RemoveDevice(const std::string &devNode)
{
    devMgr_.RemoveDevice(devNode);
}

DeviceManager::DeviceManager()
    : hotplug_(*this)
{}

int32_t DeviceManager::Init(IContext *context)
{
    CALL_DEBUG_ENTER;
    CHKPR(context, RET_ERR);
    int32_t ret = context->GetDelegateTasks().PostSyncTask([this, context] {
        return this->OnInit(context);
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post sync task failed");
    }
    return ret;
}

int32_t DeviceManager::OnInit(IContext *context)
{
    CALL_DEBUG_ENTER;
    CHKPR(context, RET_ERR);
    context_ = context;
    monitor_.SetDeviceMgr(&hotplug_);
    enumerator_.SetDeviceMgr(&hotplug_);
    return RET_OK;
}

int32_t DeviceManager::Enable()
{
    CALL_DEBUG_ENTER;
    CHKPR(context_, RET_ERR);
    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this] {
        return this->OnEnable();
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post sync task failed");
    }
    return ret;
}

int32_t DeviceManager::OnEnable()
{
    CALL_DEBUG_ENTER;
    epollMgr_ = std::make_shared<EpollManager>();
    int32_t ret = epollMgr_->Open();
    if (ret != RET_OK) {
        return ret;
    }
    ret = monitor_.Enable();
    if (ret != RET_OK) {
        goto CLOSE_EPOLL;
    }
    ret = epollMgr_->Add(monitor_);
    if (ret != RET_OK) {
        goto DISABLE_MONITOR;
    }
    enumerator_.ScanDevices();
    return RET_OK;

DISABLE_MONITOR:
    monitor_.Disable();

CLOSE_EPOLL:
    epollMgr_.reset();
    return ret;
}

int32_t DeviceManager::Disable()
{
    CALL_DEBUG_ENTER;
    CHKPR(context_, RET_ERR);
    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this] {
        return this->OnDisable();
    });
    if (ret != RET_OK) {
        FI_HILOGE("PostSyncTask failed");
    }
    return ret;
}

int32_t DeviceManager::OnDisable()
{
    CHKPR(epollMgr_, RET_ERR);
    epollMgr_->Remove(monitor_);
    monitor_.Disable();
    epollMgr_.reset();
    return RET_OK;
}

std::shared_ptr<IDevice> DeviceManager::FindDevice(const std::string &devPath)
{
    auto tIter = std::find_if(devices_.cbegin(), devices_.cend(),
        [devPath](const auto &item) {
            return ((item.second != nullptr) && (item.second->GetDevPath() == devPath));
        });
    return (tIter != devices_.cend() ? tIter->second : nullptr);
}

int32_t DeviceManager::ParseDeviceId(const std::string &devNode)
{
    CALL_DEBUG_ENTER;
    std::regex pattern("^event(\\d+)$");
    std::smatch mr;

    if (std::regex_match(devNode, mr, pattern)) {
        if (mr.ready() && mr.size() == EXPECTED_N_SUBMATCHES) {
            return std::stoi(mr[EXPECTED_SUBMATCH].str());
        }
    }
    return RET_ERR;
}

std::shared_ptr<IDevice> DeviceManager::AddDevice(const std::string &devNode)
{
    CALL_DEBUG_ENTER;
    const std::string SYS_INPUT_PATH { "/sys/class/input/" };
    const std::string devPath { DEV_INPUT_PATH + devNode };
    struct stat statbuf;

    if (stat(devPath.c_str(), &statbuf) != 0) {
        FI_HILOGD("Invalid device path:%{private}s", devPath.c_str());
        return nullptr;
    }
    if (!S_ISCHR(statbuf.st_mode)) {
        FI_HILOGD("Not character device:%{private}s", devPath.c_str());
        return nullptr;
    }

    int32_t deviceId = ParseDeviceId(devNode);
    if (deviceId < 0) {
        FI_HILOGE("Parsing device name failed:%{private}s", devNode.c_str());
        return nullptr;
    }

    std::shared_ptr<IDevice> dev = FindDevice(devPath);
    if (dev != nullptr) {
        FI_HILOGD("Already exists:%{private}s", devPath.c_str());
        return dev;
    }

    const std::string lSysPath { SYS_INPUT_PATH + devNode };
    char rpath[PATH_MAX];
    if (realpath(lSysPath.c_str(), rpath) == nullptr) {
        FI_HILOGD("Invalid sysPath:%{private}s", lSysPath.c_str());
        return nullptr;
    }

    dev = std::make_shared<Device>(deviceId);
    dev->SetDevPath(devPath);
    dev->SetSysPath(std::string(rpath));
    if (dev->Open() != RET_OK) {
        FI_HILOGE("Unable to open \'%{private}s\'", devPath.c_str());
        return nullptr;
    }
    auto ret = devices_.insert_or_assign(dev->GetId(), dev);
    if (ret.second) {
        FI_HILOGD("\'%{public}s\' added", dev->GetName().c_str());
        OnDeviceAdded(dev);
    }
    return dev;
}

std::shared_ptr<IDevice> DeviceManager::RemoveDevice(const std::string &devNode)
{
    CALL_DEBUG_ENTER;
    const std::string devPath { DEV_INPUT_PATH + devNode };

    for (auto devIter = devices_.begin(); devIter != devices_.end(); ++devIter) {
        std::shared_ptr<IDevice> dev = devIter->second;
        CHKPC(dev);
        if (dev->GetDevPath() == devPath) {
            devices_.erase(devIter);
            FI_HILOGD("\'%{public}s\' removed", dev->GetName().c_str());
            dev->Close();
            OnDeviceRemoved(dev);
            return dev;
        }
    }
    FI_HILOGD("\'%{public}s\' was not found", devNode.c_str());
    return nullptr;
}

void DeviceManager::OnDeviceAdded(std::shared_ptr<IDevice> dev)
{
    CHKPV(dev);
    FI_HILOGI("Add device %{private}d:%{private}s", dev->GetId(), dev->GetDevPath().c_str());
    FI_HILOGI("  sysPath:       \"%{private}s\"", dev->GetSysPath().c_str());
    FI_HILOGI("  bus:           %{public}04x", dev->GetBus());
    FI_HILOGI("  vendor:        %{public}04x", dev->GetVendor());
    FI_HILOGI("  product:       %{public}04x", dev->GetProduct());
    FI_HILOGI("  version:       %{public}04x", dev->GetVersion());
    FI_HILOGI("  name:          \"%{public}s\"", dev->GetName().c_str());
    FI_HILOGI("  location:      \"%{public}s\"", dev->GetPhys().c_str());
    FI_HILOGI("  unique id:     \"%{public}s\"", dev->GetUniq().c_str());
    FI_HILOGI("  is pointer:    %{public}s, is keyboard:%{public}s",
        dev->IsPointerDevice() ? "True" : "False", dev->IsKeyboard() ? "True" : "False");

    for (const auto &observer : observers_) {
        std::shared_ptr<IDeviceObserver> ptr = observer.lock();
        CHKPC(ptr);
        ptr->OnDeviceAdded(dev);
    }
}

void DeviceManager::OnDeviceRemoved(std::shared_ptr<IDevice> dev)
{
    for (const auto &observer : observers_) {
        std::shared_ptr<IDeviceObserver> ptr = observer.lock();
        CHKPC(ptr);
        ptr->OnDeviceRemoved(dev);
    }
}

void DeviceManager::Dispatch(const struct epoll_event &ev)
{
    CALL_DEBUG_ENTER;
    CHKPV(context_);
    int32_t ret = context_->GetDelegateTasks().PostAsyncTask([this, &ev] {
        return this->OnEpollDispatch(ev.events);
    });
    if (ret != RET_OK) {
        FI_HILOGE("PostAsyncTask failed");
    }
}

int32_t DeviceManager::OnEpollDispatch(uint32_t events)
{
    struct epoll_event ev {};
    ev.events = events;
    ev.data.ptr = epollMgr_.get();

    CHKPR(epollMgr_, RET_ERR);
    epollMgr_->Dispatch(ev);
    return RET_OK;
}

std::shared_ptr<IDevice> DeviceManager::GetDevice(int32_t id) const
{
    CHKPP(context_);
    std::packaged_task<std::shared_ptr<IDevice>(int32_t)> task {[this](int32_t id) {
        return this->OnGetDevice(id);
    }};
    auto fu = task.get_future();

    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this, &task, id] {
        return this->RunGetDevice(std::ref(task), id);
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post task failed");
        return nullptr;
    }
    return fu.get();
}

std::shared_ptr<IDevice> DeviceManager::OnGetDevice(int32_t id) const
{
    if (auto devIter = devices_.find(id); devIter != devices_.cend()) {
        return devIter->second;
    }
    FI_HILOGE("Device id not found");
    return nullptr;
}

int32_t DeviceManager::RunGetDevice(std::packaged_task<std::shared_ptr<IDevice>(int32_t)> &task,
                                    int32_t id) const
{
    task(id);
    return RET_OK;
}

void DeviceManager::RetriggerHotplug(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPV(context_);
    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this, observer] {
        return this->OnRetriggerHotplug(observer);
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post task failed");
    }
}

int32_t DeviceManager::OnRetriggerHotplug(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPR(observer, RET_ERR);
    std::shared_ptr<IDeviceObserver> ptr = observer.lock();
    CHKPR(ptr, RET_ERR);
    std::for_each(devices_.cbegin(), devices_.cend(),
        [ptr] (const auto &item) {
            if (item.second != nullptr) {
                ptr->OnDeviceAdded(item.second);
            }
        });
    return RET_OK;
}

int32_t DeviceManager::AddDeviceObserver(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPR(context_, RET_ERR);
    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this, observer] {
        return this->OnAddDeviceObserver(observer);
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post task failed");
    }
    return ret;
}

int32_t DeviceManager::OnAddDeviceObserver(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPR(observer, RET_ERR);
    auto ret = observers_.insert(observer);
    if (!ret.second) {
        FI_HILOGW("Observer is duplicated");
    }
    return RET_OK;
}

void DeviceManager::RemoveDeviceObserver(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPV(context_);
    int32_t ret = context_->GetDelegateTasks().PostSyncTask([this, observer] {
        return this->OnRemoveDeviceObserver(observer);
    });
    if (ret != RET_OK) {
        FI_HILOGE("Post task failed");
    }
}

int32_t DeviceManager::OnRemoveDeviceObserver(std::weak_ptr<IDeviceObserver> observer)
{
    CALL_DEBUG_ENTER;
    CHKPR(observer, RET_ERR);
    observers_.erase(observer);
    return RET_OK;
}

bool DeviceManager::AnyOf(std::function<bool(std::shared_ptr<IDevice>)> pred)
{
    return std::any_of(devices_.cbegin(), devices_.cend(), [pred](const auto &item) {
        return (pred != nullptr ? pred(item.second) : false);
    });
}

bool DeviceManager::HasLocalPointerDevice()
{
    return AnyOf([this](std::shared_ptr<IDevice> dev) {
        if ((dev == nullptr) || (dev->GetName() == FINGER_PRINT)) {
            return false;
        }
        return (dev->IsPointerDevice() && !dev->IsRemote());
    });
}

bool DeviceManager::HasLocalKeyboardDevice()
{
    return AnyOf([this](std::shared_ptr<IDevice> dev) {
        CHKPF(dev);
        return (dev->IsKeyboard() && !dev->IsRemote());
    });
}

bool DeviceManager::HasKeyboard()
{
    return AnyOf([this](std::shared_ptr<IDevice> dev) {
        if ((dev == nullptr)) {
            return false;
        }
        return (dev->IsKeyboard() && !dev->IsRemote() &&
            dev->GetKeyboardType() == IDevice::KeyboardType::KEYBOARD_TYPE_ALPHABETICKEYBOARD);
    });
}

std::vector<std::shared_ptr<IDevice>> DeviceManager::GetKeyboard()
{
    std::vector<std::shared_ptr<IDevice>> keyboards;
    for (const auto &dev : devices_) {
        if (dev.second->IsKeyboard() && !dev.second->IsRemote() &&
            dev.second->GetKeyboardType() == IDevice::KeyboardType::KEYBOARD_TYPE_ALPHABETICKEYBOARD) {
            keyboards.push_back(dev.second);
        }
    }
    return keyboards;
}
} // namespace DeviceStatus
} // namespace Msdp
} // namespace OHOS