/**
 * @file nfc.h
 * @brief Nintendo Figurine (amiibo) Platform (nfp:user) service IPC wrapper.
 * @author averne
 * @copyright libnx Authors
 */

#pragma once
#include "../types.h"
#include "../sf/service.h"
#include "../services/mii.h"

/// NfpServiceType
typedef enum {
    NfpServiceType_User           = 0,  ///< Initializes nfp:user.
    NfpServiceType_Debug          = 1,  ///< Initializes nfp:dbg.
    NfpServiceType_System         = 2,  ///< Initializes nfp:sys.
} NfpServiceType;

/// NfcServiceType
typedef enum {
    NfcServiceType_User           = 0,  ///< Initializes nfc:user.
    NfcServiceType_System         = 1,  ///< Initializes nfc:sys.
} NfcServiceType;

typedef enum {
    NfcState_NonInitialized = 0,
    NfcState_Initialized    = 1,
} NfcState;

typedef enum {
    NfpDeviceState_Initialized     = 0,
    NfpDeviceState_SearchingForTag = 1,
    NfpDeviceState_TagFound        = 2,
    NfpDeviceState_TagRemoved      = 3,
    NfpDeviceState_TagMounted      = 4,
    NfpDeviceState_Unavailable     = 5,
} NfpDeviceState;

typedef enum {
    NfcDeviceState_Initialized     = 0,
    NfcDeviceState_SearchingForTag = 1,
    NfcDeviceState_TagFound        = 2,
    NfcDeviceState_TagRemoved      = 3,
    NfcDeviceState_TagMounted      = 4,
} NfcDeviceState;

typedef enum {
    NfcMifareDeviceState_Initialized     = 0,
    NfcMifareDeviceState_SearchingForTag = 1,
    NfcMifareDeviceState_TagFound        = 2,
    NfcMifareDeviceState_TagRemoved      = 3,
    NfcMifareDeviceState_TagMounted      = 4,
    NfcMifareDeviceState_Unavailable     = 5,
} NfcMifareDeviceState;

typedef enum {
    NfpApplicationAreaVersion_3DS     = 0,     ///< Application area created by a 3DS game.
    NfpApplicationAreaVersion_WiiU    = 1,     ///< Application area created by a Wii U game.
    NfpApplicationAreaVersion_3DSv2   = 2,     ///< Application area created by a (new?) 3DS game.
    NfpApplicationAreaVersion_Switch  = 3,     ///< Application area created by a Switch game.
    NfpApplicationAreaVersion_Invalid = 0xFF,  ///< Invalid value (application area not created).
} NfpApplicationAreaVersion;

typedef enum {
    NfpDeviceType_Amiibo = 0,
} NfpDeviceType;

typedef enum {
    NfpMountTarget_Rom = BIT(0),
    NfpMountTarget_Ram = BIT(1),
    NfpMountTarget_All = NfpMountTarget_Rom | NfpMountTarget_Ram,
} NfpMountTarget;

typedef enum {
    NfcProtocol_None  = 0,
    NfcProtocol_TypeA = BIT(0), ///< ISO14443A
    NfcProtocol_TypeB = BIT(1), ///< ISO14443B
    NfcProtocol_TypeF = BIT(2), ///< Sony FeliCa
    NfcProtocol_All   = 0xFFFFFFFF,
} NfcProtocol;

typedef enum {
    NfcTagType_None   = 0,
    NfcTagType_Type1  = BIT(0), ///< ISO14443A RW. Topaz
    NfcTagType_Type2  = BIT(1), ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
    NfcTagType_Type3  = BIT(2), ///< ISO14443A RW/RO. Sony FeliCa
    NfcTagType_Type4A = BIT(3), ///< ISO14443A RW/RO. DESFire
    NfcTagType_Type4B = BIT(4), ///< ISO14443B RW/RO. DESFire
    NfcTagType_Type5  = BIT(5), ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
    NfcTagType_Mifare = BIT(6), ///< Mifare clasic. Skylanders
    NfcTagType_All    = 0xFFFFFFFF,
} NfcTagType;

