/**
 * @file fs.h
 * @brief Filesystem (fsp-srv) service IPC wrapper.
 * Normally applications should just use standard stdio not FS-serv directly. However this can be used if obtaining a FsFileSystem, FsFile, or FsStorage, for mounting with fs_dev/romfs_dev, etc.
 * @author plutoo
 * @author yellows8
 * @copyright libnx Authors
 */
#pragma once
#include "../types.h"
#include "../kernel/event.h"
#include "../sf/service.h"

// We use wrapped handles for type safety.

#define FS_MAX_PATH 0x301

/// For use with FsSave.
#define FS_SAVEDATA_CURRENT_TITLEID 0

/// For use with \ref FsSave and \ref FsSaveDataInfo.
#define FS_SAVEDATA_USERID_COMMONSAVE 0

typedef struct {
    u8 c[0x10];
} FsRightsId;

typedef struct {
    Service  s;
} FsFileSystem;

typedef struct {
    Service  s;
} FsFile;

typedef struct {
    Service  s;
} FsDir;

typedef struct {
    Service  s;
} FsStorage;

typedef struct {
    Service  s;
} FsSaveDataIterator;

typedef struct {
    Service  s;
} FsEventNotifier;

typedef struct {
    Service  s;
} FsDeviceOperator;

/// Directory entry.
typedef struct
{
    char name[FS_MAX_PATH];      ///< Entry name.
    u8 pad[3];
    s8 type;       ///< See FsDirEntryType.
    u8 pad2[3];     ///< ?
    u64 fileSize;         ///< File size.
} FsDirectoryEntry;

/// Save Struct
typedef struct
{
    u64 titleID;                    ///< titleID of the savedata to access when accessing other titles' savedata via SaveData, otherwise FS_SAVEDATA_CURRENT_TITLEID.
    union { u128 userID; } PACKED;  ///< userID of the user-specific savedata to access, otherwise FS_SAVEDATA_USERID_COMMONSAVE. See account.h.
    u64 saveID;                     ///< saveID, 0 for SaveData.
    u8 saveDataType;                ///< See \ref FsSaveDataType.
    u8 rank;                        ///< Save data 'rank' or 'precedence'. 0 if this save data is considered the primary save data. 1 if it's considered the secondary save data.
    u16 index;                      ///< Save data index.
    u32 pad_x24;                    ///< Padding.
    u64 unk_x28;                    ///< 0 for SystemSaveData/SaveData.
    u64 unk_x30;                    ///< 0 for SystemSaveData/SaveData.
    u64 unk_x38;                    ///< 0 for SystemSaveData/SaveData.
} FsSave;

/// SaveDataExtraData Struct
typedef struct {
    FsSave save;      ///< Save struct.
    u64 ownerId;      ///< Title id of the owner of this save data. 0 for SystemSaveData.
    u64 timestamp;    ///< POSIX timestamp.
    u32 flags;        ///< Save data flags. See \ref FsSaveDataFlags.
    u32 unk_x54;      ///< Normally 0. Possibly unused?
    s64 dataSize;     ///< Usable save data size.
    s64 journalSize;  ///< Journal size of the save data.
    u64 commitId;     ///< Id of the latest commit.
    u8 unused[0x190]; ///< Uninitialized.
} FsSaveDataExtraData;

/// SaveCreate Struct
typedef struct {
    s64 size;           ///< Size of the save data.
    s64 journalSize;    ///< Journal size of the save data.
    u64 blockSize;      ///< Block size of the save data.
    u64 ownerId;        ///< Title id of the owner of this save data. 0 for SystemSaveData.
    u32 flags;          ///< Save data flags. See \ref FsSaveDataFlags.
    u8 saveDataSpaceId; ///< See \ref FsSaveDataSpaceId.
    u8 unk;             ///< 0 for SystemSaveData.
    u8 padding[0x1A];   ///< Uninitialized for SystemSaveData.
} FsSaveCreate;

