/*
 * Copyright (c) 2026 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 <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <iostream>
#include <string>
#include <vector>

#include <securec.h>
#include "distributedcameratest.h"
#include "dcamera_hdf_operate.h"
#include "distributed_hardware_log.h"
#include "v1_1/id_camera_provider.h"
#include "v1_1/dcamera_types.h"
#include "token_setproc.h"
#include "nativetoken_kit.h"
#include "v1_3/icamera_host.h"

#include "v1_0/icamera_host.h"
#include "v1_3/icamera_host.h"
#include "v1_0/icamera_host_callback.h"
#include "v1_0/icamera_device_callback.h"
#include "v1_3/icamera_device.h"
#include "accesstoken_kit.h"
#include "camera_metadata_info.h"
#include "metadata_utils.h"
#include "idevmgr_hdi.h"

namespace {
constexpr int32_t RET_OK = 0;
constexpr int32_t RET_ERR = -1;

// connectionType 取值(OHOS_ABILITY_CAMERA_CONNECTION_TYPE):0=本机,1=USB,2=远端
constexpr uint8_t CONNECTION_TYPE_BUILTIN = 0;
constexpr uint8_t CONNECTION_TYPE_USB = 1;
constexpr uint8_t CONNECTION_TYPE_REMOTE = 2;

const int32_t CMD_QUIT = 0;
const int32_t CMD_FIND = 1;
const int32_t CMD_SELECT_CAMERA = 2;
const int32_t CMD_OPEN_CAMERA = 3;
const int32_t CMD_CLOSE_CAMERA = 4;
const int32_t CMD_INPUT_TOKENID = 5;
constexpr const char *DCAMERA_SERVICE = "distributed_camera_service";

enum  DeviceStatus {
    DEVICE_IDLE = 0,
    DEVICE_OPEN = 1,
    DEVICE_START = 2,
    DEVICE_STOP = 3,
};

uint64_t g_inputTokenId = 0;
static DeviceStatus g_cameraStatus = DeviceStatus::DEVICE_IDLE;
static std::string g_serviceName = DCAMERA_SERVICE;
static std::vector<std::string> g_cameraIds;
static size_t g_selectCameraIndex = 0;
static OHOS::sptr<OHOS::HDI::Camera::V1_0::ICameraHost> g_cameraHost = nullptr;
static OHOS::sptr<OHOS::HDI::Camera::V1_3::ICameraHost> g_cameraHostV1_3 = nullptr;
static OHOS::sptr<OHOS::HDI::Camera::V1_3::ICameraDevice> g_cameraDevice = nullptr;

static const char *ConnectionTypeStr(uint8_t type)
{
    switch (type) {
        case CONNECTION_TYPE_REMOTE:
            return "REMOTE(对端)";
        case CONNECTION_TYPE_USB:
            return "USB";
        case CONNECTION_TYPE_BUILTIN:
        default:
            return "BUILTIN(本端)";
    }
}

static uint8_t GetCameraConnectionType(const std::string &cameraId)
{
    if (g_cameraHost == nullptr) {
        return CONNECTION_TYPE_BUILTIN;
    }
    std::vector<uint8_t> abilityVec;
    int32_t ret = g_cameraHost->GetCameraAbility(cameraId, abilityVec);
    if (ret != OHOS::HDI::Camera::V1_0::NO_ERROR || abilityVec.empty()) {
        return CONNECTION_TYPE_BUILTIN;
    }
    std::shared_ptr<OHOS::Camera::CameraMetadata> ability;
    OHOS::Camera::MetadataUtils::ConvertVecToMetadata(abilityVec, ability);
    if (ability == nullptr) {
        return CONNECTION_TYPE_BUILTIN;
    }
    camera_metadata_item_t item;
    ret = OHOS::Camera::FindCameraMetadataItem(ability->get(), OHOS_ABILITY_CAMERA_CONNECTION_TYPE, &item);
    if (ret == CAM_META_SUCCESS && item.count > 0) {
        return item.data.u8[0];
    }
    return CONNECTION_TYPE_BUILTIN;
}

class DemoCameraDeviceCallback : public OHOS::HDI::Camera::V1_0::ICameraDeviceCallback {
public:
    DemoCameraDeviceCallback() = default;
    ~DemoCameraDeviceCallback() = default;

    int32_t OnError(OHOS::HDI::Camera::V1_0::ErrorType type, int32_t errorCode) override
    {
        std::cout << "DeviceCallback OnError type:" << type << " code:" << errorCode << std::endl;
        return OHOS::HDI::Camera::V1_0::NO_ERROR;
    }

    int32_t OnResult(uint64_t timestamp, const std::vector<uint8_t>& result) override
    {
        return OHOS::HDI::Camera::V1_0::NO_ERROR;
    }
};

class DemoCameraHostCallback : public OHOS::HDI::Camera::V1_0::ICameraHostCallback {
public:
    DemoCameraHostCallback() = default;
    ~DemoCameraHostCallback() = default;

    int32_t OnCameraStatus(const std::string& cameraId, OHOS::HDI::Camera::V1_0::CameraStatus status) override
    {
        std::cout << "HostCallback OnCameraStatus id:" << cameraId << " status:" << static_cast<int32_t>(status)
                  << std::endl;
        return OHOS::HDI::Camera::V1_0::NO_ERROR;
    }

    int32_t OnFlashlightStatus(const std::string& cameraId, OHOS::HDI::Camera::V1_0::FlashlightStatus status) override
    {
        std::cout << "HostCallback OnFlashlightStatus id:" << cameraId
                  << " status:" << static_cast<int32_t>(status) << std::endl;
        return OHOS::HDI::Camera::V1_0::NO_ERROR;
    }

    int32_t OnCameraEvent(const std::string& cameraId, OHOS::HDI::Camera::V1_0::CameraEvent event) override
    {
        std::cout << "HostCallback OnCameraEvent id:" << cameraId << " event:" << static_cast<int32_t>(event)
                  << std::endl;
        return OHOS::HDI::Camera::V1_0::NO_ERROR;
    }
};

static int32_t GetUserInput()
{
    int32_t res = -1;
    size_t count = 3;
    std::cout << ">>";
    int ret = scanf_s("%d", &res);
    if (ret == -1) {
        std::cout << "get input error" << std::endl;
    }
    while (std::cin.fail() && count > 0) {
        std::cin.clear();
        std::cin.ignore();
        std::cout << "invalid input, not a number! Please retry with a number." << std::endl;
        std::cout << ">>";
        ret = scanf_s("%d", &res);
        if (ret == -1) {
            std::cout << "get input error" << std::endl;
        }
        count--;
    }
    return res;
}

static void InputTokenId()
{
    std::cout << "Please input tokenId." << std::endl;
    int ret = scanf_s("%llu", &g_inputTokenId);
    if (ret != 1) {
        std::cout << "get input error" << std::endl;
    }
}

static void SetPermissions()
{
    const char *perms[1];
    perms[0] = "ohos.permission.CAMERA";
    NativeTokenInfoParams infoInstance = {
        .dcapsNum = 0,
        .permsNum = 1,
        .aclsNum = 0,
        .dcaps = NULL,
        .perms = perms,
        .acls = NULL,
        .processName = "camera_host_sample",
        .aplStr = "system_basic",
    };
    uint64_t tokenId = GetAccessTokenId(&infoInstance);
    SetSelfTokenID(tokenId);
    OHOS::Security::AccessToken::AccessTokenKit::ReloadNativeTokenInfo();
}

static int32_t InitTestDemo()
{
    std::cout << "**********************************************************************************" << std::endl;
    std::cout << "Camera Host Sample (ICameraHost V1_3) v1.0" << std::endl;
    std::cout << "**********************************************************************************" << std::endl;
    std::cout << "Init camera host service, name: " << g_serviceName << std::endl;

    g_cameraHost = OHOS::HDI::Camera::V1_0::ICameraHost::Get(g_serviceName.c_str(), false);
    if (g_cameraHost == nullptr) {
        std::cout << "Get ICameraHost failed, check camera_host service." << std::endl;
        return RET_ERR;
    }
    std::cout << "Get V1_0 ICameraHost success." << std::endl;

    uint32_t majorVer = 0;
    uint32_t minorVer = 0;
    g_cameraHost->GetVersion(majorVer, minorVer);
    std::cout << "CameraHost version: " << majorVer << "." << minorVer << std::endl;

    g_cameraHostV1_3 = OHOS::HDI::Camera::V1_3::ICameraHost::CastFrom(g_cameraHost);
    if (g_cameraHostV1_3 == nullptr) {
        std::cout << "Cast to V1_3 ICameraHost failed, host does not support V1_3." << std::endl;
    } else {
        std::cout << "Cast to V1_3 ICameraHost success." << std::endl;
    }

    OHOS::sptr<DemoCameraHostCallback> hostCallback = new DemoCameraHostCallback();
    int32_t ret = g_cameraHost->SetCallback(hostCallback);
    if (ret != OHOS::HDI::Camera::V1_0::NO_ERROR) {
        std::cout << "SetCallback failed, ret: " << ret << std::endl;
    } else {
        std::cout << "SetCallback success." << std::endl;
    }

    ret = g_cameraHost->GetCameraIds(g_cameraIds);
    if (ret != OHOS::HDI::Camera::V1_0::NO_ERROR) {
        std::cout << "GetCameraIds failed, ret: " << ret << std::endl;
        return RET_ERR;
    }
    std::cout << "GetCameraIds success, count: " << g_cameraIds.size() << std::endl;
    for (const auto &id : g_cameraIds) {
        std::cout << "  cameraId: " << id << std::endl;
    }
    return RET_OK;
}

static void OpenCamera()
{
    if (g_cameraStatus != DeviceStatus::DEVICE_IDLE) {
        std::cout << "Camera device is already opened." << std::endl;
        return;
    }
    if (g_cameraHostV1_3 == nullptr) {
        std::cout << "V1_3 ICameraHost is null, cannot OpenCamera_V1_3." << std::endl;
        return;
    }
    if (g_cameraIds.empty()) {
        std::cout << "No camera id available, run find first." << std::endl;
        return;
    }

    if (g_inputTokenId != 0) {
        SetFirstCallerTokenID(g_inputTokenId);
    }

    std::string cameraId = g_cameraIds.front();
    if (g_selectCameraIndex < g_cameraIds.size()) {
        cameraId = g_cameraIds[g_selectCameraIndex];
    }
    uint8_t connType = GetCameraConnectionType(cameraId);
    std::cout << "Opening cameraId: " << cameraId << " connectionType: " << ConnectionTypeStr(connType) << std::endl;
    OHOS::sptr<OHOS::HDI::Camera::V1_0::ICameraDeviceCallback> callback = new DemoCameraDeviceCallback();
    OHOS::sptr<OHOS::HDI::Camera::V1_3::ICameraDevice> device = nullptr;
    int32_t ret = g_cameraHostV1_3->OpenCamera_V1_3(cameraId, callback, device);
    if (ret != OHOS::HDI::Camera::V1_0::NO_ERROR || device == nullptr) {
        std::cout << "OpenCamera_V1_3 failed, ret: " << ret << std::endl;
        return;
    }
    g_cameraDevice = device;
    g_cameraStatus = DeviceStatus::DEVICE_OPEN;
    std::cout << "OpenCamera_V1_3 success, cameraId: " << cameraId << std::endl;
}

static void CloseCamera()
{
    if (g_cameraStatus == DeviceStatus::DEVICE_IDLE) {
        std::cout << "Camera device is not opened." << std::endl;
        return;
    }
    if (g_cameraDevice != nullptr) {
        g_cameraDevice->Close();
        g_cameraDevice = nullptr;
    }
    g_cameraStatus = DeviceStatus::DEVICE_IDLE;
    std::cout << "CloseCamera success." << std::endl;
}

static void FindCameraDevice()
{
    if (g_cameraHost == nullptr) {
        g_cameraHost = OHOS::HDI::Camera::V1_0::ICameraHost::Get(g_serviceName.c_str(), false);
        if (g_cameraHost == nullptr) {
            std::cout << "Camera host is null, check service." << std::endl;
            return;
        }
    }
    if (g_cameraHostV1_3 == nullptr) {
        g_cameraHostV1_3 = OHOS::HDI::Camera::V1_3::ICameraHost::CastFrom(g_cameraHost);
    }
    int32_t ret = g_cameraHost->GetCameraIds(g_cameraIds);
    if (ret != OHOS::HDI::Camera::V1_0::NO_ERROR) {
        std::cout << "GetCameraIds failed, ret: " << ret << std::endl;
        return;
    }
    std::cout << "Find camera device success, count: " << g_cameraIds.size() << std::endl;
    for (size_t i = 0; i < g_cameraIds.size(); i++) {
        uint8_t connType = GetCameraConnectionType(g_cameraIds[i]);
        std::cout << "  [" << i << "] cameraId: " << g_cameraIds[i]
                  << "  connectionType: " << ConnectionTypeStr(connType) << std::endl;
    }
    if (g_selectCameraIndex >= g_cameraIds.size()) {
        g_selectCameraIndex = 0;
    }
    std::cout << "current select index: " << g_selectCameraIndex
              << " (use 14 to change)" << std::endl;
}

static void SelectCamera()
{
    if (g_cameraIds.empty()) {
        std::cout << "No camera id available, run find(9) first." << std::endl;
        return;
    }
    std::cout << "Please input camera index (0-" << (g_cameraIds.size() - 1) << ")." << std::endl;
    int32_t index = -1;
    int ret = scanf_s("%d", &index);
    if (ret == -1) {
        std::cout << "get input error" << std::endl;
    }
    if (index < 0 || static_cast<size_t>(index) >= g_cameraIds.size()) {
        std::cout << "invalid index: " << index << std::endl;
        return;
    }
    g_selectCameraIndex = static_cast<size_t>(index);
    uint8_t connType = GetCameraConnectionType(g_cameraIds[g_selectCameraIndex]);
    std::cout << "selected [" << g_selectCameraIndex << "] " << g_cameraIds[g_selectCameraIndex]
              << " connectionType: " << ConnectionTypeStr(connType) << std::endl;
}

static void HandleCameraEvent(const int32_t cmd)
{
    switch (cmd) {
        case CMD_FIND:
            FindCameraDevice();
            break;
        case CMD_OPEN_CAMERA:
            OpenCamera();
            break;
        case CMD_CLOSE_CAMERA:
            CloseCamera();
            break;
        case CMD_INPUT_TOKENID:
            InputTokenId();
            break;
        case CMD_SELECT_CAMERA:
            SelectCamera();
            break;
        default:
            std::cout << "Unknown operation." << std::endl;
            break;
    }
}

static void PrintInteractiveUsage()
{
    std::cout << std::endl << "=============== InteractiveRunTestSelect ================" << std::endl;
    std::cout << "You can respond to instructions for corresponding option:" << std::endl;
    std::cout << "\t enter 1 to find camera device. " << std::endl;
    std::cout << "\t enter 2 to select camera index (find first). " << std::endl;
    std::cout << "\t enter 3 to open camera (selected index). " << std::endl;
    std::cout << "\t enter 4 to close camera. " << std::endl;
    std::cout << "\t enter 5 to input tokenId. " << std::endl;
    std::cout << "\t enter 0 to exit. " << std::endl;
    std::cout << "hint: use service \"camera_service\"; the remote (对端) camera is the id whose "
              << "connectionType == REMOTE(对端)." << std::endl;
}
} // namespace

int main(int argc, char *argv[])
{
    SetPermissions();
    if (InitTestDemo() != RET_OK) {
        std::cout << "InitTestDemo failed, retry find(9) after camera_host ready, or exit(0)." << std::endl;
    }
    bool quit = false;
    while (!quit) {
        PrintInteractiveUsage();
        int32_t cmd = GetUserInput();
        if (cmd == CMD_QUIT) {
            CloseCamera();
            quit = true;   // 退出循环
        } else {
            HandleCameraEvent(cmd);
        }
    }
    return 0;
}