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

#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/frontend/applet_software_keyboard.h"
#include "core/hle/service/am/service/storage.h"

namespace Service::AM::Frontend {

namespace {

// The maximum number of UTF-16 characters that can be input into the swkbd text field.
constexpr u32 DEFAULT_MAX_TEXT_LENGTH = 500;

constexpr std::size_t REPLY_BASE_SIZE = sizeof(SwkbdState) + sizeof(SwkbdReplyType);
constexpr std::size_t REPLY_UTF8_SIZE = 0x7D4;
constexpr std::size_t REPLY_UTF16_SIZE = 0x3EC;

constexpr const char* GetTextCheckResultName(SwkbdTextCheckResult text_check_result) {
    switch (text_check_result) {
    case SwkbdTextCheckResult::Success:
        return "Success";
    case SwkbdTextCheckResult::Failure:
        return "Failure";
    case SwkbdTextCheckResult::Confirm:
        return "Confirm";
    case SwkbdTextCheckResult::Silent:
        return "Silent";
    default:
        UNIMPLEMENTED_MSG("Unknown TextCheckResult={}", text_check_result);
        return "Unknown";
    }
}

void SetReplyBase(std::vector<u8>& reply, SwkbdState state, SwkbdReplyType reply_type) {
    std::memcpy(reply.data(), &state, sizeof(SwkbdState));
    std::memcpy(reply.data() + sizeof(SwkbdState), &reply_type, sizeof(SwkbdReplyType));
}

} // Anonymous namespace

SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, std::shared_ptr<Applet> applet_,
                                   LibraryAppletMode applet_mode_,
                                   Core::Frontend::SoftwareKeyboardApplet& frontend_)
    : FrontendApplet{system_, applet_, applet_mode_}, frontend{frontend_} {}

SoftwareKeyboard::~SoftwareKeyboard() = default;

void SoftwareKeyboard::Initialize() {
    FrontendApplet::Initialize();

    LOG_INFO(Service_AM, "Initializing Software Keyboard Applet with LibraryAppletMode={}",
             applet_mode);

    LOG_DEBUG(Service_AM,
              "Initializing Applet with common_args: arg_version={}, lib_version={}, "
              "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
              common_args.arguments_version, common_args.library_version,
              common_args.play_startup_sound, common_args.size, common_args.system_tick,
              common_args.theme_color);

    swkbd_applet_version = SwkbdAppletVersion{common_args.library_version};

    switch (applet_mode) {
    case LibraryAppletMode::AllForeground:
        InitializeForeground();
        break;
    case LibraryAppletMode::PartialForeground:
    case LibraryAppletMode::PartialForegroundIndirectDisplay:
        InitializePartialForeground(applet_mode);
        break;
    default:
        ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
        break;
    }
}

Result SoftwareKeyboard::GetStatus() const {
    return status;
}

void SoftwareKeyboard::ExecuteInteractive() {
    if (complete) {
        return;
    }

    if (is_background) {
        ProcessInlineKeyboardRequest();
    } else {
        ProcessTextCheck();
    }
}

void SoftwareKeyboard::Execute() {
    if (complete) {
        return;
    }

    if (is_background) {
        return;
    }

    ShowNormalKeyboard();
}

void SoftwareKeyboard::SubmitTextNormal(SwkbdResult result, std::u16string submitted_text,
                                        bool confirmed) {
    if (complete) {
        return;
    }

    if (swkbd_config_common.use_text_check && result == SwkbdResult::Ok) {
        if (confirmed) {
            SubmitNormalOutputAndExit(result, submitted_text);
        } else {
            SubmitForTextCheck(submitted_text);
        }
    } else {
        SubmitNormalOutputAndExit(result, submitted_text);
    }
}

void SoftwareKeyboard::SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text,
                                        s32 cursor_position) {
    if (complete) {
        return;
    }

    current_text = std::move(submitted_text);
    current_cursor_position = cursor_position;

    if (inline_use_utf8) {
        switch (reply_type) {
        case SwkbdReplyType::ChangedString:
            reply_type = SwkbdReplyType::ChangedStringUtf8;
            break;
        case SwkbdReplyType::MovedCursor:
            reply_type = SwkbdReplyType::MovedCursorUtf8;
            break;
        case SwkbdReplyType::DecidedEnter:
            reply_type = SwkbdReplyType::DecidedEnterUtf8;
            break;
        default:
            break;
        }
    }

    if (use_changed_string_v2) {
        switch (reply_type) {
        case SwkbdReplyType::ChangedString:
            reply_type = SwkbdReplyType::ChangedStringV2;
            break;
        case SwkbdReplyType::ChangedStringUtf8:
            reply_type = SwkbdReplyType::ChangedStringUtf8V2;
            break;
        default:
            break;
        }
    }

    if (use_moved_cursor_v2) {
        switch (reply_type) {
        case SwkbdReplyType::MovedCursor:
            reply_type = SwkbdReplyType::MovedCursorV2;
            break;
        case SwkbdReplyType::MovedCursorUtf8:
            reply_type = SwkbdReplyType::MovedCursorUtf8V2;
            break;
        default:
            break;
        }
    }

    SendReply(reply_type);
}

void SoftwareKeyboard::InitializeForeground() {
    LOG_INFO(Service_AM, "Initializing Normal Software Keyboard Applet.");

    is_background = false;

    const auto swkbd_config_storage = PopInData();
    ASSERT(swkbd_config_storage != nullptr);

    const auto& swkbd_config_data = swkbd_config_storage->GetData();
    ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon));

    std::memcpy(&swkbd_config_common, swkbd_config_data.data(), sizeof(SwkbdConfigCommon));

    switch (swkbd_applet_version) {
    case SwkbdAppletVersion::Version5:
    case SwkbdAppletVersion::Version65542:
        ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld));
        std::memcpy(&swkbd_config_old, swkbd_config_data.data() + sizeof(SwkbdConfigCommon),
                    sizeof(SwkbdConfigOld));
        break;
    case SwkbdAppletVersion::Version196615:
    case SwkbdAppletVersion::Version262152:
    case SwkbdAppletVersion::Version327689:
        ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld2));
        std::memcpy(&swkbd_config_old2, swkbd_config_data.data() + sizeof(SwkbdConfigCommon),
                    sizeof(SwkbdConfigOld2));
        break;
    case SwkbdAppletVersion::Version393227:
    case SwkbdAppletVersion::Version524301:
        ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew));
        std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon),
                    sizeof(SwkbdConfigNew));
        break;
    default:
        UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version,
                          swkbd_config_data.size());
        ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew));
        std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon),
                    sizeof(SwkbdConfigNew));
        break;
    }

    const auto work_buffer_storage = PopInData();
    ASSERT(work_buffer_storage != nullptr);

    if (swkbd_config_common.initial_string_length == 0) {
        InitializeFrontendNormalKeyboard();
        return;
    }

    const auto& work_buffer = work_buffer_storage->GetData();

    std::vector<char16_t> initial_string(swkbd_config_common.initial_string_length);

    std::memcpy(initial_string.data(),
                work_buffer.data() + swkbd_config_common.initial_string_offset,
                swkbd_config_common.initial_string_length * sizeof(char16_t));

    initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(initial_string.data(),
                                                                    initial_string.size());

    LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text));

    InitializeFrontendNormalKeyboard();
}

void SoftwareKeyboard::InitializePartialForeground(LibraryAppletMode library_applet_mode) {
    LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet.");

    is_background = true;

    const auto swkbd_inline_initialize_arg_storage = PopInData();
    ASSERT(swkbd_inline_initialize_arg_storage != nullptr);

    const auto& swkbd_inline_initialize_arg = swkbd_inline_initialize_arg_storage->GetData();
    ASSERT(swkbd_inline_initialize_arg.size() == sizeof(SwkbdInitializeArg));

    std::memcpy(&swkbd_initialize_arg, swkbd_inline_initialize_arg.data(),
                swkbd_inline_initialize_arg.size());

    if (swkbd_initialize_arg.library_applet_mode_flag) {
        ASSERT(library_applet_mode == LibraryAppletMode::PartialForeground);
    } else {
        ASSERT(library_applet_mode == LibraryAppletMode::PartialForegroundIndirectDisplay);
    }
}

void SoftwareKeyboard::ProcessTextCheck() {
    const auto text_check_storage = PopInteractiveInData();
    ASSERT(text_check_storage != nullptr);

    const auto& text_check_data = text_check_storage->GetData();
    ASSERT(text_check_data.size() == sizeof(SwkbdTextCheck));

    SwkbdTextCheck swkbd_text_check;

    std::memcpy(&swkbd_text_check, text_check_data.data(), sizeof(SwkbdTextCheck));

    std::u16string text_check_message = [this, &swkbd_text_check]() -> std::u16string {
        if (swkbd_text_check.text_check_result == SwkbdTextCheckResult::Failure ||
            swkbd_text_check.text_check_result == SwkbdTextCheckResult::Confirm) {
            return swkbd_config_common.use_utf8
                       ? Common::UTF8ToUTF16(Common::StringFromFixedZeroTerminatedBuffer(
                             reinterpret_cast<const char*>(
                                 swkbd_text_check.text_check_message.data()),
                             swkbd_text_check.text_check_message.size() * sizeof(char16_t)))
                       : Common::UTF16StringFromFixedZeroTerminatedBuffer(
                             swkbd_text_check.text_check_message.data(),
                             swkbd_text_check.text_check_message.size());
        } else {
            return u"";
        }
    }();

    LOG_INFO(Service_AM, "\nTextCheckResult: {}\nTextCheckMessage: {}",
             GetTextCheckResultName(swkbd_text_check.text_check_result),
             Common::UTF16ToUTF8(text_check_message));

    switch (swkbd_text_check.text_check_result) {
    case SwkbdTextCheckResult::Success:
        SubmitNormalOutputAndExit(SwkbdResult::Ok, current_text);
        break;
    case SwkbdTextCheckResult::Failure:
        ShowTextCheckDialog(SwkbdTextCheckResult::Failure, std::move(text_check_message));
        break;
    case SwkbdTextCheckResult::Confirm:
        ShowTextCheckDialog(SwkbdTextCheckResult::Confirm, std::move(text_check_message));
        break;
    case SwkbdTextCheckResult::Silent:
    default:
        break;
    }
}

