#pragma once

#include "ui/menus/menu_base.hpp"
#include "yati/container/base.hpp"
#include "yati/source/base.hpp"
#include "ui/list.hpp"
#include <span>
#include <memory>

namespace sphaira::ui::menu::gc {

typedef enum {
    FsGameCardPartitionRaw_None   = -1,
    FsGameCardPartitionRaw_Normal = 0,
    FsGameCardPartitionRaw_Secure = 1,
} FsGameCardPartitionRaw;

////////////////////////////////////////////////
// The below structs are taken from nxdumptool./
////////////////////////////////////////////////

/// Located at offset 0x7000 in the gamecard image.
typedef struct {
    u8 signature[0x100];        ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the rest of the data.
    u32 magic;                  ///< "CERT".
    u32 version;
    u8 kek_index;
    u8 reserved[0x7];
    u8 t1_card_device_id[0x10];
    u8 iv[0x10];
    u8 hw_key[0x10];            ///< Encrypted.
    u8 data[0xC0];              ///< Encrypted.
} FsGameCardCertificate;

static_assert(sizeof(FsGameCardCertificate) == 0x200);

typedef struct {
    u8 maker_code;      ///< FsCardId1MakerCode.
    u8 memory_capacity; ///< Matches GameCardRomSize.
    u8 reserved;        ///< Known values: 0x00, 0x01, 0x02, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x80.
    u8 memory_type;     ///< FsCardId1MemoryType.
} FsCardId1;

static_assert(sizeof(FsCardId1) == 0x4);

typedef struct {
    u8 card_security_number;    ///< FsCardId2CardSecurityNumber.
    u8 card_type;               ///< FsCardId2CardType.
    u8 reserved[0x2];           ///< Usually filled with zeroes.
} FsCardId2;

static_assert(sizeof(FsCardId2) == 0x4);

typedef struct {
    u8 reserved[0x4];   ///< Usually filled with zeroes.
} FsCardId3;

static_assert(sizeof(FsCardId3) == 0x4);

/// Returned by fsDeviceOperatorGetGameCardIdSet.
typedef struct {
    FsCardId1 id1;  ///< Specifies maker code, memory capacity and memory type.
    FsCardId2 id2;  ///< Specifies card security number and card type.
    FsCardId3 id3;  ///< Always zero (so far).
} FsGameCardIdSet;

/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
typedef struct {
    union {
        u8 value[0x10];
        struct {
            u8 package_id[0x8]; ///< Matches package_id from GameCardHeader.
            u8 reserved[0x8];   ///< Just zeroes.
        };
    };
} GameCardKeySource;

static_assert(sizeof(GameCardKeySource) == 0x10);

/// Plaintext area. Dumped from FS program memory.
typedef struct {
    GameCardKeySource key_source;
    u8 encrypted_titlekey[0x10];    ///< Encrypted using AES-128-CCM with the decrypted key_source and the nonce from this section.
    u8 mac[0x10];                   ///< Used to verify the validity of the decrypted titlekey.
    u8 nonce[0xC];                  ///< Used as the IV to decrypt encrypted_titlekey using AES-128-CCM.
    u8 reserved[0x1C4];
} GameCardInitialData;

static_assert(sizeof(GameCardInitialData) == 0x200);

typedef struct {
    u8 maker_code;              ///< GameCardUidMakerCode.
    u8 version;                 ///< TODO: determine whether this matches GameCardVersion or not.
    u8 card_type;               ///< GameCardUidCardType.
    u8 unique_data[0x9];
    u32 random;
    u8 platform_flag;
    u8 reserved[0xB];
    FsCardId1 card_id_1_mirror; ///< This field mirrors bit 5 of FsCardId1MemoryType.
    u8 mac[0x20];
} GameCardUid;

static_assert(sizeof(GameCardUid) == 0x40);

/// Plaintext area. Dumped from FS program memory.
/// Overall structure may change with each new LAFW version.
typedef struct {
    u32 asic_security_mode; ///< Determines how the Lotus ASIC initialised the gamecard security mode. Usually 0xFFFFFFF9.
    u32 asic_status;        ///< Bitmask of the internal gamecard interface status. Usually 0x20000000.
    FsCardId1 card_id1;
    FsCardId2 card_id2;
    GameCardUid card_uid;
    u8 reserved[0x190];
    u8 mac[0x20];           ///< Changes with each gamecard (re)insertion.
} GameCardSpecificData;

static_assert(sizeof(GameCardSpecificData) == 0x200);

/// Plaintext area. Dumped from FS program memory.
/// This struct is returned by Lotus command "ChangeToSecureMode" (0xF). This means it is only available *after* the gamecard secure area has been mounted.
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory.
typedef struct {
    GameCardSpecificData specific_data;
    FsGameCardCertificate certificate;
    u8 reserved[0x200];
    GameCardInitialData initial_data;
} GameCardSecurityInformation;

static_assert(sizeof(GameCardSecurityInformation) == 0x800);

///////////////////
// nxdumptool fin./
///////////////////

struct GcCollection : yati::container::CollectionEntry {
    GcCollection(const char* _name, s64 _size, u8 _type, u8 _id_offset) {
        name = _name;
        size = _size;
        type = _type;
        id_offset = _id_offset;
    }