typedef enum {
    NfcMifareCommand_Read      = 0x30,
    NfcMifareCommand_AuthA     = 0x60,
    NfcMifareCommand_AuthB     = 0x61,
    NfcMifareCommand_Write     = 0xA0,
    NfcMifareCommand_Transfer  = 0xB0,
    NfcMifareCommand_Decrement = 0xC0,
    NfcMifareCommand_Increment = 0xC1,
    NfcMifareCommand_Store     = 0xC2,
} NfcMifareCommand;

typedef enum {
    NfpAmiiboFlag_Valid                 = BIT(0),   ///< Initialized in system settings.
    NfpAmiiboFlag_ApplicationAreaExists = BIT(1),   ///< Application area exists.
} NfpAmiiboFlag;

typedef enum {
    NfpBreakType_Flush  = 0,
    NfpBreakType_Break1 = 1,
    NfpBreakType_Break2 = 2,
} NfpBreakType;

typedef struct {
    u16 year;
    u8 month;
    u8 day;
} NfpDate;

typedef struct {
    u8 uid[10];             ///< UUID.
    u8 uid_length;          ///< UUID length.
    u8 reserved[0x15];
} NfcTagUid;

typedef struct {
    NfcTagUid uid;       ///< UUID.
    u32 protocol;        ///< \ref NfcProtocol
    u32 tag_type;        ///< \ref NfcTagType
    u8 reserved[0x30];
} NfpTagInfo;

typedef struct {
    NfcTagUid uid;       ///< UUID.
    u32 protocol;        ///< \ref NfcProtocol
    u32 tag_type;        ///< \ref NfcTagType
    u8 reserved[0x30];
} NfcTagInfo;

typedef struct {
    NfpDate last_write_date;
    u16 write_counter;
    u16 version;
    u32 application_area_size;
    u8 reserved[0x34];
} NfpCommonInfo;

typedef struct {
    union {
        u8 character_id[3];
        struct {
            u16 game_character_id;
            u8 character_variant;
        } NX_PACKED;
    };
    u8 series_id;       ///< Series.
    u16 numbering_id;   ///< Model number.
    u8 nfp_type;        ///< Figure type.
    u8 reserved[0x39];
} NfpModelInfo;

typedef struct {
    MiiCharInfo mii;
    NfpDate first_write_date;
    char amiibo_name[(10*4)+1];   ///< Amiibo name (utf-8, null-terminated).
    u8 font_region;
    u8 reserved[0x7A];
} NfpRegisterInfo;

typedef struct {
    MiiStoreData mii_store_data;
    NfpDate first_write_date;
    char amiibo_name[(10*4)+1];   ///< Amiibo name (utf-8, null-terminated).
    u8 font_region;
    u8 reserved[0x8E];
} NfpRegisterInfoPrivate;

typedef struct {
    u64 application_id;
    u32 access_id;
    u16 crc32_change_counter;
    u8 flags;
    u8 tag_type;
    u8 application_area_version;
    u8 reserved[0x2F];
} NfpAdminInfo;