void SoftwareKeyboard::ProcessInlineKeyboardRequest() {
    const auto request_data_storage = PopInteractiveInData();
    ASSERT(request_data_storage != nullptr);

    const auto& request_data = request_data_storage->GetData();
    ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand));

    SwkbdRequestCommand request_command;

    std::memcpy(&request_command, request_data.data(), sizeof(SwkbdRequestCommand));

    switch (request_command) {
    case SwkbdRequestCommand::Finalize:
        RequestFinalize(request_data);
        break;
    case SwkbdRequestCommand::SetUserWordInfo:
        RequestSetUserWordInfo(request_data);
        break;
    case SwkbdRequestCommand::SetCustomizeDic:
        RequestSetCustomizeDic(request_data);
        break;
    case SwkbdRequestCommand::Calc:
        RequestCalc(request_data);
        break;
    case SwkbdRequestCommand::SetCustomizedDictionaries:
        RequestSetCustomizedDictionaries(request_data);
        break;
    case SwkbdRequestCommand::UnsetCustomizedDictionaries:
        RequestUnsetCustomizedDictionaries(request_data);
        break;
    case SwkbdRequestCommand::SetChangedStringV2Flag:
        RequestSetChangedStringV2Flag(request_data);
        break;
    case SwkbdRequestCommand::SetMovedCursorV2Flag:
        RequestSetMovedCursorV2Flag(request_data);
        break;
    default:
        UNIMPLEMENTED_MSG("Unknown SwkbdRequestCommand={}", request_command);
        break;
    }
}

void SoftwareKeyboard::SubmitNormalOutputAndExit(SwkbdResult result,
                                                 std::u16string submitted_text) {
    std::vector<u8> out_data(sizeof(SwkbdResult) + STRING_BUFFER_SIZE);

    if (swkbd_config_common.use_utf8) {
        std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text);

        LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-8 Submitted Text: {}", result,
                  utf8_submitted_text);

        std::memcpy(out_data.data(), &result, sizeof(SwkbdResult));
        std::memcpy(out_data.data() + sizeof(SwkbdResult), utf8_submitted_text.data(),
                    utf8_submitted_text.size());
    } else {
        LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-16 Submitted Text: {}", result,
                  Common::UTF16ToUTF8(submitted_text));

        std::memcpy(out_data.data(), &result, sizeof(SwkbdResult));
        std::memcpy(out_data.data() + sizeof(SwkbdResult), submitted_text.data(),
                    submitted_text.size() * sizeof(char16_t));
    }

    PushOutData(std::make_shared<IStorage>(system, std::move(out_data)));

    ExitKeyboard();
}

void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) {
    current_text = std::move(submitted_text);

    std::vector<u8> out_data(sizeof(u64) + STRING_BUFFER_SIZE);

    if (swkbd_config_common.use_utf8) {
        std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text);
        // Include the null terminator in the buffer size.
        const u64 buffer_size = utf8_submitted_text.size() + 1;

        LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size,
                  utf8_submitted_text);

        std::memcpy(out_data.data(), &buffer_size, sizeof(u64));
        std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(),
                    utf8_submitted_text.size());
    } else {
        // Include the null terminator in the buffer size.
        const u64 buffer_size = (current_text.size() + 1) * sizeof(char16_t);

        LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size,
                  Common::UTF16ToUTF8(current_text));

        std::memcpy(out_data.data(), &buffer_size, sizeof(u64));
        std::memcpy(out_data.data() + sizeof(u64), current_text.data(),
                    current_text.size() * sizeof(char16_t));
    }

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(out_data)));
}

void SoftwareKeyboard::SendReply(SwkbdReplyType reply_type) {
    switch (reply_type) {
    case SwkbdReplyType::FinishedInitialize:
        ReplyFinishedInitialize();
        break;
    case SwkbdReplyType::Default:
        ReplyDefault();
        break;
    case SwkbdReplyType::ChangedString:
        ReplyChangedString();
        break;
    case SwkbdReplyType::MovedCursor:
        ReplyMovedCursor();
        break;
    case SwkbdReplyType::MovedTab:
        ReplyMovedTab();
        break;
    case SwkbdReplyType::DecidedEnter:
        ReplyDecidedEnter();
        break;
    case SwkbdReplyType::DecidedCancel:
        ReplyDecidedCancel();
        break;
    case SwkbdReplyType::ChangedStringUtf8:
        ReplyChangedStringUtf8();
        break;
    case SwkbdReplyType::MovedCursorUtf8:
        ReplyMovedCursorUtf8();
        break;
    case SwkbdReplyType::DecidedEnterUtf8:
        ReplyDecidedEnterUtf8();
        break;
    case SwkbdReplyType::UnsetCustomizeDic:
        ReplyUnsetCustomizeDic();
        break;
    case SwkbdReplyType::ReleasedUserWordInfo:
        ReplyReleasedUserWordInfo();
        break;
    case SwkbdReplyType::UnsetCustomizedDictionaries:
        ReplyUnsetCustomizedDictionaries();
        break;
    case SwkbdReplyType::ChangedStringV2:
        ReplyChangedStringV2();
        break;
    case SwkbdReplyType::MovedCursorV2:
        ReplyMovedCursorV2();
        break;
    case SwkbdReplyType::ChangedStringUtf8V2:
        ReplyChangedStringUtf8V2();
        break;
    case SwkbdReplyType::MovedCursorUtf8V2:
        ReplyMovedCursorUtf8V2();
        break;
    default:
        UNIMPLEMENTED_MSG("Unknown SwkbdReplyType={}", reply_type);
        ReplyDefault();
        break;
    }
}

void SoftwareKeyboard::ChangeState(SwkbdState state) {
    swkbd_state = state;

    ReplyDefault();
}

void SoftwareKeyboard::InitializeFrontendNormalKeyboard() {
    std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size());

    std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size());

    std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size());

    std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size());

    const u32 max_text_length =
        swkbd_config_common.max_text_length > 0 &&
                swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
            ? swkbd_config_common.max_text_length
            : DEFAULT_MAX_TEXT_LENGTH;

    const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length
                                    ? swkbd_config_common.min_text_length
                                    : 0;

    const s32 initial_cursor_position = [this] {
        switch (swkbd_config_common.initial_cursor_position) {
        case SwkbdInitialCursorPosition::Start:
        default:
            return 0;
        case SwkbdInitialCursorPosition::End:
            return static_cast<s32>(initial_text.size());
        }
    }();

    const auto text_draw_type = [this, max_text_length] {
        switch (swkbd_config_common.text_draw_type) {
        case SwkbdTextDrawType::Line:
        default:
            return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
        case SwkbdTextDrawType::Box:
        case SwkbdTextDrawType::DownloadCode:
            return swkbd_config_common.text_draw_type;
        }
    }();

    const auto enable_return_button =
        text_draw_type == SwkbdTextDrawType::Box ? swkbd_config_common.enable_return_button : false;

    const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227
                                           ? swkbd_config_new.disable_cancel_button
                                           : false;

    Core::Frontend::KeyboardInitializeParameters initialize_parameters{
        .ok_text{std::move(ok_text)},
        .header_text{std::move(header_text)},
        .sub_text{std::move(sub_text)},
        .guide_text{std::move(guide_text)},
        .initial_text{initial_text},
        .left_optional_symbol_key{swkbd_config_common.left_optional_symbol_key},
        .right_optional_symbol_key{swkbd_config_common.right_optional_symbol_key},
        .max_text_length{max_text_length},
        .min_text_length{min_text_length},
        .initial_cursor_position{initial_cursor_position},
        .type{swkbd_config_common.type},
        .password_mode{swkbd_config_common.password_mode},
        .text_draw_type{text_draw_type},
        .key_disable_flags{swkbd_config_common.key_disable_flags},
        .use_blur_background{swkbd_config_common.use_blur_background},
        .enable_backspace_button{true},
        .enable_return_button{enable_return_button},
        .disable_cancel_button{disable_cancel_button},
    };

    frontend.InitializeKeyboard(
        false, std::move(initialize_parameters),
        [this](SwkbdResult result, std::u16string submitted_text, bool confirmed) {
            SubmitTextNormal(result, submitted_text, confirmed);
        },
        {});
}

void SoftwareKeyboard::InitializeFrontendInlineKeyboard(
    Core::Frontend::KeyboardInitializeParameters initialize_parameters) {
    frontend.InitializeKeyboard(
        true, std::move(initialize_parameters), {},
        [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) {
            SubmitTextInline(reply_type, submitted_text, cursor_position);
        });
}

void SoftwareKeyboard::InitializeFrontendInlineKeyboardOld() {
    const auto& appear_arg = swkbd_calc_arg_old.appear_arg;

    std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        appear_arg.ok_text.data(), appear_arg.ok_text.size());

    const u32 max_text_length =
        appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
            ? appear_arg.max_text_length
            : DEFAULT_MAX_TEXT_LENGTH;

    const u32 min_text_length =
        appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;

    const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0;

    const auto text_draw_type =
        max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;

    Core::Frontend::KeyboardInitializeParameters initialize_parameters{
        .ok_text{std::move(ok_text)},
        .header_text{},
        .sub_text{},
        .guide_text{},
        .initial_text{current_text},
        .left_optional_symbol_key{appear_arg.left_optional_symbol_key},
        .right_optional_symbol_key{appear_arg.right_optional_symbol_key},
        .max_text_length{max_text_length},
        .min_text_length{min_text_length},
        .initial_cursor_position{initial_cursor_position},
        .type{appear_arg.type},
        .password_mode{SwkbdPasswordMode::Disabled},
        .text_draw_type{text_draw_type},
        .key_disable_flags{appear_arg.key_disable_flags},
        .use_blur_background{false},
        .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button},
        .enable_return_button{appear_arg.enable_return_button},
        .disable_cancel_button{appear_arg.disable_cancel_button},
    };

    InitializeFrontendInlineKeyboard(std::move(initialize_parameters));
}