    // NcmContentType
    u8 type{};
    u8 id_offset{};
};

using GcCollections = std::vector<GcCollection>;

struct ApplicationEntry {
    u64 app_id{};
    u32 version{};
    u8 key_gen{};
    std::unique_ptr<NsApplicationControlData> control{};
    u64 control_size{};
    NacpLanguageEntry lang_entry{};

    std::vector<GcCollections> application{};
    std::vector<GcCollections> patch{};
    std::vector<GcCollections> add_on{};
    std::vector<GcCollections> data_patch{};
    yati::container::Collections tickets{};

    auto GetSize() const -> s64;
    auto GetSize(const std::vector<GcCollections>& entries) const -> s64;
};

struct Menu final : MenuBase {
    Menu(u32 flags);
    ~Menu();

    auto GetShortTitle() const -> const char* override { return "GC"; };
    void Update(Controller* controller, TouchInfo* touch) override;
    void Draw(NVGcontext* vg, Theme* theme) override;
    void OnFocusGained() override;

    Result GcStorageRead(void* buf, s64 off, s64 size);

private:
    Result GcPoll(bool* inserted);
    Result GcOnEvent(bool force = false);

    // GameCard FS api.
    Result GcMount();
    void GcUnmount();

    // GameCard Storage api
    Result GcMountStorage();
    void GcUmountStorage();
    Result GcMountPartition(FsGameCardPartitionRaw partition);
    void GcUnmountPartition();
    Result GcStorageReadInternal(void* buf, s64 off, s64 size, u64* bytes_read);

    // taken from nxdumptool.
    Result GcGetSecurityInfo(GameCardSecurityInformation& out);

    Result LoadControlData(ApplicationEntry& e);
    Result UpdateStorageSize();
    void FreeImage();
    void OnChangeIndex(s64 new_index);
    Result DumpGames(u32 flags);

private:
    FsDeviceOperator m_dev_op{};
    FsGameCardHandle m_handle{};
    std::unique_ptr<fs::FsNativeGameCard> m_fs{};
    FsEventNotifier m_event_notifier{};
    Event m_event{};

    std::vector<ApplicationEntry> m_entries{};
    std::unique_ptr<List> m_list{};
    s64 m_entry_index{};
    s64 m_option_index{};

    s64 m_size_free_sd{};
    s64 m_size_total_sd{};
    s64 m_size_free_nand{};
    s64 m_size_total_nand{};
    int m_icon{};
    bool m_mounted{};

    FsStorage m_storage{};
    // size of normal partition.
    s64 m_parition_normal_size{};
    // size of secure partition.
    s64 m_parition_secure_size{};
    // used size reported in the xci header.
    s64 m_storage_trimmed_size{};
    // total size of m_parition_normal_size + m_parition_secure_size.
    s64 m_storage_total_size{};
    // reported size via rom_size in the xci header.
    s64 m_storage_full_size{};
    // found in xci header.
    u64 m_package_id{};
    // found in xci header.
    u8 m_initial_data_hash[SHA256_HASH_SIZE]{};
    // currently mounted storage partiton.
    FsGameCardPartitionRaw m_partition{FsGameCardPartitionRaw_None};
    bool m_storage_mounted{};

    // set when the gc should be re-mounted, cleared when handled.
    bool m_dirty{};
};

} // namespace sphaira::ui::menu::gc