typedef struct
{
    u64 saveID_unk;
    u8 saveDataSpaceId; ///< See \ref FsSaveDataSpaceId.
    u8 saveDataType;    ///< See \ref FsSaveDataType.
    u8 pad[6];          ///< Padding.
    u128 userID;        ///< See userID for \ref FsSave.
    u64 saveID;         ///< See saveID for \ref FsSave.
    u64 titleID;        ///< titleID for FsSaveDataType_SaveData.
    u64 size;           ///< Raw saveimage size.
    u16 index;          ///< Save data index.
    u8 rank;            ///< Save data 'rank' or 'precedence'. 0 if this save data is considered the primary save data. 1 if it's considered the secondary save data.
    u8 unk_x3b[0x25];   ///< Unknown. Usually zeros?
} FsSaveDataInfo;

typedef struct
{
    u64 created;  ///< POSIX timestamp.
    u64 modified; ///< POSIX timestamp.
    u64 accessed; ///< POSIX timestamp.
    u8 is_valid;  ///< 0x1 when the timestamps are set.
    u8 padding[7];
} FsTimeStampRaw;

/// Returned by fsFsGetEntryType.
typedef enum {
    FsDirEntryType_Dir  = 0, ///< Entry is a directory.
    FsDirEntryType_File = 1, ///< Entry is a file.
} FsDirEntryType;

/// For use with fsFsOpenFile.
typedef enum
{
    FsOpenMode_Read   = BIT(0), ///< Open for reading.
    FsOpenMode_Write  = BIT(1), ///< Open for writing.
    FsOpenMode_Append = BIT(2), ///< Append file.
} FsOpenMode;

/// For use with fsFsCreateFile.
typedef enum
{
    FsCreateOption_BigFile = BIT(0), ///< Creates a ConcatenationFile (dir with archive bit) instead of file.
} FsCreateOption;

/// For use with fsFsOpenDirectory.
typedef enum
{
    FsDirOpenMode_ReadDirs   = BIT(0),  ///< Enable reading directory entries.
    FsDirOpenMode_ReadFiles  = BIT(1),  ///< Enable reading file entries.
    FsDirOpenMode_NoFileSize = BIT(31), ///< Causes result entries to not contain filesize information (always 0).
} FsDirOpenMode;

/// For use with fsFileRead.
typedef enum
{
    FsReadOption_None = 0, ///< No option.
} FsReadOption;

/// For use with fsFileWrite.
typedef enum
{
    FsWriteOption_None  = 0,      ///< No option.
    FsWriteOption_Flush = BIT(0), ///< Forces a flush after write.
} FsWriteOption;

/// StorageId
typedef enum {
    FsStorageId_None       = 0,   ///< None
    FsStorageId_Host       = 1,   ///< Host
    FsStorageId_GameCard   = 2,   ///< GameCard
    FsStorageId_NandSystem = 3,   ///< NandSystem
    FsStorageId_NandUser   = 4,   ///< NandUser
    FsStorageId_SdCard     = 5,   ///< SdCard
} FsStorageId;

typedef enum
{
    FsContentStorageId_NandSystem = 0,
    FsContentStorageId_NandUser   = 1,
    FsContentStorageId_SdCard     = 2,
} FsContentStorageId;

typedef enum
{
    FsCustomStorageId_NandUser = 0,
    FsCustomStorageId_SdCard   = 1,
} FsCustomStorageId;

typedef enum
{
    FsSaveDataSpaceId_NandSystem       = 0,
    FsSaveDataSpaceId_NandUser         = 1,
    FsSaveDataSpaceId_SdCard           = 2,
    FsSaveDataSpaceId_TemporaryStorage = 3,

    FsSaveDataSpaceId_All              = -1, ///< Pseudo value for fsOpenSaveDataIterator().
} FsSaveDataSpaceId;

typedef enum
{
    FsSaveDataType_SystemSaveData           = 0,
    FsSaveDataType_SaveData                 = 1,
    FsSaveDataType_BcatDeliveryCacheStorage = 2,
    FsSaveDataType_DeviceSaveData           = 3,
    FsSaveDataType_TemporaryStorage         = 4, ///< [3.0.0+]
    FsSaveDataType_CacheStorage             = 5, ///< [3.0.0+]
} FsSaveDataType;