void SoftwareKeyboard::InitializeFrontendInlineKeyboardNew() {
    const auto& appear_arg = swkbd_calc_arg_new.appear_arg;

    std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
        appear_arg.ok_text.data(), appear_arg.ok_text.size());

    const u32 max_text_length =
        appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
            ? appear_arg.max_text_length
            : DEFAULT_MAX_TEXT_LENGTH;

    const u32 min_text_length =
        appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;

    const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0;

    const auto text_draw_type =
        max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;

    Core::Frontend::KeyboardInitializeParameters initialize_parameters{
        .ok_text{std::move(ok_text)},
        .header_text{},
        .sub_text{},
        .guide_text{},
        .initial_text{current_text},
        .left_optional_symbol_key{appear_arg.left_optional_symbol_key},
        .right_optional_symbol_key{appear_arg.right_optional_symbol_key},
        .max_text_length{max_text_length},
        .min_text_length{min_text_length},
        .initial_cursor_position{initial_cursor_position},
        .type{appear_arg.type},
        .password_mode{SwkbdPasswordMode::Disabled},
        .text_draw_type{text_draw_type},
        .key_disable_flags{appear_arg.key_disable_flags},
        .use_blur_background{false},
        .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button},
        .enable_return_button{appear_arg.enable_return_button},
        .disable_cancel_button{appear_arg.disable_cancel_button},
    };

    InitializeFrontendInlineKeyboard(std::move(initialize_parameters));
}

void SoftwareKeyboard::ShowNormalKeyboard() {
    frontend.ShowNormalKeyboard();
}

void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_result,
                                           std::u16string text_check_message) {
    frontend.ShowTextCheckDialog(text_check_result, std::move(text_check_message));
}

void SoftwareKeyboard::ShowInlineKeyboard(
    Core::Frontend::InlineAppearParameters appear_parameters) {
    frontend.ShowInlineKeyboard(std::move(appear_parameters));

    ChangeState(SwkbdState::InitializedIsShown);
}

void SoftwareKeyboard::ShowInlineKeyboardOld() {
    if (swkbd_state != SwkbdState::InitializedIsHidden) {
        return;
    }

    ChangeState(SwkbdState::InitializedIsAppearing);

    const auto& appear_arg = swkbd_calc_arg_old.appear_arg;

    const u32 max_text_length =
        appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
            ? appear_arg.max_text_length
            : DEFAULT_MAX_TEXT_LENGTH;

    const u32 min_text_length =
        appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;

    Core::Frontend::InlineAppearParameters appear_parameters{
        .max_text_length{max_text_length},
        .min_text_length{min_text_length},
        .key_top_scale_x{swkbd_calc_arg_old.key_top_scale_x},
        .key_top_scale_y{swkbd_calc_arg_old.key_top_scale_y},
        .key_top_translate_x{swkbd_calc_arg_old.key_top_translate_x},
        .key_top_translate_y{swkbd_calc_arg_old.key_top_translate_y},
        .type{appear_arg.type},
        .key_disable_flags{appear_arg.key_disable_flags},
        .key_top_as_floating{swkbd_calc_arg_old.key_top_as_floating},
        .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button},
        .enable_return_button{appear_arg.enable_return_button},
        .disable_cancel_button{appear_arg.disable_cancel_button},
    };

    ShowInlineKeyboard(std::move(appear_parameters));
}

void SoftwareKeyboard::ShowInlineKeyboardNew() {
    if (swkbd_state != SwkbdState::InitializedIsHidden) {
        return;
    }

    ChangeState(SwkbdState::InitializedIsAppearing);

    const auto& appear_arg = swkbd_calc_arg_new.appear_arg;

    const u32 max_text_length =
        appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
            ? appear_arg.max_text_length
            : DEFAULT_MAX_TEXT_LENGTH;

    const u32 min_text_length =
        appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;

    Core::Frontend::InlineAppearParameters appear_parameters{
        .max_text_length{max_text_length},
        .min_text_length{min_text_length},
        .key_top_scale_x{swkbd_calc_arg_new.key_top_scale_x},
        .key_top_scale_y{swkbd_calc_arg_new.key_top_scale_y},
        .key_top_translate_x{swkbd_calc_arg_new.key_top_translate_x},
        .key_top_translate_y{swkbd_calc_arg_new.key_top_translate_y},
        .type{appear_arg.type},
        .key_disable_flags{appear_arg.key_disable_flags},
        .key_top_as_floating{swkbd_calc_arg_new.key_top_as_floating},
        .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button},
        .enable_return_button{appear_arg.enable_return_button},
        .disable_cancel_button{appear_arg.disable_cancel_button},
    };

    ShowInlineKeyboard(std::move(appear_parameters));
}

void SoftwareKeyboard::HideInlineKeyboard() {
    if (swkbd_state != SwkbdState::InitializedIsShown) {
        return;
    }

    ChangeState(SwkbdState::InitializedIsDisappearing);

    frontend.HideInlineKeyboard();

    ChangeState(SwkbdState::InitializedIsHidden);
}

void SoftwareKeyboard::InlineTextChanged() {
    Core::Frontend::InlineTextParameters text_parameters{
        .input_text{current_text},
        .cursor_position{current_cursor_position},
    };

    frontend.InlineTextChanged(std::move(text_parameters));
}

void SoftwareKeyboard::ExitKeyboard() {
    complete = true;
    status = ResultSuccess;

    frontend.ExitKeyboard();

    Exit();
}

Result SoftwareKeyboard::RequestExit() {
    frontend.Close();
    R_SUCCEED();
}

// Inline Software Keyboard Requests

void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
    LOG_DEBUG(Service_AM, "Processing Request: Finalize");

    ChangeState(SwkbdState::NotInitialized);

    ExitKeyboard();
}

void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) {
    LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented.");

    ReplyReleasedUserWordInfo();
}

void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) {
    LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented.");
}

void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) {
    LOG_DEBUG(Service_AM, "Processing Request: Calc");

    ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon));

    std::memcpy(&swkbd_calc_arg_common, request_data.data() + sizeof(SwkbdRequestCommand),
                sizeof(SwkbdCalcArgCommon));

    switch (swkbd_calc_arg_common.calc_arg_size) {
    case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld):
        ASSERT(request_data.size() ==
               sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld));
        std::memcpy(&swkbd_calc_arg_old,
                    request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
                    sizeof(SwkbdCalcArgOld));
        RequestCalcOld();
        break;
    case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew):
        ASSERT(request_data.size() ==
               sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew));
        std::memcpy(&swkbd_calc_arg_new,
                    request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
                    sizeof(SwkbdCalcArgNew));
        RequestCalcNew();
        break;
    default:
        UNIMPLEMENTED_MSG("Unknown SwkbdCalcArg size={}", swkbd_calc_arg_common.calc_arg_size);
        ASSERT(request_data.size() >=
               sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew));
        std::memcpy(&swkbd_calc_arg_new,
                    request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
                    sizeof(SwkbdCalcArgNew));
        RequestCalcNew();
        break;
    }
}

void SoftwareKeyboard::RequestCalcOld() {
    if (swkbd_calc_arg_common.flags.set_input_text) {
        current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
            swkbd_calc_arg_old.input_text.data(), swkbd_calc_arg_old.input_text.size());
    }

    if (swkbd_calc_arg_common.flags.set_cursor_position) {
        current_cursor_position = swkbd_calc_arg_old.cursor_position;
    }

    if (swkbd_calc_arg_common.flags.set_utf8_mode) {
        inline_use_utf8 = swkbd_calc_arg_old.utf8_mode;
    }

    if (swkbd_state <= SwkbdState::InitializedIsHidden &&
        swkbd_calc_arg_common.flags.unset_customize_dic) {
        ReplyUnsetCustomizeDic();
    }

    if (swkbd_state <= SwkbdState::InitializedIsHidden &&
        swkbd_calc_arg_common.flags.unset_user_word_info) {
        ReplyReleasedUserWordInfo();
    }

    if (swkbd_state == SwkbdState::NotInitialized &&
        swkbd_calc_arg_common.flags.set_initialize_arg) {
        InitializeFrontendInlineKeyboardOld();

        ChangeState(SwkbdState::InitializedIsHidden);

        ReplyFinishedInitialize();
    }

    if (!swkbd_calc_arg_common.flags.set_initialize_arg &&
        (swkbd_calc_arg_common.flags.set_input_text ||
         swkbd_calc_arg_common.flags.set_cursor_position)) {
        InlineTextChanged();
    }

    if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) {
        ShowInlineKeyboardOld();
        return;
    }

    if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) {
        HideInlineKeyboard();
        return;
    }
}

void SoftwareKeyboard::RequestCalcNew() {
    if (swkbd_calc_arg_common.flags.set_input_text) {
        current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
            swkbd_calc_arg_new.input_text.data(), swkbd_calc_arg_new.input_text.size());
    }

    if (swkbd_calc_arg_common.flags.set_cursor_position) {
        current_cursor_position = swkbd_calc_arg_new.cursor_position;
    }

    if (swkbd_calc_arg_common.flags.set_utf8_mode) {
        inline_use_utf8 = swkbd_calc_arg_new.utf8_mode;
    }

    if (swkbd_state <= SwkbdState::InitializedIsHidden &&
        swkbd_calc_arg_common.flags.unset_customize_dic) {
        ReplyUnsetCustomizeDic();
    }

    if (swkbd_state <= SwkbdState::InitializedIsHidden &&
        swkbd_calc_arg_common.flags.unset_user_word_info) {
        ReplyReleasedUserWordInfo();
    }

    if (swkbd_state == SwkbdState::NotInitialized &&
        swkbd_calc_arg_common.flags.set_initialize_arg) {
        InitializeFrontendInlineKeyboardNew();

        ChangeState(SwkbdState::InitializedIsHidden);

        ReplyFinishedInitialize();
    }

    if (!swkbd_calc_arg_common.flags.set_initialize_arg &&
        (swkbd_calc_arg_common.flags.set_input_text ||
         swkbd_calc_arg_common.flags.set_cursor_position)) {
        InlineTextChanged();
    }

    if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) {
        ShowInlineKeyboardNew();
        return;
    }

    if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) {
        HideInlineKeyboard();
        return;
    }
}

void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector<u8>& request_data) {
    LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented.");
}

void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data) {
    LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries");

    ReplyUnsetCustomizedDictionaries();
}