typedef struct {
    u8 tag_magic;                                         ///< Tag magic (always 0xA5: https://www.3dbrew.org/wiki/Amiibo#Page_layout).
    u8 reserved1[0x1];
    u16 tag_write_counter;                                ///< Incremented every tag write.
    u32 crc32_1;                                          ///< CRC32 of some internal 8-byte data.
    u8 reserved2[0x38];
    NfpDate last_write_date;                              ///< Updated every write.
    u16 write_counter;                                    ///< Incremented every write, until it maxes out at 0xFFFF.
    u16 version;                                          ///< Version.
    u32 application_area_size;                            ///< Size of the application area.
    u8 reserved3[0x34];
    MiiVer3StoreData mii_v3;                              ///< Ver3StoreData (Mii format used in 3DS).
    u8 pad[0x2];
    u16 mii_v3_crc16;                                     ///< CRC16 of Ver3StoreData.
    MiiNfpStoreDataExtension mii_store_data_extension;    ///< StoreDataExtension
    NfpDate first_write_date;                             ///< Set when the amiibo is first written to.
    u16 amiibo_name[10+1];                                ///< Amiibo name (utf-16, null-terminated).
    u8 font_region;                                       ///< Font region.
    u8 unknown1;                                          ///< Normally zero
    u32 crc32_2;                                          ///< CRC32 of Ver3StoreData + application_id_byte + unknown1 + StoreDataExtension + unknown2 (0x7E bytes total)
    u32 unknown2[0x5];                                    ///< Normally zero
    u8 reserved4[0x64];
    u64 application_id;                                   ///< Modified application ID (Application ID & 0xFFFFFFFF0FFFFFFF | 0x30000000)
    u32 access_id;                                        ///< Application area access ID
    u16 settings_crc32_change_counter;
    u8 flags;                                             ///< \ref NfpAmiiboFlag
    u8 tag_type;                                          ///< \ref NfcTagType
    u8 application_area_version;                          ///< \ref NfpApplicationAreaVersion
    u8 application_id_byte;                               ///< Application ID byte ((Application ID >> 28) & 0xFF)
    u8 reserved5[0x2E];
    u8 application_area[0xD8];                            ///< Application area.
} NfpData;

typedef struct {
    u8 mifare_command;
    u8 unknown; ///< Usually 1
    u8 reserved1[0x6];
    u8 sector_key[0x6];
    u8 reserved2[0x2];
} NX_PACKED NfcSectorKey;

typedef struct {
    u8 sector_number;
    u8 reserved[0x7];
    NfcSectorKey sector_key;
} NX_PACKED NfcMifareReadBlockParameter;

typedef struct {
    u8 data[0x10];
    u8 sector_number;
    u8 reserved[0x7];
} NX_PACKED NfcMifareReadBlockData;

typedef struct {
    u8 data[0x10];
    u8 sector_number;
    u8 reserved[0x7];
    NfcSectorKey sector_key;
} NfcMifareWriteBlockParameter;

typedef struct {
    u64 version;
    u64 reserved[3];
} NfcRequiredMcuVersionData;

/// Nfc/Nfp DeviceHandle
typedef struct {
    u8 handle[0x8];            ///< Handle.
} NfcDeviceHandle;

/// Initialize nfp:*.
Result nfpInitialize(NfpServiceType service_type);

/// Exit nfp:*.
void nfpExit(void);

/// Initialize nfc:*.
Result nfcInitialize(NfcServiceType service_type);

/// Exit nfc:*.
void nfcExit(void);

/// Initialize nfc:mf:u.
Result nfcMfInitialize();

/// Exit nfc:mf:u.
void nfcMfExit(void);

/// Gets the Service object for the actual nfp:* service session.
Service* nfpGetServiceSession(void);

/// Gets the Service object for the interface from nfp:*.
Service* nfpGetServiceSession_Interface(void);

/// Gets the Service object for the actual nfc:* service session.
Service* nfcGetServiceSession(void);

/// Gets the Service object for the interface from nfc:*.
Service* nfcGetServiceSession_Interface(void);

/// Gets the Service object for the actual nfc:mf:u service session.
Service* nfcMfGetServiceSession(void);

/// Gets the Service object for the interface from nfc:mf:u.
Service* nfcMfGetServiceSession_Interface(void);

Result nfpListDevices(s32 *total_out, NfcDeviceHandle *out, s32 count);
Result nfpStartDetection(const NfcDeviceHandle *handle);
Result nfpStopDetection(const NfcDeviceHandle *handle);
Result nfpMount(const NfcDeviceHandle *handle, NfpDeviceType device_type, NfpMountTarget mount_target);
Result nfpUnmount(const NfcDeviceHandle *handle);

/// Only available with [4.0.0+].
Result nfcListDevices(s32 *total_out, NfcDeviceHandle *out, s32 count);
/// Only available with [4.0.0+].
Result nfcStartDetection(const NfcDeviceHandle *handle, NfcProtocol protocol);
/// Only available with [4.0.0+].
Result nfcStopDetection(const NfcDeviceHandle *handle);