/// SaveDataFlags
typedef enum {
    FsSaveDataFlags_SurviveFactoryReset                    = BIT(0),
    FsSaveDataFlags_SurviveFactoryResetForRefurbishment    = BIT(1),
    FsSaveDataFlags_SurviveFactoryResetWithoutUserSaveData = BIT(2),
} FsSaveDataFlags;

typedef enum {
    FsGameCardAttribute_AutoBoot   = BIT(0), ///< Causes the cartridge to automatically start on bootup
    FsGameCardAttribute_ForceError = BIT(1), ///< Causes NS to throw an error on attempt to load the cartridge
    FsGameCardAttribute_Repair     = BIT(2), ///< Indicates that this gamecard is a repair tool.
} FsGameCardAttribute;

typedef enum {
    FsGameCardPartiton_Update = 0,
    FsGameCardPartiton_Normal = 1,
    FsGameCardPartiton_Secure = 2,
} FsGameCardPartiton;

typedef struct {
    u32 value;
} FsGameCardHandle;

typedef struct {
    u32 aes_ctr_key_type;           ///< Contains bitflags describing how data is AES encrypted.
    u32 speed_emulation_type;       ///< Contains bitflags describing how data is emulated.
    u32 reserved[0x38/sizeof(u32)];
} FsRangeInfo;

typedef enum {
    FsOperationId_Clear,           ///< Fill range with zero for supported file/storage.
    FsOperationId_ClearSignature,  ///< Clears signature for supported file/storage.
    FsOperationId_InvalidateCache, ///< Invalidates cache for supported file/storage.
    FsOperationId_QueryRange,      ///< Retrieves information on data for supported file/storage.
} FsOperationId;

typedef enum {
    FsBisStorageId_Boot0                           = 0,

    FsBisStorageId_Boot1                           = 10,

    FsBisStorageId_UserDataRoot                    = 20,
    FsBisStorageId_BootConfigAndPackage2NormalMain = 21,
    FsBisStorageId_BootConfigAndPackage2NormalSub  = 22,
    FsBisStorageId_BootConfigAndPackage2SafeMain   = 23,
    FsBisStorageId_BootConfigAndPackage2SafeSub    = 24,
    FsBisStorageId_BootConfigAndPackage2RepairMain = 25,
    FsBisStorageId_BootConfigAndPackage2RepairSub  = 26,
    FsBisStorageId_CalibrationBinary               = 27,
    FsBisStorageId_CalibrationFile                 = 28,
    FsBisStorageId_SafeMode                        = 29,
    FsBisStorageId_User                            = 30,
    FsBisStorageId_System                          = 31,
    FsBisStorageId_SystemProperEncryption          = 32,
    FsBisStorageId_SystemProperPartition           = 33,
} FsBisStorageId;

typedef enum {
    FsPriority_Normal     = 0,
    FsPriority_Realtime   = 1,
    FsPriority_Low        = 2,
    FsPriority_Background = 3,
} FsPriority;

Result fsInitialize(void);
void fsExit(void);

Service* fsGetServiceSession(void);

void fsSetPriority(FsPriority prio);

Result fsOpenBisStorage(FsStorage* out, FsBisStorageId partitionId);
Result fsOpenBisFileSystem(FsFileSystem* out, FsBisStorageId partitionId, const char* string);

Result fsCreateSaveDataFileSystemBySystemSaveDataId(const FsSave* save, const FsSaveCreate* create);
Result fsDeleteSaveDataFileSystemBySaveDataSpaceId(FsSaveDataSpaceId saveDataSpaceId, u64 saveID);

Result fsIsExFatSupported(bool* out);
Result fsOpenGameCardFileSystem(FsFileSystem* out, const FsGameCardHandle* handle, FsGameCardPartiton partition);
Result fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(void* buf, size_t len, FsSaveDataSpaceId saveDataSpaceId, u64 saveID);
Result fsReadSaveDataFileSystemExtraData(void* buf, size_t len, u64 saveID);
Result fsWriteSaveDataFileSystemExtraData(const void* buf, size_t len, FsSaveDataSpaceId saveDataSpaceId, u64 saveID);
Result fsExtendSaveDataFileSystem(FsSaveDataSpaceId saveDataSpaceId, u64 saveID, s64 dataSize, s64 journalSize); /// [3.0.0+]