void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& request_data) {
    LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag");

    ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);

    std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1);
}

void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data) {
    LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag");

    ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);

    std::memcpy(&use_moved_cursor_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1);
}

// Inline Software Keyboard Replies

void SoftwareKeyboard::ReplyFinishedInitialize() {
    LOG_DEBUG(Service_AM, "Sending Reply: FinishedInitialize");

    std::vector<u8> reply(REPLY_BASE_SIZE + 1);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::FinishedInitialize);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyDefault() {
    LOG_DEBUG(Service_AM, "Sending Reply: Default");

    std::vector<u8> reply(REPLY_BASE_SIZE);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::Default);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyChangedString() {
    LOG_DEBUG(Service_AM, "Sending Reply: ChangedString");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedString);

    const SwkbdChangedStringArg changed_string_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .dictionary_start_cursor_position{-1},
        .dictionary_end_cursor_position{-1},
        .cursor_position{current_cursor_position},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg,
                sizeof(SwkbdChangedStringArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyMovedCursor() {
    LOG_DEBUG(Service_AM, "Sending Reply: MovedCursor");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursor);

    const SwkbdMovedCursorArg moved_cursor_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .cursor_position{current_cursor_position},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg,
                sizeof(SwkbdMovedCursorArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyMovedTab() {
    LOG_DEBUG(Service_AM, "Sending Reply: MovedTab");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedTabArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedTab);

    const SwkbdMovedTabArg moved_tab_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .cursor_position{current_cursor_position},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_tab_arg,
                sizeof(SwkbdMovedTabArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyDecidedEnter() {
    LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnter");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdDecidedEnterArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnter);

    const SwkbdDecidedEnterArg decided_enter_arg{
        .text_length{static_cast<u32>(current_text.size())},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &decided_enter_arg,
                sizeof(SwkbdDecidedEnterArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));

    HideInlineKeyboard();
}

void SoftwareKeyboard::ReplyDecidedCancel() {
    LOG_DEBUG(Service_AM, "Sending Reply: DecidedCancel");

    std::vector<u8> reply(REPLY_BASE_SIZE);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedCancel);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));

    HideInlineKeyboard();
}

void SoftwareKeyboard::ReplyChangedStringUtf8() {
    LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8);

    std::string utf8_current_text = Common::UTF16ToUTF8(current_text);

    const SwkbdChangedStringArg changed_string_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .dictionary_start_cursor_position{-1},
        .dictionary_end_cursor_position{-1},
        .cursor_position{current_cursor_position},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size());
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg,
                sizeof(SwkbdChangedStringArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyMovedCursorUtf8() {
    LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8);

    std::string utf8_current_text = Common::UTF16ToUTF8(current_text);

    const SwkbdMovedCursorArg moved_cursor_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .cursor_position{current_cursor_position},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size());
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg,
                sizeof(SwkbdMovedCursorArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyDecidedEnterUtf8() {
    LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnterUtf8");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdDecidedEnterArg));

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnterUtf8);

    std::string utf8_current_text = Common::UTF16ToUTF8(current_text);

    const SwkbdDecidedEnterArg decided_enter_arg{
        .text_length{static_cast<u32>(current_text.size())},
    };

    std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size());
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &decided_enter_arg,
                sizeof(SwkbdDecidedEnterArg));

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));

    HideInlineKeyboard();
}

void SoftwareKeyboard::ReplyUnsetCustomizeDic() {
    LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizeDic");

    std::vector<u8> reply(REPLY_BASE_SIZE);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizeDic);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyReleasedUserWordInfo() {
    LOG_DEBUG(Service_AM, "Sending Reply: ReleasedUserWordInfo");

    std::vector<u8> reply(REPLY_BASE_SIZE);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::ReleasedUserWordInfo);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyUnsetCustomizedDictionaries() {
    LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizedDictionaries");

    std::vector<u8> reply(REPLY_BASE_SIZE);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizedDictionaries);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyChangedStringV2() {
    LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringV2");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg) + 1);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringV2);

    const SwkbdChangedStringArg changed_string_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .dictionary_start_cursor_position{-1},
        .dictionary_end_cursor_position{-1},
        .cursor_position{current_cursor_position},
    };

    constexpr u8 flag = 0;

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg,
                sizeof(SwkbdChangedStringArg));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg),
                &flag, 1);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyMovedCursorV2() {
    LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorV2");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg) + 1);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorV2);

    const SwkbdMovedCursorArg moved_cursor_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .cursor_position{current_cursor_position},
    };

    constexpr u8 flag = 0;

    std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(),
                current_text.size() * sizeof(char16_t));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg,
                sizeof(SwkbdMovedCursorArg));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg),
                &flag, 1);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyChangedStringUtf8V2() {
    LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8V2");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg) + 1);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8V2);

    std::string utf8_current_text = Common::UTF16ToUTF8(current_text);

    const SwkbdChangedStringArg changed_string_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .dictionary_start_cursor_position{-1},
        .dictionary_end_cursor_position{-1},
        .cursor_position{current_cursor_position},
    };

    constexpr u8 flag = 0;

    std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size());
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg,
                sizeof(SwkbdChangedStringArg));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg),
                &flag, 1);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

void SoftwareKeyboard::ReplyMovedCursorUtf8V2() {
    LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8V2");

    std::vector<u8> reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg) + 1);

    SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8V2);

    std::string utf8_current_text = Common::UTF16ToUTF8(current_text);

    const SwkbdMovedCursorArg moved_cursor_arg{
        .text_length{static_cast<u32>(current_text.size())},
        .cursor_position{current_cursor_position},
    };

    constexpr u8 flag = 0;

    std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size());
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg,
                sizeof(SwkbdMovedCursorArg));
    std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg),
                &flag, 1);

    PushInteractiveOutData(std::make_shared<IStorage>(system, std::move(reply)));
}

} // namespace Service::AM::Frontend


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

#pragma once

#include "common/common_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/frontend/applet_software_keyboard_types.h"
#include "core/hle/service/am/frontend/applets.h"

namespace Core {
class System;
}

namespace Core::Frontend {
struct KeyboardInitializeParameters;
struct InlineAppearParameters;
} // namespace Core::Frontend

namespace Service::AM::Frontend {

class SoftwareKeyboard final : public FrontendApplet {
public:
    explicit SoftwareKeyboard(Core::System& system_, std::shared_ptr<Applet> applet_,
                              LibraryAppletMode applet_mode_,
                              Core::Frontend::SoftwareKeyboardApplet& frontend_);
    ~SoftwareKeyboard() override;

    void Initialize() override;

    Result GetStatus() const override;
    void ExecuteInteractive() override;
    void Execute() override;
    Result RequestExit() override;

    /**
     * Submits the input text to the application.
     * If text checking is enabled, the application will verify the input text.
     * If use_utf8 is enabled, the input text will be converted to UTF-8 prior to being submitted.
     * This should only be used by the normal software keyboard.
     *
     * @param result SwkbdResult enum
     * @param submitted_text UTF-16 encoded string
     * @param confirmed Whether the text has been confirmed after TextCheckResult::Confirm
     */
    void SubmitTextNormal(SwkbdResult result, std::u16string submitted_text, bool confirmed);

    /**
     * Submits the input text to the application.
     * If utf8_mode is enabled, the input text will be converted to UTF-8 prior to being submitted.
     * This should only be used by the inline software keyboard.
     *
     * @param reply_type SwkbdReplyType enum
     * @param submitted_text UTF-16 encoded string
     * @param cursor_position The current position of the text cursor
     */
    void SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text,
                          s32 cursor_position);

private:
    /// Initializes the normal software keyboard.
    void InitializeForeground();

    /// Initializes the inline software keyboard.
    void InitializePartialForeground(LibraryAppletMode library_applet_mode);

    /// Processes the text check sent by the application.
    void ProcessTextCheck();

    /// Processes the inline software keyboard request command sent by the application.
    void ProcessInlineKeyboardRequest();

    /// Submits the input text and exits the applet.
    void SubmitNormalOutputAndExit(SwkbdResult result, std::u16string submitted_text);

    /// Submits the input text for text checking.
    void SubmitForTextCheck(std::u16string submitted_text);

    /// Sends a reply to the application after processing a request command.
    void SendReply(SwkbdReplyType reply_type);

    /// Changes the inline keyboard state.
    void ChangeState(SwkbdState state);

    /**
     * Signals the frontend to initialize the normal software keyboard with common parameters.
     * Note that this does not cause the keyboard to appear.
     * Use the ShowNormalKeyboard() functions to cause the keyboard to appear.
     */
    void InitializeFrontendNormalKeyboard();

    /**
     * Signals the frontend to initialize the inline software keyboard with common parameters.
     * Note that this does not cause the keyboard to appear.
     * Use the ShowInlineKeyboard() to cause the keyboard to appear.
     */
    void InitializeFrontendInlineKeyboard(
        Core::Frontend::KeyboardInitializeParameters initialize_parameters);

    void InitializeFrontendInlineKeyboardOld();
    void InitializeFrontendInlineKeyboardNew();

    /// Signals the frontend to show the normal software keyboard.
    void ShowNormalKeyboard();

    /// Signals the frontend to show the text check dialog.
    void ShowTextCheckDialog(SwkbdTextCheckResult text_check_result,
                             std::u16string text_check_message);

