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

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/applets/mii_edit.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/frontend/applet_mii_edit.h"
#include "core/hle/service/am/storage.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/sm/sm.h"

namespace Service::AM::Frontend {

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

MiiEdit::~MiiEdit() = default;

void MiiEdit::Initialize() {
    // Note: MiiEdit is not initialized with common arguments.
    //       Instead, it is initialized by an AppletInput storage with size 0x100 bytes.
    //       Do NOT call Applet::Initialize() here.

    const std::shared_ptr<IStorage> storage = PopInData();
    ASSERT(storage != nullptr);

    const auto applet_input_data = storage->GetData();
    ASSERT(applet_input_data.size() >= sizeof(MiiEditAppletInputCommon));

    std::memcpy(&applet_input_common, applet_input_data.data(), sizeof(MiiEditAppletInputCommon));

    LOG_INFO(Service_AM,
             "Initializing MiiEdit Applet with MiiEditAppletVersion={} and MiiEditAppletMode={}",
             applet_input_common.version, applet_input_common.applet_mode);

    switch (applet_input_common.version) {
    case MiiEditAppletVersion::Version3:
        ASSERT(applet_input_data.size() ==
               sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV3));
        std::memcpy(&applet_input_v3, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
                    sizeof(MiiEditAppletInputV3));
        break;
    case MiiEditAppletVersion::Version4:
        ASSERT(applet_input_data.size() ==
               sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4));
        std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
                    sizeof(MiiEditAppletInputV4));
        break;
    default:
        UNIMPLEMENTED_MSG("Unknown MiiEditAppletVersion={} with size={}",
                          applet_input_common.version, applet_input_data.size());
        ASSERT(applet_input_data.size() >=
               sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4));
        std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
                    sizeof(MiiEditAppletInputV4));
        break;
    }

    manager = system.ServiceManager().GetService<Mii::IStaticService>("mii:e")->GetMiiManager();
    if (manager == nullptr) {
        manager = std::make_shared<Mii::MiiManager>();
    }
    manager->Initialize(metadata);
}

Result MiiEdit::GetStatus() const {
    return ResultSuccess;
}

void MiiEdit::ExecuteInteractive() {
    ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet.");
}

void MiiEdit::Execute() {
    if (is_complete) {
        return;
    }

    // This is a default stub for each of the MiiEdit applet modes.
    switch (applet_input_common.applet_mode) {
    case MiiEditAppletMode::ShowMiiEdit:
    case MiiEditAppletMode::AppendMiiImage:
    case MiiEditAppletMode::UpdateMiiImage:
        MiiEditOutput(MiiEditResult::Success, 0);
        break;
    case MiiEditAppletMode::AppendMii: {
        Mii::StoreData store_data{};
        store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
        store_data.SetNickname({u'y', u'u', u'z', u'u'});
        store_data.SetChecksum();
        const auto result = manager->AddOrReplace(metadata, store_data);

        if (result.IsError()) {
            MiiEditOutput(MiiEditResult::Cancel, 0);
            break;
        }

        s32 index = manager->FindIndex(store_data.GetCreateId(), false);

        if (index == -1) {
            MiiEditOutput(MiiEditResult::Cancel, 0);
            break;
        }

        MiiEditOutput(MiiEditResult::Success, index);
        break;
    }
    case MiiEditAppletMode::CreateMii: {
        Mii::CharInfo char_info{};
        manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);

        const MiiEditCharInfo edit_char_info{
            .mii_info{char_info},
        };

        MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
        break;
    }
    case MiiEditAppletMode::EditMii: {
        const MiiEditCharInfo edit_char_info{
            .mii_info{applet_input_v4.char_info.mii_info},
        };

        MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
        break;
    }
    default:
        UNIMPLEMENTED_MSG("Unknown MiiEditAppletMode={}", applet_input_common.applet_mode);

        MiiEditOutput(MiiEditResult::Success, 0);
        break;
    }
}

void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) {
    const MiiEditAppletOutput applet_output{
        .result{result},
        .index{index},
    };

    LOG_INFO(Input, "called, result={}, index={}", result, index);

    std::vector<u8> out_data(sizeof(MiiEditAppletOutput));
    std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput));

    is_complete = true;

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

void MiiEdit::MiiEditOutputForCharInfoEditing(MiiEditResult result,
                                              const MiiEditCharInfo& char_info) {
    const MiiEditAppletOutputForCharInfoEditing applet_output{
        .result{result},
        .char_info{char_info},
    };

    std::vector<u8> out_data(sizeof(MiiEditAppletOutputForCharInfoEditing));
    std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutputForCharInfoEditing));

    is_complete = true;

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

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

} // namespace Service::AM::Frontend