Result nfcMfListDevices(s32 *total_out, NfcDeviceHandle *out, s32 count);
Result nfcMfStartDetection(const NfcDeviceHandle *handle);
Result nfcMfStopDetection(const NfcDeviceHandle *handle);

/// Not available with ::NfpServiceType_System.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpOpenApplicationArea(const NfcDeviceHandle *handle, u32 app_id);

/// Not available with ::NfpServiceType_System.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram, and the application area to be opened.
Result nfpGetApplicationArea(const NfcDeviceHandle *handle, void* buf, size_t buf_size, u32 *out_size);

/// Not available with ::NfpServiceType_System.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram, and the application area to be opened.
Result nfpSetApplicationArea(const NfcDeviceHandle *handle, const void* buf, size_t buf_size);

/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpFlush(const NfcDeviceHandle *handle);

Result nfpRestore(const NfcDeviceHandle *handle);

/// Not available with ::NfpServiceType_System.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpCreateApplicationArea(const NfcDeviceHandle *handle, u32 app_id, const void* buf, size_t buf_size);

/// Not available with ::NfpServiceType_System.
/// Only available with [3.0.0+].
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram, and the application area to be opened.
Result nfpRecreateApplicationArea(const NfcDeviceHandle *handle, u32 app_id, const void* buf, size_t buf_size);

/// Not available with ::NfpServiceType_System.
Result nfpGetApplicationAreaSize(const NfcDeviceHandle *handle, u32 *out_app_area_size);

/// Not available with ::NfpServiceType_User.
Result nfpDeleteApplicationArea(const NfcDeviceHandle *handle);

/// Not available with ::NfpServiceType_User.
Result nfpExistsApplicationArea(const NfcDeviceHandle *handle, bool *out);

Result nfpGetTagInfo(const NfcDeviceHandle *handle, NfpTagInfo *out);

/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpGetRegisterInfo(const NfcDeviceHandle *handle, NfpRegisterInfo *out);

/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpGetCommonInfo(const NfcDeviceHandle *handle, NfpCommonInfo *out);

/// Requires the amiibo to be mounted with ::NfpMountTarget_Rom.
Result nfpGetModelInfo(const NfcDeviceHandle *handle, NfpModelInfo *out);

/// Not available with ::NfpServiceType_User.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpGetAdminInfo(const NfcDeviceHandle *handle, NfpAdminInfo *out);

/// Only available with [4.0.0+].
Result nfcGetTagInfo(const NfcDeviceHandle *handle, NfcTagInfo *out);

Result nfcMfGetTagInfo(const NfcDeviceHandle *handle, NfcTagInfo *out);

/// Returned event will have autoclear off.
Result nfpAttachActivateEvent(const NfcDeviceHandle *handle, Event *out_event);
/// Returned event will have autoclear off.
Result nfpAttachDeactivateEvent(const NfcDeviceHandle *handle, Event *out_event);

/// Returned event will have autoclear off.
/// Only available with [4.0.0+].
Result nfcAttachActivateEvent(const NfcDeviceHandle *handle, Event *out_event);
/// Returned event will have autoclear off.
/// Only available with [4.0.0+].
Result nfcAttachDeactivateEvent(const NfcDeviceHandle *handle, Event *out_event);

/// Returned event will have autoclear off.
Result nfcMfAttachActivateEvent(const NfcDeviceHandle *handle, Event *out_event);
/// Returned event will have autoclear off.
Result nfcMfAttachDeactivateEvent(const NfcDeviceHandle *handle, Event *out_event);

Result nfpGetState(NfcState *out);
Result nfpGetDeviceState(const NfcDeviceHandle *handle, NfpDeviceState *out);
Result nfpGetNpadId(const NfcDeviceHandle *handle, u32 *out);