    /// Signals the frontend to show the inline software keyboard.
    void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters);

    void ShowInlineKeyboardOld();
    void ShowInlineKeyboardNew();

    /// Signals the frontend to hide the inline software keyboard.
    void HideInlineKeyboard();

    /// Signals the frontend that the current inline keyboard text has changed.
    void InlineTextChanged();

    /// Signals both the frontend and application that the software keyboard is exiting.
    void ExitKeyboard();

    // Inline Software Keyboard Requests

    void RequestFinalize(const std::vector<u8>& request_data);
    void RequestSetUserWordInfo(const std::vector<u8>& request_data);
    void RequestSetCustomizeDic(const std::vector<u8>& request_data);
    void RequestCalc(const std::vector<u8>& request_data);
    void RequestCalcOld();
    void RequestCalcNew();
    void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data);
    void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data);
    void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data);
    void RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data);

    // Inline Software Keyboard Replies

    void ReplyFinishedInitialize();
    void ReplyDefault();
    void ReplyChangedString();
    void ReplyMovedCursor();
    void ReplyMovedTab();
    void ReplyDecidedEnter();
    void ReplyDecidedCancel();
    void ReplyChangedStringUtf8();
    void ReplyMovedCursorUtf8();
    void ReplyDecidedEnterUtf8();
    void ReplyUnsetCustomizeDic();
    void ReplyReleasedUserWordInfo();
    void ReplyUnsetCustomizedDictionaries();
    void ReplyChangedStringV2();
    void ReplyMovedCursorV2();
    void ReplyChangedStringUtf8V2();
    void ReplyMovedCursorUtf8V2();

    Core::Frontend::SoftwareKeyboardApplet& frontend;

    SwkbdAppletVersion swkbd_applet_version;

    SwkbdConfigCommon swkbd_config_common;
    SwkbdConfigOld swkbd_config_old;
    SwkbdConfigOld2 swkbd_config_old2;
    SwkbdConfigNew swkbd_config_new;
    std::u16string initial_text;

    SwkbdState swkbd_state{SwkbdState::NotInitialized};
    SwkbdInitializeArg swkbd_initialize_arg;
    SwkbdCalcArgCommon swkbd_calc_arg_common;
    SwkbdCalcArgOld swkbd_calc_arg_old;
    SwkbdCalcArgNew swkbd_calc_arg_new;
    bool use_changed_string_v2{false};
    bool use_moved_cursor_v2{false};
    bool inline_use_utf8{false};
    s32 current_cursor_position{};

    std::u16string current_text;

    bool is_background{false};

    bool complete{false};
    Result status{ResultSuccess};
};

} // namespace Service::AM::Frontend

repo_name: litucks/torzu

license: mit


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

#pragma once

#include <array>

#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "common/uuid.h"

namespace Service::AM::Frontend {

constexpr std::size_t MAX_OK_TEXT_LENGTH = 8;
constexpr std::size_t MAX_HEADER_TEXT_LENGTH = 64;
constexpr std::size_t MAX_SUB_TEXT_LENGTH = 128;
constexpr std::size_t MAX_GUIDE_TEXT_LENGTH = 256;
constexpr std::size_t STRING_BUFFER_SIZE = 0x7D4;

enum class SwkbdAppletVersion : u32_le {
    Version5 = 0x5,          // 1.0.0
    Version65542 = 0x10006,  // 2.0.0 - 2.3.0
    Version196615 = 0x30007, // 3.0.0 - 3.0.2
    Version262152 = 0x40008, // 4.0.0 - 4.1.0
    Version327689 = 0x50009, // 5.0.0 - 5.1.0
    Version393227 = 0x6000B, // 6.0.0 - 7.0.1
    Version524301 = 0x8000D, // 8.0.0+
};

enum class SwkbdType : u32 {
    Normal,
    NumberPad,
    Qwerty,
    Unknown3,
    Latin,
    SimplifiedChinese,
    TraditionalChinese,
    Korean,
};

enum class SwkbdInitialCursorPosition : u32 {
    Start,
    End,
};

enum class SwkbdPasswordMode : u32 {
    Disabled,
    Enabled,
};

enum class SwkbdTextDrawType : u32 {
    Line,
    Box,
    DownloadCode,
};

enum class SwkbdResult : u32 {
    Ok,
    Cancel,
};

enum class SwkbdTextCheckResult : u32 {
    Success,
    Failure,
    Confirm,
    Silent,
};

enum class SwkbdState : u32 {
    NotInitialized = 0x0,
    InitializedIsHidden = 0x1,
    InitializedIsAppearing = 0x2,
    InitializedIsShown = 0x3,
    InitializedIsDisappearing = 0x4,
};

enum class SwkbdRequestCommand : u32 {
    Finalize = 0x4,
    SetUserWordInfo = 0x6,
    SetCustomizeDic = 0x7,
    Calc = 0xA,
    SetCustomizedDictionaries = 0xB,
    UnsetCustomizedDictionaries = 0xC,
    SetChangedStringV2Flag = 0xD,
    SetMovedCursorV2Flag = 0xE,
};

enum class SwkbdReplyType : u32 {
    FinishedInitialize = 0x0,
    Default = 0x1,
    ChangedString = 0x2,
    MovedCursor = 0x3,
    MovedTab = 0x4,
    DecidedEnter = 0x5,
    DecidedCancel = 0x6,
    ChangedStringUtf8 = 0x7,
    MovedCursorUtf8 = 0x8,
    DecidedEnterUtf8 = 0x9,
    UnsetCustomizeDic = 0xA,
    ReleasedUserWordInfo = 0xB,
    UnsetCustomizedDictionaries = 0xC,
    ChangedStringV2 = 0xD,
    MovedCursorV2 = 0xE,
    ChangedStringUtf8V2 = 0xF,
    MovedCursorUtf8V2 = 0x10,
};

struct SwkbdKeyDisableFlags {
    union {
        u32 raw{};

        BitField<1, 1, u32> space;
        BitField<2, 1, u32> at;
        BitField<3, 1, u32> percent;
        BitField<4, 1, u32> slash;
        BitField<5, 1, u32> backslash;
        BitField<6, 1, u32> numbers;
        BitField<7, 1, u32> download_code;
        BitField<8, 1, u32> username;
    };
};
static_assert(sizeof(SwkbdKeyDisableFlags) == 0x4, "SwkbdKeyDisableFlags has incorrect size.");

struct SwkbdConfigCommon {
    SwkbdType type{};
    std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{};
    char16_t left_optional_symbol_key{};
    char16_t right_optional_symbol_key{};
    bool use_prediction{};
    INSERT_PADDING_BYTES(1);
    SwkbdKeyDisableFlags key_disable_flags{};
    SwkbdInitialCursorPosition initial_cursor_position{};
    std::array<char16_t, MAX_HEADER_TEXT_LENGTH + 1> header_text{};
    std::array<char16_t, MAX_SUB_TEXT_LENGTH + 1> sub_text{};
    std::array<char16_t, MAX_GUIDE_TEXT_LENGTH + 1> guide_text{};
    u32 max_text_length{};
    u32 min_text_length{};
    SwkbdPasswordMode password_mode{};
    SwkbdTextDrawType text_draw_type{};
    bool enable_return_button{};
    bool use_utf8{};
    bool use_blur_background{};
    INSERT_PADDING_BYTES(1);
    u32 initial_string_offset{};
    u32 initial_string_length{};
    u32 user_dictionary_offset{};
    u32 user_dictionary_entries{};
    bool use_text_check{};
    INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(SwkbdConfigCommon) == 0x3D4, "SwkbdConfigCommon has incorrect size.");

#pragma pack(push, 4)
// SwkbdAppletVersion 0x5, 0x10006
struct SwkbdConfigOld {
    INSERT_PADDING_WORDS(1);
    VAddr text_check_callback{};
};
static_assert(sizeof(SwkbdConfigOld) == 0x3E0 - sizeof(SwkbdConfigCommon),
              "SwkbdConfigOld has incorrect size.");

// SwkbdAppletVersion 0x30007, 0x40008, 0x50009
struct SwkbdConfigOld2 {
    INSERT_PADDING_WORDS(1);
    VAddr text_check_callback{};
    std::array<u32, 8> text_grouping{};
};
static_assert(sizeof(SwkbdConfigOld2) == 0x400 - sizeof(SwkbdConfigCommon),
              "SwkbdConfigOld2 has incorrect size.");

// SwkbdAppletVersion 0x6000B, 0x8000D
struct SwkbdConfigNew {
    std::array<u32, 8> text_grouping{};
    std::array<u64, 24> customized_dictionary_set_entries{};
    u8 total_customized_dictionary_set_entries{};
    bool disable_cancel_button{};
    INSERT_PADDING_BYTES(18);
};
static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon),
              "SwkbdConfigNew has incorrect size.");
#pragma pack(pop)

struct SwkbdTextCheck {
    SwkbdTextCheckResult text_check_result{};
    std::array<char16_t, STRING_BUFFER_SIZE / 2> text_check_message{};
};
static_assert(sizeof(SwkbdTextCheck) == 0x7D8, "SwkbdTextCheck has incorrect size.");

struct SwkbdCalcArgFlags {
    union {
        u64 raw{};

        BitField<0, 1, u64> set_initialize_arg;
        BitField<1, 1, u64> set_volume;
        BitField<2, 1, u64> appear;
        BitField<3, 1, u64> set_input_text;
        BitField<4, 1, u64> set_cursor_position;
        BitField<5, 1, u64> set_utf8_mode;
        BitField<6, 1, u64> unset_customize_dic;
        BitField<7, 1, u64> disappear;
        BitField<8, 1, u64> unknown;
        BitField<9, 1, u64> set_key_top_translate_scale;
        BitField<10, 1, u64> unset_user_word_info;
        BitField<11, 1, u64> set_disable_hardware_keyboard;
    };
};
static_assert(sizeof(SwkbdCalcArgFlags) == 0x8, "SwkbdCalcArgFlags has incorrect size.");