/// Do not call this directly, see fs_dev.h.
Result fsMountSdcard(FsFileSystem* out);

Result fsMountSaveData(FsFileSystem* out, u8 inval, const FsSave *save);
Result fsMountSystemSaveData(FsFileSystem* out, u8 inval, const FsSave *save);
Result fsOpenSaveDataIterator(FsSaveDataIterator* out, FsSaveDataSpaceId saveDataSpaceId);
Result fsOpenContentStorageFileSystem(FsFileSystem* out, FsContentStorageId content_storage_id);
Result fsOpenCustomStorageFileSystem(FsFileSystem* out, FsCustomStorageId custom_storage_id); /// [7.0.0+]
Result fsOpenDataStorageByCurrentProcess(FsStorage* out);
Result fsOpenDataStorageByDataId(FsStorage* out, u64 dataId, FsStorageId storageId);
Result fsOpenDeviceOperator(FsDeviceOperator* out);
Result fsOpenSdCardDetectionEventNotifier(FsEventNotifier* out);

/// Retrieves the rights id corresponding to the content path. Only available on [2.0.0+].
Result fsGetRightsIdByPath(const char* path, FsRightsId* out_rights_id);

/// Retrieves the rights id and key generation corresponding to the content path. Only available on [3.0.0+].
Result fsGetRightsIdAndKeyGenerationByPath(const char* path, u8* out_key_generation, FsRightsId* out_rights_id);

Result fsDisableAutoSaveDataCreation(void);

Result fsSetGlobalAccessLogMode(u32 mode);
Result fsGetGlobalAccessLogMode(u32* out_mode);
// todo: Rest of commands here

// Wrapper(s) for fsCreateSaveDataFileSystemBySystemSaveDataId.
Result fsCreate_SystemSaveDataWithOwner(FsSaveDataSpaceId saveDataSpaceId, u64 saveID, u128 userID, u64 ownerId, u64 size, u64 journalSize, u32 flags);
Result fsCreate_SystemSaveData(FsSaveDataSpaceId saveDataSpaceId, u64 saveID, u64 size, u64 journalSize, u32 flags);

/// FsFileSystem can be mounted with fs_dev for use with stdio, see fs_dev.h.

/// Wrapper(s) for fsMountSaveData.
/// See FsSave for titleID and userID.
Result fsMount_SaveData(FsFileSystem* out, u64 titleID, u128 userID);

/// Wrapper for fsMountSystemSaveData.
/// WARNING: You can brick when writing to SystemSaveData, if the data is corrupted etc.
Result fsMount_SystemSaveData(FsFileSystem* out, u64 saveID);

typedef enum
{
    FsFileSystemType_Logo               = 2,
    FsFileSystemType_ContentControl     = 3,
    FsFileSystemType_ContentManual      = 4,
    FsFileSystemType_ContentMeta        = 5,
    FsFileSystemType_ContentData        = 6,
    FsFileSystemType_ApplicationPackage = 7,
} FsFileSystemType;

typedef enum
{
    FsFileSystemQueryType_SetArchiveBit = 0,
} FsFileSystemQueryType;

/// Mount requested filesystem type from content file
Result fsOpenFileSystem(FsFileSystem* out, FsFileSystemType fsType, const char* contentPath); /// same as calling fsOpenFileSystemWithId with 0 as titleId
Result fsOpenFileSystemWithId(FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath); /// works on all firmwares, titleId is ignored on [1.0.0]
Result fsOpenFileSystemWithPatch(FsFileSystem* out, u64 titleId, FsFileSystemType fsType); /// [2.0.0+], like OpenFileSystemWithId but without content path.