/// Only available with [4.0.0+].
Result nfcGetState(NfcState *out);
/// Only available with [4.0.0+].
Result nfcGetDeviceState(const NfcDeviceHandle *handle, NfcDeviceState *out);
/// Only available with [4.0.0+].
Result nfcGetNpadId(const NfcDeviceHandle *handle, u32 *out);

Result nfcMfGetState(NfcState *out);
Result nfcMfGetDeviceState(const NfcDeviceHandle *handle, NfcMifareDeviceState *out);
Result nfcMfGetNpadId(const NfcDeviceHandle *handle, u32 *out);

/// Returned event will have autoclear on.
/// Only available with [3.0.0+].
Result nfpAttachAvailabilityChangeEvent(Event *out_event);
/// Returned event will have autoclear on.
/// Only available with [4.0.0+].
Result nfcAttachAvailabilityChangeEvent(Event *out_event);
/// Returned event will have autoclear on.
Result nfcMfAttachAvailabilityChangeEvent(Event *out_event);

/// Not available with ::NfpServiceType_User.
Result nfpFormat(const NfcDeviceHandle *handle);

/// Not available with ::NfpServiceType_User.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpGetRegisterInfoPrivate(const NfcDeviceHandle *handle, NfpRegisterInfoPrivate *out);

/// Not available with ::NfpServiceType_User.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpSetRegisterInfoPrivate(const NfcDeviceHandle *handle, const NfpRegisterInfoPrivate *register_info_private);

/// Not available with ::NfpServiceType_User.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpDeleteRegisterInfo(const NfcDeviceHandle *handle);

/// Only available with ::NfpServiceType_Debug.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpGetAll(const NfcDeviceHandle *handle, NfpData *out);

/// Only available with ::NfpServiceType_Debug.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpSetAll(const NfcDeviceHandle *handle, const NfpData *nfp_data);

/// Only available with ::NfpServiceType_Debug.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpFlushDebug(const NfcDeviceHandle *handle);

/// Only available with ::NfpServiceType_Debug.
/// Requires the amiibo to be mounted with ::NfpMountTarget_Ram.
Result nfpBreakTag(const NfcDeviceHandle *handle, NfpBreakType break_type);

/// Only available with ::NfpServiceType_Debug.
Result nfpReadBackupData(const NfcDeviceHandle *handle, void* out_buf, size_t buf_size, u32 *out_size);

/// Only available with ::NfpServiceType_Debug.
Result nfpWriteBackupData(const NfcDeviceHandle *handle, const void* buf, size_t buf_size);

/// Only available with ::NfpServiceType_Debug.
Result nfpWriteNtf(const NfcDeviceHandle *handle, u32 write_type, const void* buf, size_t buf_size);

/// This uses nfc:*.
Result nfcIsNfcEnabled(bool *out);

/// Only available with [4.0.0+].
Result nfcReadMifare(const NfcDeviceHandle *handle, NfcMifareReadBlockData *out_block_data, const NfcMifareReadBlockParameter *read_block_parameter, s32 count);
/// Only available with [4.0.0+].
Result nfcWriteMifare(const NfcDeviceHandle *handle, const NfcMifareWriteBlockParameter *write_block_parameter, s32 count);

Result nfcMfReadMifare(const NfcDeviceHandle *handle, NfcMifareReadBlockData *out_block_data, const NfcMifareReadBlockParameter *read_block_parameter, s32 count);
Result nfcMfWriteMifare(const NfcDeviceHandle *handle, const NfcMifareWriteBlockParameter *write_block_parameter, s32 count);

/// Only available with [4.0.0+].
Result nfcSendCommandByPassThrough(const NfcDeviceHandle *handle, u64 timeout, const void* cmd_buf, size_t cmd_buf_size, void* reply_buf, size_t reply_buf_size, u64 *out_size);
/// Only available with [4.0.0+].
Result nfcKeepPassThroughSession(const NfcDeviceHandle *handle);
/// Only available with [4.0.0+].
Result nfcReleasePassThroughSession(const NfcDeviceHandle *handle);