struct SwkbdInitializeArg {
    u32 unknown{};
    bool library_applet_mode_flag{};
    bool is_above_hos_500{};
    INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size.");

struct SwkbdAppearArgOld {
    SwkbdType type{};
    std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{};
    char16_t left_optional_symbol_key{};
    char16_t right_optional_symbol_key{};
    bool use_prediction{};
    bool disable_cancel_button{};
    SwkbdKeyDisableFlags key_disable_flags{};
    u32 max_text_length{};
    u32 min_text_length{};
    bool enable_return_button{};
    INSERT_PADDING_BYTES(3);
    u32 flags{};
    bool is_use_save_data{};
    INSERT_PADDING_BYTES(7);
    Common::UUID user_id{};
};
static_assert(sizeof(SwkbdAppearArgOld) == 0x48, "SwkbdAppearArg has incorrect size.");

struct SwkbdAppearArgNew {
    SwkbdType type{};
    std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{};
    char16_t left_optional_symbol_key{};
    char16_t right_optional_symbol_key{};
    bool use_prediction{};
    bool disable_cancel_button{};
    SwkbdKeyDisableFlags key_disable_flags{};
    u32 max_text_length{};
    u32 min_text_length{};
    bool enable_return_button{};
    INSERT_PADDING_BYTES(3);
    u32 flags{};
    bool is_use_save_data{};
    INSERT_PADDING_BYTES(7);
    Common::UUID user_id{};
    u64 start_sampling_number{};
    INSERT_PADDING_WORDS(8);
};
static_assert(sizeof(SwkbdAppearArgNew) == 0x70, "SwkbdAppearArg has incorrect size.");

struct SwkbdCalcArgCommon {
    u32 unknown{};
    u16 calc_arg_size{};
    INSERT_PADDING_BYTES(2);
    SwkbdCalcArgFlags flags{};
    SwkbdInitializeArg initialize_arg{};
};
static_assert(sizeof(SwkbdCalcArgCommon) == 0x18, "SwkbdCalcArgCommon has incorrect size.");

struct SwkbdCalcArgOld {
    f32 volume{};
    s32 cursor_position{};
    SwkbdAppearArgOld appear_arg{};
    std::array<char16_t, 0x1FA> input_text{};
    bool utf8_mode{};
    INSERT_PADDING_BYTES(1);
    bool enable_backspace_button{};
    INSERT_PADDING_BYTES(3);
    bool key_top_as_floating{};
    bool footer_scalable{};
    bool alpha_enabled_in_input_mode{};
    u8 input_mode_fade_type{};
    bool disable_touch{};
    bool disable_hardware_keyboard{};
    INSERT_PADDING_BYTES(8);
    f32 key_top_scale_x{};
    f32 key_top_scale_y{};
    f32 key_top_translate_x{};
    f32 key_top_translate_y{};
    f32 key_top_bg_alpha{};
    f32 footer_bg_alpha{};
    f32 balloon_scale{};
    INSERT_PADDING_WORDS(4);
    u8 se_group{};
    INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(SwkbdCalcArgOld) == 0x4A0 - sizeof(SwkbdCalcArgCommon),
              "SwkbdCalcArgOld has incorrect size.");

struct SwkbdCalcArgNew {
    SwkbdAppearArgNew appear_arg{};
    f32 volume{};
    s32 cursor_position{};
    std::array<char16_t, 0x1FA> input_text{};
    bool utf8_mode{};
    INSERT_PADDING_BYTES(1);
    bool enable_backspace_button{};
    INSERT_PADDING_BYTES(3);
    bool key_top_as_floating{};
    bool footer_scalable{};
    bool alpha_enabled_in_input_mode{};
    u8 input_mode_fade_type{};
    bool disable_touch{};
    bool disable_hardware_keyboard{};
    INSERT_PADDING_BYTES(8);
    f32 key_top_scale_x{};
    f32 key_top_scale_y{};
    f32 key_top_translate_x{};
    f32 key_top_translate_y{};
    f32 key_top_bg_alpha{};
    f32 footer_bg_alpha{};
    f32 balloon_scale{};
    INSERT_PADDING_WORDS(4);
    u8 se_group{};
    INSERT_PADDING_BYTES(3);
    INSERT_PADDING_WORDS(8);
};
static_assert(sizeof(SwkbdCalcArgNew) == 0x4E8 - sizeof(SwkbdCalcArgCommon),
              "SwkbdCalcArgNew has incorrect size.");

struct SwkbdChangedStringArg {
    u32 text_length{};
    s32 dictionary_start_cursor_position{};
    s32 dictionary_end_cursor_position{};
    s32 cursor_position{};
};
static_assert(sizeof(SwkbdChangedStringArg) == 0x10, "SwkbdChangedStringArg has incorrect size.");

struct SwkbdMovedCursorArg {
    u32 text_length{};
    s32 cursor_position{};
};
static_assert(sizeof(SwkbdMovedCursorArg) == 0x8, "SwkbdMovedCursorArg has incorrect size.");

struct SwkbdMovedTabArg {
    u32 text_length{};
    s32 cursor_position{};
};
static_assert(sizeof(SwkbdMovedTabArg) == 0x8, "SwkbdMovedTabArg has incorrect size.");

struct SwkbdDecidedEnterArg {
    u32 text_length{};
};
static_assert(sizeof(SwkbdDecidedEnterArg) == 0x4, "SwkbdDecidedEnterArg has incorrect size.");

} // namespace Service::AM::Frontend


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

#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs/vfs_vector.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/frontend/applet_web_browser.h"
#include "core/hle/service/am/service/storage.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/platform_service_manager.h"
#include "core/loader/loader.h"

namespace Service::AM::Frontend {

namespace {

template <typename T>
void ParseRawValue(T& value, const std::vector<u8>& data) {
    static_assert(std::is_trivially_copyable_v<T>,
                  "It's undefined behavior to use memcpy with non-trivially copyable objects");
    std::memcpy(&value, data.data(), data.size());
}

template <typename T>
T ParseRawValue(const std::vector<u8>& data) {
    T value;
    ParseRawValue(value, data);
    return value;
}

std::string ParseStringValue(const std::vector<u8>& data) {
    return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
                                                       data.size());
}

std::string GetMainURL(const std::string& url) {
    const auto index = url.find('?');

    if (index == std::string::npos) {
        return url;
    }

    return url.substr(0, index);
}

std::string ResolveURL(const std::string& url) {
    const auto index = url.find_first_of('%');

    if (index == std::string::npos) {
        return url;
    }

    return url.substr(0, index) + "lp1" + url.substr(index + 1);
}

WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
    std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));

    if (web_arg.size() == sizeof(WebArgHeader)) {
        return {};
    }

    WebArgInputTLVMap input_tlv_map;

    u64 current_offset = sizeof(WebArgHeader);

    for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
        if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
            return input_tlv_map;
        }

        WebArgInputTLV input_tlv;
        std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));

        current_offset += sizeof(WebArgInputTLV);

        if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
            return input_tlv_map;
        }

        std::vector<u8> data(input_tlv.arg_data_size);
        std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);

        current_offset += input_tlv.arg_data_size;

        input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
    }

    return input_tlv_map;
}

FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
                                     FileSys::ContentRecordType nca_type) {
    if (nca_type == FileSys::ContentRecordType::Data) {
        const auto nca =
            system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type);

        if (nca == nullptr) {
            LOG_ERROR(Service_AM,
                      "NCA of type={} with title_id={:016X} is not found in the System NAND!",
                      nca_type, title_id);
            return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
        }

        return nca->GetRomFS();
    } else {
        const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type);

        if (nca == nullptr) {
            if (nca_type == FileSys::ContentRecordType::HtmlDocument) {
                LOG_WARNING(Service_AM, "Falling back to AppLoader to get the RomFS.");
                FileSys::VirtualFile romfs;
                system.GetAppLoader().ReadManualRomFS(romfs);
                if (romfs != nullptr) {
                    return romfs;
                }
            }

            LOG_ERROR(Service_AM,
                      "NCA of type={} with title_id={:016X} is not found in the ContentProvider!",
                      nca_type, title_id);
            return nullptr;
        }

        const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
                                       system.GetContentProvider()};

        return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type);
    }
}

void ExtractSharedFonts(Core::System& system) {
    static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
        "FontStandard.ttf",
        "FontChineseSimplified.ttf",
        "FontExtendedChineseSimplified.ttf",
        "FontChineseTraditional.ttf",
        "FontKorean.ttf",
        "FontNintendoExtended.ttf",
        "FontNintendoExtended2.ttf",
    };

    const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts";

    for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
        const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i];

        if (Common::FS::Exists(font_file_path)) {
            continue;
        }

        const auto font = NS::SHARED_FONTS[i];
        const auto font_title_id = static_cast<u64>(font.first);

        const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry(
            font_title_id, FileSys::ContentRecordType::Data);

        FileSys::VirtualFile romfs;

        if (!nca) {
            romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id);
        } else {
            romfs = nca->GetRomFS();
        }

        if (!romfs) {
            LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!",
                      font_title_id);
            continue;
        }

        const auto extracted_romfs = FileSys::ExtractRomFS(romfs);

        if (!extracted_romfs) {
            LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!",
                      font_title_id);
            continue;
        }

        const auto font_file = extracted_romfs->GetFile(font.second);

        if (!font_file) {
            LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!",
                      font_title_id, font.second);
            continue;
        }

        std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32));
        font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize());

        std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
                       Common::swap32);

        std::vector<u8> decrypted_data(font_file->GetSize() - 8);

        NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data);

        FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
            std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);

        const auto temp_dir = system.GetFilesystem()->CreateDirectory(
            Common::FS::PathToUTF8String(fonts_dir), FileSys::OpenMode::ReadWrite);

        const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);

        FileSys::VfsRawCopy(decrypted_font, out_file);
    }
}

} // namespace

WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr<Applet> applet_,
                       LibraryAppletMode applet_mode_,
                       const Core::Frontend::WebBrowserApplet& frontend_)
    : FrontendApplet{system_, applet_, applet_mode_}, frontend(frontend_) {}

WebBrowser::~WebBrowser() = default;

void WebBrowser::Initialize() {
    FrontendApplet::Initialize();

    LOG_INFO(Service_AM, "Initializing Web Browser Applet.");

    LOG_DEBUG(Service_AM,
              "Initializing Applet with common_args: arg_version={}, lib_version={}, "
              "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
              common_args.arguments_version, common_args.library_version,
              common_args.play_startup_sound, common_args.size, common_args.system_tick,
              common_args.theme_color);

    web_applet_version = WebAppletVersion{common_args.library_version};

    const auto web_arg_storage = PopInData();
    ASSERT(web_arg_storage != nullptr);

    const auto& web_arg = web_arg_storage->GetData();
    ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });

    web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);

    LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
              web_arg_header.total_tlv_entries, web_arg_header.shim_kind);

    ExtractSharedFonts(system);

    switch (web_arg_header.shim_kind) {
    case ShimKind::Shop:
        InitializeShop();
        break;
    case ShimKind::Login:
        InitializeLogin();
        break;
    case ShimKind::Offline:
        InitializeOffline();
        break;
    case ShimKind::Share:
        InitializeShare();
        break;
    case ShimKind::Web:
        InitializeWeb();
        break;
    case ShimKind::Wifi:
        InitializeWifi();
        break;
    case ShimKind::Lobby:
        InitializeLobby();
        break;
    default:
        ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind);
        break;
    }
}