// IFileSystem
Result fsFsCreateFile(FsFileSystem* fs, const char* path, u64 size, u32 option);
Result fsFsDeleteFile(FsFileSystem* fs, const char* path);
Result fsFsCreateDirectory(FsFileSystem* fs, const char* path);
Result fsFsDeleteDirectory(FsFileSystem* fs, const char* path);
Result fsFsDeleteDirectoryRecursively(FsFileSystem* fs, const char* path);
Result fsFsRenameFile(FsFileSystem* fs, const char* cur_path, const char* new_path);
Result fsFsRenameDirectory(FsFileSystem* fs, const char* cur_path, const char* new_path);
Result fsFsGetEntryType(FsFileSystem* fs, const char* path, FsDirEntryType* out);
Result fsFsOpenFile(FsFileSystem* fs, const char* path, u32 mode, FsFile* out);
Result fsFsOpenDirectory(FsFileSystem* fs, const char* path, u32 mode, FsDir* out);
Result fsFsCommit(FsFileSystem* fs);
Result fsFsGetFreeSpace(FsFileSystem* fs, const char* path, u64* out);
Result fsFsGetTotalSpace(FsFileSystem* fs, const char* path, u64* out);
Result fsFsGetFileTimeStampRaw(FsFileSystem* fs, const char* path, FsTimeStampRaw *out); /// [3.0.0+]
Result fsFsCleanDirectoryRecursively(FsFileSystem* fs, const char* path); /// [3.0.0+]
Result fsFsQueryEntry(FsFileSystem* fs, void *out, size_t out_size, const void *in, size_t in_size, const char* path, FsFileSystemQueryType query_type); /// [4.0.0+]
void fsFsClose(FsFileSystem* fs);

/// Uses \ref fsFsQueryEntry to set the archive bit on the specified absolute directory path.
/// This will cause HOS to treat the directory as if it were a file containing the directory's concatenated contents.
Result fsFsSetArchiveBit(FsFileSystem* fs, const char *path);

// IFile
Result fsFileRead(FsFile* f, u64 off, void* buf, u64 read_size, u32 option, u64* bytes_read);
Result fsFileWrite(FsFile* f, u64 off, const void* buf, u64 write_size, u32 option);
Result fsFileFlush(FsFile* f);
Result fsFileSetSize(FsFile* f, u64 sz);
Result fsFileGetSize(FsFile* f, u64* out);
Result fsFileOperateRange(FsFile* f, FsOperationId op_id, u64 off, u64 len, FsRangeInfo* out); /// [4.0.0+]
void fsFileClose(FsFile* f);

// IDirectory
Result fsDirRead(FsDir* d, u64 inval, u64* total_entries, size_t max_entries, FsDirectoryEntry *buf);
Result fsDirGetEntryCount(FsDir* d, u64* count);
void fsDirClose(FsDir* d);

// IStorage
Result fsStorageRead(FsStorage* s, u64 off, void* buf, u64 read_size);
Result fsStorageWrite(FsStorage* s, u64 off, const void* buf, u64 write_size);
Result fsStorageFlush(FsStorage* s);
Result fsStorageSetSize(FsStorage* s, u64 sz);
Result fsStorageGetSize(FsStorage* s, u64* out);
Result fsStorageOperateRange(FsStorage* s, FsOperationId op_id, u64 off, u64 len, FsRangeInfo* out); /// [4.0.0+]
void fsStorageClose(FsStorage* s);

// ISaveDataInfoReader

/// Read FsSaveDataInfo data into the buf array.
Result fsSaveDataIteratorRead(FsSaveDataIterator *s, FsSaveDataInfo* buf, size_t max_entries, u64* total_entries);
void fsSaveDataIteratorClose(FsSaveDataIterator *s);

// IEventNotifier
Result fsEventNotifierGetEventHandle(FsEventNotifier* e, Event* out, bool autoclear);
void fsEventNotifierClose(FsEventNotifier* e);

// IDeviceOperator
Result fsDeviceOperatorIsSdCardInserted(FsDeviceOperator* d, bool* out);
Result fsDeviceOperatorIsGameCardInserted(FsDeviceOperator* d, bool* out);
Result fsDeviceOperatorGetGameCardHandle(FsDeviceOperator* d, FsGameCardHandle* out);
Result fsDeviceOperatorGetGameCardAttribute(FsDeviceOperator* d, const FsGameCardHandle* handle, u8 *out);
void fsDeviceOperatorClose(FsDeviceOperator* d);