// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <thread>
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/irs.h"

namespace InputCommon::Joycon {

IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
    : JoyconCommonProtocol(std::move(handle)) {}

DriverResult IrsProtocol::EnableIrs() {
    LOG_INFO(Input, "Enable IRS");
    DriverResult result{DriverResult::Success};
    SetBlocking();

    if (result == DriverResult::Success) {
        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
    }
    if (result == DriverResult::Success) {
        result = EnableMCU(true);
    }
    if (result == DriverResult::Success) {
        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
    }
    if (result == DriverResult::Success) {
        const MCUConfig config{
            .command = MCUCommand::ConfigureMCU,
            .sub_command = MCUSubCommand::SetMCUMode,
            .mode = MCUMode::IR,
            .crc = {},
        };

        result = ConfigureMCU(config);
    }
    if (result == DriverResult::Success) {
        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
    }
    if (result == DriverResult::Success) {
        result = ConfigureIrs();
    }
    if (result == DriverResult::Success) {
        result = WriteRegistersStep1();
    }
    if (result == DriverResult::Success) {
        result = WriteRegistersStep2();
    }

    is_enabled = true;

    SetNonBlocking();
    return result;
}

DriverResult IrsProtocol::DisableIrs() {
    LOG_DEBUG(Input, "Disable IRS");
    DriverResult result{DriverResult::Success};
    SetBlocking();

    if (result == DriverResult::Success) {
        result = EnableMCU(false);
    }

    is_enabled = false;

    SetNonBlocking();
    return result;
}

DriverResult IrsProtocol::ConfigureIrs() {
    LOG_DEBUG(Input, "Configure IRS");
    constexpr std::size_t max_tries = 28;
    std::vector<u8> output;
    std::size_t tries = 0;

    const IrsConfigure irs_configuration{
        .command = MCUCommand::ConfigureIR,
        .sub_command = MCUSubCommand::SetDeviceMode,
        .irs_mode = IrsMode::ImageTransfer,
        .number_of_fragments = 0x3,
        .mcu_major_version = 0x0500,
        .mcu_minor_version = 0x1800,
        .crc = {},
    };

    std::vector<u8> request_data(sizeof(IrsConfigure));
    memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
    do {
        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);

        if (result != DriverResult::Success) {
            return result;
        }
        if (tries++ >= max_tries) {
            return DriverResult::WrongReply;
        }
    } while (output[15] != 0x0b);

    return DriverResult::Success;
}

DriverResult IrsProtocol::WriteRegistersStep1() {
    LOG_DEBUG(Input, "Configure IRS");
    DriverResult result{DriverResult::Success};
    constexpr std::size_t max_tries = 28;
    std::vector<u8> output;
    std::size_t tries = 0;

    const IrsWriteRegisters irs_registers{
        .command = MCUCommand::ConfigureIR,
        .sub_command = MCUSubCommand::WriteDeviceRegisters,
        .number_of_registers = 0x9,
        .registers =
            {
                IrsRegister{0x2e00, resolution},
                {0x3001, static_cast<u8>(exposure & 0xff)},
                {0x3101, static_cast<u8>(exposure >> 8)},
                {0x3201, 0x00},
                {0x1000, leds},
                {0x2e01, static_cast<u8>((digital_gain & 0x0f) << 4)},
                {0x2f01, static_cast<u8>((digital_gain & 0xf0) >> 4)},
                {0x0e00, ex_light_filter},
                {0x4301, 0xc8},
            },
        .crc = {},
    };

    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);

    std::array<u8, 38> mcu_request{0x02};
    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
    mcu_request[37] = 0xFF;

    if (result != DriverResult::Success) {
        return result;
    }

    do {
        result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);

        // First time we need to set the report mode
        if (result == DriverResult::Success && tries == 0) {
            result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
        }
        if (result == DriverResult::Success && tries == 0) {
            GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
        }

        if (result != DriverResult::Success) {
            return result;
        }
        if (tries++ >= max_tries) {
            return DriverResult::WrongReply;
        }
    } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);

    return DriverResult::Success;
}

DriverResult IrsProtocol::WriteRegistersStep2() {
    LOG_DEBUG(Input, "Configure IRS");
    constexpr std::size_t max_tries = 28;
    std::vector<u8> output;
    std::size_t tries = 0;

    const IrsWriteRegisters irs_registers{
        .command = MCUCommand::ConfigureIR,
        .sub_command = MCUSubCommand::WriteDeviceRegisters,
        .number_of_registers = 0x8,
        .registers =
            {
                IrsRegister{0x1100, static_cast<u8>(led_intensity >> 8)},
                {0x1200, static_cast<u8>(led_intensity & 0xff)},
                {0x2d00, image_flip},
                {0x6701, static_cast<u8>((denoise >> 16) & 0xff)},
                {0x6801, static_cast<u8>((denoise >> 8) & 0xff)},
                {0x6901, static_cast<u8>(denoise & 0xff)},
                {0x0400, 0x2d},
                {0x0700, 0x01},
            },
        .crc = {},
    };

    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
    do {
        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);

        if (result != DriverResult::Success) {
            return result;
        }
        if (tries++ >= max_tries) {
            return DriverResult::WrongReply;
        }
    } while (output[15] != 0x13 && output[15] != 0x23);

    return DriverResult::Success;
}

bool IrsProtocol::IsEnabled() const {
    return is_enabled;
}

} // namespace InputCommon::Joycon