Result WebBrowser::GetStatus() const {
    return status;
}

void WebBrowser::ExecuteInteractive() {
    UNIMPLEMENTED_MSG("WebSession is not implemented");
}

void WebBrowser::Execute() {
    switch (web_arg_header.shim_kind) {
    case ShimKind::Shop:
        ExecuteShop();
        break;
    case ShimKind::Login:
        ExecuteLogin();
        break;
    case ShimKind::Offline:
        ExecuteOffline();
        break;
    case ShimKind::Share:
        ExecuteShare();
        break;
    case ShimKind::Web:
        ExecuteWeb();
        break;
    case ShimKind::Wifi:
        ExecuteWifi();
        break;
    case ShimKind::Lobby:
        ExecuteLobby();
        break;
    default:
        ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind);
        WebBrowserExit(WebExitReason::EndButtonPressed);
        break;
    }
}

void WebBrowser::ExtractOfflineRomFS() {
    LOG_DEBUG(Service_AM, "Extracting RomFS to {}",
              Common::FS::PathToUTF8String(offline_cache_dir));

    const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs);

    const auto temp_dir = system.GetFilesystem()->CreateDirectory(
        Common::FS::PathToUTF8String(offline_cache_dir), FileSys::OpenMode::ReadWrite);

    FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
}

void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
    if ((web_arg_header.shim_kind == ShimKind::Share &&
         web_applet_version >= WebAppletVersion::Version196608) ||
        (web_arg_header.shim_kind == ShimKind::Web &&
         web_applet_version >= WebAppletVersion::Version524288)) {
        // TODO: Push Output TLVs instead of a WebCommonReturnValue
    }

    WebCommonReturnValue web_common_return_value;

    web_common_return_value.exit_reason = exit_reason;
    std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
    web_common_return_value.last_url_size = last_url.size();

    LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
              exit_reason, last_url, last_url.size());

    complete = true;
    std::vector<u8> out_data(sizeof(WebCommonReturnValue));
    std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
    PushOutData(std::make_shared<IStorage>(system, std::move(out_data)));
    Exit();
}

Result WebBrowser::RequestExit() {
    frontend.Close();
    R_SUCCEED();
}

bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const {
    return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end();
}

std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) {
    const auto map_it = web_arg_input_tlv_map.find(input_tlv_type);

    if (map_it == web_arg_input_tlv_map.end()) {
        return std::nullopt;
    }

    return map_it->second;
}

void WebBrowser::InitializeShop() {}

void WebBrowser::InitializeLogin() {}

void WebBrowser::InitializeOffline() {
    const auto document_path =
        ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value());

    const auto document_kind =
        ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value());

    std::string additional_paths;

    switch (document_kind) {
    case DocumentKind::OfflineHtmlPage:
    default:
        title_id = system.GetApplicationProcessProgramID();
        nca_type = FileSys::ContentRecordType::HtmlDocument;
        additional_paths = "html-document";
        break;
    case DocumentKind::ApplicationLegalInformation:
        title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value());
        nca_type = FileSys::ContentRecordType::LegalInformation;
        break;
    case DocumentKind::SystemDataPage:
        title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value());
        nca_type = FileSys::ContentRecordType::Data;
        break;
    }

    static constexpr std::array<const char*, 3> RESOURCE_TYPES{
        "manual",
        "legal_information",
        "system_data",
    };

    offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
                        fmt::format("offline_web_applet_{}/{:016X}",
                                    RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id);

    offline_document = Common::FS::ConcatPathSafe(
        offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path));
}

void WebBrowser::InitializeShare() {}

void WebBrowser::InitializeWeb() {
    external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());

    // Resolve Nintendo CDN URLs.
    external_url = ResolveURL(external_url);
}

void WebBrowser::InitializeWifi() {}

void WebBrowser::InitializeLobby() {}

void WebBrowser::ExecuteShop() {
    LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
    WebBrowserExit(WebExitReason::EndButtonPressed);
}

void WebBrowser::ExecuteLogin() {
    LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
    WebBrowserExit(WebExitReason::EndButtonPressed);
}

void WebBrowser::ExecuteOffline() {
    // TODO (Morph): This is a hack for WebSession foreground web applets such as those used by
    //               Super Mario 3D All-Stars.
    // TODO (Morph): Implement WebSession.
    if (applet_mode == LibraryAppletMode::AllForegroundInitiallyHidden) {
        LOG_WARNING(Service_AM, "WebSession is not implemented");
        return;
    }

    const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document));

    if (!Common::FS::Exists(main_url)) {
        offline_romfs = GetOfflineRomFS(system, title_id, nca_type);

        if (offline_romfs == nullptr) {
            LOG_ERROR(Service_AM,
                      "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id,
                      nca_type);
            WebBrowserExit(WebExitReason::WindowClosed);
            return;
        }
    }

    LOG_INFO(Service_AM, "Opening offline document at {}",
             Common::FS::PathToUTF8String(offline_document));

    frontend.OpenLocalWebPage(
        Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); },
        [this](WebExitReason exit_reason, std::string last_url) {
            WebBrowserExit(exit_reason, last_url);
        });
}

void WebBrowser::ExecuteShare() {
    LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
    WebBrowserExit(WebExitReason::EndButtonPressed);
}

void WebBrowser::ExecuteWeb() {
    LOG_INFO(Service_AM, "Opening external URL at {}", external_url);

    frontend.OpenExternalWebPage(external_url,
                                 [this](WebExitReason exit_reason, std::string last_url) {
                                     WebBrowserExit(exit_reason, last_url);
                                 });
}

void WebBrowser::ExecuteWifi() {
    LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
    WebBrowserExit(WebExitReason::EndButtonPressed);
}

void WebBrowser::ExecuteLobby() {
    LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
    WebBrowserExit(WebExitReason::EndButtonPressed);
}
} // namespace Service::AM::Frontend


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

#pragma once

#include <filesystem>
#include <optional>

#include "common/common_types.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/frontend/applet_web_browser_types.h"
#include "core/hle/service/am/frontend/applets.h"

namespace Core {
class System;
}

namespace FileSys {
enum class ContentRecordType : u8;
}

namespace Service::AM::Frontend {

class WebBrowser final : public FrontendApplet {
public:
    WebBrowser(Core::System& system_, std::shared_ptr<Applet> applet_,
               LibraryAppletMode applet_mode_, const Core::Frontend::WebBrowserApplet& frontend_);

    ~WebBrowser() override;

    void Initialize() override;

    Result GetStatus() const override;
    void ExecuteInteractive() override;
    void Execute() override;
    Result RequestExit() override;

    void ExtractOfflineRomFS();

    void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");

private:
    bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const;

    std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type);

    // Initializers for the various types of browser applets
    void InitializeShop();
    void InitializeLogin();
    void InitializeOffline();
    void InitializeShare();
    void InitializeWeb();
    void InitializeWifi();
    void InitializeLobby();

    // Executors for the various types of browser applets
    void ExecuteShop();
    void ExecuteLogin();
    void ExecuteOffline();
    void ExecuteShare();
    void ExecuteWeb();
    void ExecuteWifi();
    void ExecuteLobby();

    const Core::Frontend::WebBrowserApplet& frontend;

    bool complete{false};
    Result status{ResultSuccess};

    WebAppletVersion web_applet_version{};
    WebArgHeader web_arg_header{};
    WebArgInputTLVMap web_arg_input_tlv_map;

    u64 title_id{};
    FileSys::ContentRecordType nca_type{};
    std::filesystem::path offline_cache_dir;
    std::filesystem::path offline_document;
    FileSys::VirtualFile offline_romfs;

    std::string external_url;
};

} // namespace Service::AM::Frontend


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

#pragma once

#include <array>
#include <unordered_map>
#include <vector>

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"

namespace Service::AM::Frontend {

enum class WebAppletVersion : u32_le {
    Version0 = 0x0,          // Only used by WifiWebAuthApplet
    Version131072 = 0x20000, // 1.0.0 - 2.3.0
    Version196608 = 0x30000, // 3.0.0 - 4.1.0
    Version327680 = 0x50000, // 5.0.0 - 5.1.0
    Version393216 = 0x60000, // 6.0.0 - 7.0.1
    Version524288 = 0x80000, // 8.0.0+
};

enum class ShimKind : u32 {
    Shop = 1,
    Login = 2,
    Offline = 3,
    Share = 4,
    Web = 5,
    Wifi = 6,
    Lobby = 7,
};

enum class WebExitReason : u32 {
    EndButtonPressed = 0,
    BackButtonPressed = 1,
    ExitRequested = 2,
    CallbackURL = 3,
    WindowClosed = 4,
    ErrorDialog = 7,
};

enum class WebArgInputTLVType : u16 {
    InitialURL = 0x1,
    CallbackURL = 0x3,
    CallbackableURL = 0x4,
    ApplicationID = 0x5,
    DocumentPath = 0x6,
    DocumentKind = 0x7,
    SystemDataID = 0x8,
    ShareStartPage = 0x9,
    Whitelist = 0xA,
    News = 0xB,
    UserID = 0xE,
    AlbumEntry0 = 0xF,
    ScreenShotEnabled = 0x10,
    EcClientCertEnabled = 0x11,
    PlayReportEnabled = 0x13,
    BootDisplayKind = 0x17,
    BackgroundKind = 0x18,
    FooterEnabled = 0x19,
    PointerEnabled = 0x1A,
    LeftStickMode = 0x1B,
    KeyRepeatFrame1 = 0x1C,
    KeyRepeatFrame2 = 0x1D,
    BootAsMediaPlayerInverted = 0x1E,
    DisplayURLKind = 0x1F,
    BootAsMediaPlayer = 0x21,
    ShopJumpEnabled = 0x22,
    MediaAutoPlayEnabled = 0x23,
    LobbyParameter = 0x24,
    ApplicationAlbumEntry = 0x26,
    JsExtensionEnabled = 0x27,
    AdditionalCommentText = 0x28,
    TouchEnabledOnContents = 0x29,
    UserAgentAdditionalString = 0x2A,
    AdditionalMediaData0 = 0x2B,
    MediaPlayerAutoCloseEnabled = 0x2C,
    PageCacheEnabled = 0x2D,
    WebAudioEnabled = 0x2E,
    YouTubeVideoWhitelist = 0x31,
    FooterFixedKind = 0x32,
    PageFadeEnabled = 0x33,
    MediaCreatorApplicationRatingAge = 0x34,
    BootLoadingIconEnabled = 0x35,
    PageScrollIndicatorEnabled = 0x36,
    MediaPlayerSpeedControlEnabled = 0x37,
    AlbumEntry1 = 0x38,
    AlbumEntry2 = 0x39,
    AlbumEntry3 = 0x3A,
    AdditionalMediaData1 = 0x3B,
    AdditionalMediaData2 = 0x3C,
    AdditionalMediaData3 = 0x3D,
    BootFooterButton = 0x3E,
    OverrideWebAudioVolume = 0x3F,
    OverrideMediaAudioVolume = 0x40,
    BootMode = 0x41,
    WebSessionEnabled = 0x42,
    MediaPlayerOfflineEnabled = 0x43,
};

enum class WebArgOutputTLVType : u16 {
    ShareExitReason = 0x1,
    LastURL = 0x2,
    LastURLSize = 0x3,
    SharePostResult = 0x4,
    PostServiceName = 0x5,
    PostServiceNameSize = 0x6,
    PostID = 0x7,
    PostIDSize = 0x8,
    MediaPlayerAutoClosedByCompletion = 0x9,
};

enum class DocumentKind : u32 {
    OfflineHtmlPage = 1,
    ApplicationLegalInformation = 2,
    SystemDataPage = 3,
};

enum class ShareStartPage : u32 {
    Default,
    Settings,
};

enum class BootDisplayKind : u32 {
    Default,
    White,
    Black,
};

enum class BackgroundKind : u32 {
    Default,
};

enum class LeftStickMode : u32 {
    Pointer,
    Cursor,
};

enum class WebSessionBootMode : u32 {
    AllForeground,
    AllForegroundInitiallyHidden,
};

struct WebArgHeader {
    u16 total_tlv_entries{};
    INSERT_PADDING_BYTES(2);
    ShimKind shim_kind{};
};
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");

struct WebArgInputTLV {
    WebArgInputTLVType input_tlv_type{};
    u16 arg_data_size{};
    INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");

struct WebArgOutputTLV {
    WebArgOutputTLVType output_tlv_type{};
    u16 arg_data_size{};
    INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");

struct WebCommonReturnValue {
    WebExitReason exit_reason{};
    INSERT_PADDING_WORDS(1);
    std::array<char, 0x1000> last_url{};
    u64 last_url_size{};
};
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");

using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;

} // namespace Service::AM::Frontend


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

#include <cstring>

#include "common/assert.h"
#include "core/core.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general.h"
#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_data_broker.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applet_cabinet.h"
#include "core/hle/service/am/frontend/applet_controller.h"
#include "core/hle/service/am/frontend/applet_error.h"
#include "core/hle/service/am/frontend/applet_general.h"
#include "core/hle/service/am/frontend/applet_mii_edit.h"
#include "core/hle/service/am/frontend/applet_profile_select.h"
#include "core/hle/service/am/frontend/applet_software_keyboard.h"
#include "core/hle/service/am/frontend/applet_web_browser.h"
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/am/service/storage.h"
#include "core/hle/service/sm/sm.h"

namespace Service::AM::Frontend {

FrontendApplet::FrontendApplet(Core::System& system_, std::shared_ptr<Applet> applet_,
                               LibraryAppletMode applet_mode_)
    : system{system_}, applet{std::move(applet_)}, applet_mode{applet_mode_} {}

FrontendApplet::~FrontendApplet() = default;

void FrontendApplet::Initialize() {
    std::shared_ptr<IStorage> common = PopInData();
    ASSERT(common != nullptr);
    const auto common_data = common->GetData();

    ASSERT(common_data.size() >= sizeof(CommonArguments));
    std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments));

    initialized = true;
}

std::shared_ptr<IStorage> FrontendApplet::PopInData() {
    std::shared_ptr<IStorage> ret;
    applet.lock()->caller_applet_broker->GetInData().Pop(&ret);
    return ret;
}

std::shared_ptr<IStorage> FrontendApplet::PopInteractiveInData() {
    std::shared_ptr<IStorage> ret;
    applet.lock()->caller_applet_broker->GetInteractiveInData().Pop(&ret);
    return ret;
}

void FrontendApplet::PushOutData(std::shared_ptr<IStorage> storage) {
    applet.lock()->caller_applet_broker->GetOutData().Push(storage);
}

void FrontendApplet::PushInteractiveOutData(std::shared_ptr<IStorage> storage) {
    applet.lock()->caller_applet_broker->GetInteractiveOutData().Push(storage);
}

void FrontendApplet::Exit() {
    applet.lock()->caller_applet_broker->SignalCompletion();
}

FrontendAppletSet::FrontendAppletSet() = default;

FrontendAppletSet::FrontendAppletSet(CabinetApplet cabinet_applet,
                                     ControllerApplet controller_applet, ErrorApplet error_applet,
                                     MiiEdit mii_edit_,
                                     ParentalControlsApplet parental_controls_applet,
                                     PhotoViewer photo_viewer_, ProfileSelect profile_select_,
                                     SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
    : cabinet{std::move(cabinet_applet)}, controller{std::move(controller_applet)},
      error{std::move(error_applet)}, mii_edit{std::move(mii_edit_)},
      parental_controls{std::move(parental_controls_applet)},
      photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
      software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}

FrontendAppletSet::~FrontendAppletSet() = default;

FrontendAppletSet::FrontendAppletSet(FrontendAppletSet&&) noexcept = default;

FrontendAppletSet& FrontendAppletSet::operator=(FrontendAppletSet&&) noexcept = default;

FrontendAppletHolder::FrontendAppletHolder(Core::System& system_) : system{system_} {}

FrontendAppletHolder::~FrontendAppletHolder() = default;

const FrontendAppletSet& FrontendAppletHolder::GetFrontendAppletSet() const {
    return frontend;
}

NFP::CabinetMode FrontendAppletHolder::GetCabinetMode() const {
    return cabinet_mode;
}

AppletId FrontendAppletHolder::GetCurrentAppletId() const {
    return current_applet_id;
}

void FrontendAppletHolder::SetFrontendAppletSet(FrontendAppletSet set) {
    if (set.cabinet != nullptr) {
        frontend.cabinet = std::move(set.cabinet);
    }

    if (set.controller != nullptr) {
        frontend.controller = std::move(set.controller);
    }

    if (set.error != nullptr) {
        frontend.error = std::move(set.error);
    }

    if (set.mii_edit != nullptr) {
        frontend.mii_edit = std::move(set.mii_edit);
    }

    if (set.parental_controls != nullptr) {
        frontend.parental_controls = std::move(set.parental_controls);
    }

    if (set.photo_viewer != nullptr) {
        frontend.photo_viewer = std::move(set.photo_viewer);
    }

    if (set.profile_select != nullptr) {
        frontend.profile_select = std::move(set.profile_select);
    }

    if (set.software_keyboard != nullptr) {
        frontend.software_keyboard = std::move(set.software_keyboard);
    }

    if (set.web_browser != nullptr) {
        frontend.web_browser = std::move(set.web_browser);
    }
}

void FrontendAppletHolder::SetCabinetMode(NFP::CabinetMode mode) {
    cabinet_mode = mode;
}

void FrontendAppletHolder::SetCurrentAppletId(AppletId applet_id) {
    current_applet_id = applet_id;
}

void FrontendAppletHolder::SetDefaultAppletsIfMissing() {
    if (frontend.cabinet == nullptr) {
        frontend.cabinet = std::make_unique<Core::Frontend::DefaultCabinetApplet>();
    }

    if (frontend.controller == nullptr) {
        frontend.controller =
            std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore());
    }

    if (frontend.error == nullptr) {
        frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
    }

    if (frontend.mii_edit == nullptr) {
        frontend.mii_edit = std::make_unique<Core::Frontend::DefaultMiiEditApplet>();
    }

    if (frontend.parental_controls == nullptr) {
        frontend.parental_controls =
            std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
    }

    if (frontend.photo_viewer == nullptr) {
        frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
    }

    if (frontend.profile_select == nullptr) {
        frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
    }

    if (frontend.software_keyboard == nullptr) {
        frontend.software_keyboard =
            std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
    }

    if (frontend.web_browser == nullptr) {
        frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
    }
}

void FrontendAppletHolder::ClearAll() {
    frontend = {};
}

std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<Applet> applet,
                                                                AppletId id,
                                                                LibraryAppletMode mode) const {
    switch (id) {
    case AppletId::Auth:
        return std::make_shared<Auth>(system, applet, mode, *frontend.parental_controls);
    case AppletId::Cabinet:
        return std::make_shared<Cabinet>(system, applet, mode, *frontend.cabinet);
    case AppletId::Controller:
        return std::make_shared<Controller>(system, applet, mode, *frontend.controller);
    case AppletId::Error:
        return std::make_shared<Error>(system, applet, mode, *frontend.error);
    case AppletId::ProfileSelect:
        return std::make_shared<ProfileSelect>(system, applet, mode, *frontend.profile_select);
    case AppletId::SoftwareKeyboard:
        return std::make_shared<SoftwareKeyboard>(system, applet, mode,
                                                  *frontend.software_keyboard);
    case AppletId::MiiEdit:
        return std::make_shared<MiiEdit>(system, applet, mode, *frontend.mii_edit);
    case AppletId::Web:
    case AppletId::Shop:
    case AppletId::OfflineWeb:
    case AppletId::LoginShare:
    case AppletId::WebAuth:
        return std::make_shared<WebBrowser>(system, applet, mode, *frontend.web_browser);
    case AppletId::PhotoViewer:
        return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
    default:
        UNIMPLEMENTED_MSG(
            "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
            static_cast<u8>(id));
        return std::make_shared<StubApplet>(system, applet, id, mode);
    }
}

} // namespace Service::AM::Frontend