/**
 * Copyright (c) 2011-2025 Bill Greiman
 * This file is part of the SdFat library for SD memory cards.
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
//------------------------------------------------------------------------------
// See:
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-systems
// https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification
// https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc
// https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/FileIO/exfat-specification.md
// https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html
// https://en.wikipedia.org/wiki/File_Allocation_Table
//------------------------------------------------------------------------------
typedef uint32_t Cluster_t;
//------------------------------------------------------------------------------
void lbaToMbrChs(uint8_t *chs, uint32_t capacityMB, uint32_t lba);
//------------------------------------------------------------------------------
#if !defined(USE_SIMPLE_LITTLE_ENDIAN) || USE_SIMPLE_LITTLE_ENDIAN
// assumes CPU is little-endian and handles alignment issues.
inline uint16_t getLe16(const uint8_t *src)
{
    return *reinterpret_cast<const uint16_t *>(src);
}
inline uint32_t getLe32(const uint8_t *src)
{
    return *reinterpret_cast<const uint32_t *>(src);
}
inline uint64_t getLe64(const uint8_t *src)
{
    return *reinterpret_cast<const uint64_t *>(src);
}
inline void setLe16(uint8_t *dst, uint16_t src)
{
    *reinterpret_cast<uint16_t *>(dst) = src;
}
inline void setLe32(uint8_t *dst, uint32_t src)
{
    *reinterpret_cast<uint32_t *>(dst) = src;
}
inline void setLe64(uint8_t *dst, uint64_t src)
{
    *reinterpret_cast<uint64_t *>(dst) = src;
}
#else  // USE_SIMPLE_LITTLE_ENDIAN
inline uint16_t getLe16(const uint8_t *src)
{
    return static_cast<uint16_t>(src[0]) << 0 | static_cast<uint16_t>(src[1]) << 8;
}
inline uint32_t getLe32(const uint8_t *src)
{
    return static_cast<uint32_t>(src[0]) << 0 | static_cast<uint32_t>(src[1]) << 8 |
           static_cast<uint32_t>(src[2]) << 16 | static_cast<uint32_t>(src[3]) << 24;
}
inline uint64_t getLe64(const uint8_t *src)
{
    return static_cast<uint64_t>(src[0]) << 0 | static_cast<uint64_t>(src[1]) << 8 |
           static_cast<uint64_t>(src[2]) << 16 | static_cast<uint64_t>(src[3]) << 24 |
           static_cast<uint64_t>(src[4]) << 32 | static_cast<uint64_t>(src[5]) << 40 |
           static_cast<uint64_t>(src[7]) << 56;
}
inline void setLe16(uint8_t *dst, uint16_t src)
{
    dst[0] = src >> 0;
    dst[1] = src >> 8;
}
inline void setLe32(uint8_t *dst, uint32_t src)
{
    dst[0] = src >> 0;
    dst[1] = src >> 8;
    dst[2] = src >> 16;
    dst[3] = src >> 24;
}
inline void setLe64(uint8_t *dst, uint64_t src)
{
    dst[0] = src >> 0;
    dst[1] = src >> 8;
    dst[2] = src >> 16;
    dst[3] = src >> 24;
    dst[4] = src >> 32;
    dst[5] = src >> 40;
    dst[6] = src >> 48;
    dst[7] = src >> 56;
}
#endif // USE_SIMPLE_LITTLE_ENDIAN
//------------------------------------------------------------------------------
// Size of FAT and exFAT directory structures.
const size_t FS_DIR_SIZE = 32;
//------------------------------------------------------------------------------
// Reserved characters for exFAT names and FAT LFN.
inline bool lfnReservedChar(uint8_t c)
{
    return c < 0X20 || c == '"' || c == '*' || c == '/' || c == ':' || c == '<' || c == '>' || c == '?' || c == '\\' ||
           c == '|';
}
//------------------------------------------------------------------------------
// Reserved characters for FAT short 8.3 names.
inline bool sfnReservedChar(uint8_t c)
{
    if (c == '"' || c == '|' || c == '[' || c == '\\' || c == ']')
    {
        return true;
    }
    //  *+,./ or :;<=>?
    if ((0X2A <= c && c <= 0X2F && c != 0X2D) || (0X3A <= c && c <= 0X3F))
    {
        return true;
    }
    // Reserved if not in range (0X20, 0X7F).
    return !(0X20 < c && c < 0X7F);
}
//------------------------------------------------------------------------------
const uint16_t MBR_SIGNATURE = 0xAA55;
const uint16_t PBR_SIGNATURE = 0xAA55;
const uint8_t MBR_TYPE_FAT12 = 0x01;  // FAT with 12-bit LBA.
const uint8_t MBR_TYPE_FAT16 = 0x04;  // Original FAT16 with 16-bit LBA.
const uint8_t MBR_TYPE_FAT16B = 0x06; // Modern FAT16 with 32-bit LBA.
const uint8_t MBR_TYPE_FAT32 = 0x0C;  // Modern FAT32 with 32-bit LBA.
const uint8_t MBR_TYPE_EX_FAT = 0x07; // NTFS or exFAT.
const uint8_t MBR_TYPE_GPT = 0xEE;    // Protective MBR for GPT disk.
typedef struct mbrPartition
{
    uint8_t boot;
    uint8_t beginCHS[3];
    uint8_t type;
    uint8_t endCHS[3];
    uint8_t startSector[4];
    uint8_t totalSectors[4];
} MbrPart_t;
//------------------------------------------------------------------------------
typedef struct masterBootRecordSector
{
    uint8_t bootCode[446];
    MbrPart_t part[4];
    uint8_t signature[2];
} MbrSector_t;
//------------------------------------------------------------------------------

typedef struct GptHeader
{
    uint8_t signature[8];                // must contain the ASCII string “EFI PART”
    uint8_t revision[4];                 // The revision number for this header
    uint8_t headerSize[4];               // Size in bytes of the GPT Header
    uint8_t headerCRC32[4];              // CRC32 checksum for the GPT Header structure.
    uint8_t reserved[4];                 // Must be zero.
    uint8_t myLBA[8];                    // The LBA that contains this data structure.
    uint8_t alternateLBA[8];             // LBA address of the alternate GPT Header.
    uint8_t firstUsableLBA[8];           // The first usable LBA for a partition.
    uint8_t lastUsableLBA[8];            // The last usable LBA for a partition.
    uint8_t diskGUID[16];                // GUID that uniquely identify the disk.
    uint8_t partitionEntryLBA[8];        // Starting LBA of Partition Entry array.
    uint8_t numberOfPartitionEntries[4]; // The number of Partition Entries.
    uint8_t sizeOfPartitionEntry[4];     // Size of GUID each Partition Entry.
    uint8_t partitionEntryArrayCRC32;    // PartitionE ntryArrayCRC32.
    // The rest of the block is reserved by UEFI and must be zero.
    // Verify signature.
    bool valid()
    {
        return memcmp(signature, "EFI PART", 8) == 0;
    }
} GptHeader_t;
//------------------------------------------------------------------------------
// Microsoft basic data partition GUID: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
// partitionTypeGUID: A2 A0 D0 EB E5 B9 33 44 87 C0 68 B6 B7 26 99 C7
static const uint8_t MS_TYPE_GUID[16] = {0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44,
                                         0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7};
typedef struct GptPart
{
    uint8_t partitionTypeGUID[16];   // Unique ID for partition, zero if unused.
    uint8_t uniquePartitionGUID[16]; // GUID that is unique for every entry.
    uint8_t startingLBA[8];          // Starting LBA for this entry.
    uint8_t endingLBA[8];            //  Ending LBA for this entry.
    uint8_t attributes[8];           // Attribute bits, all bits reserved by UEFI.
    uint8_t partitionName[72];       // Null-terminated string.
    // Entry is used.
    bool used()
    {
        for (int i = 0; i < 16; i++)
        {
            if (partitionTypeGUID[i])
            {
                return true;
            }
        }
        return false;
    }
    // Verify GUID for Microsoft basic data partition.
    bool isMsBasicPartition()
    {
        return memcmp(partitionTypeGUID, MS_TYPE_GUID, 16) == 0;
    }
} GptPart_t;
//------------------------------------------------------------------------------
typedef struct partitionBootSector
{
    uint8_t jmpInstruction[3];
    char oemName[8];
    uint8_t bpb[109];
    uint8_t bootCode[390];
    uint8_t signature[2];
} pbs_t;
//------------------------------------------------------------------------------
typedef struct
{
    uint8_t type;
    uint8_t data[31];
} DirGeneric_t;
//==============================================================================
typedef struct
{
    uint64_t position;
    uint32_t cluster;
} fspos_t;
//==============================================================================
const uint8_t EXTENDED_BOOT_SIGNATURE = 0X29;
typedef struct biosParameterBlockFat16
{
    uint8_t bytesPerSector[2];
    uint8_t sectorsPerCluster;
    uint8_t reservedSectorCount[2];
    uint8_t fatCount;
    uint8_t rootDirEntryCount[2];
    uint8_t totalSectors16[2];
    uint8_t mediaType;
    uint8_t sectorsPerFat16[2];
    uint8_t sectorsPerTrtack[2];
    uint8_t headCount[2];
    uint8_t hidddenSectors[4];
    uint8_t totalSectors32[4];

    uint8_t physicalDriveNumber;
    uint8_t extReserved;
    uint8_t extSignature;
    uint8_t volumeSerialNumber[4];
    uint8_t volumeLabel[11];
    uint8_t volumeType[8];
} BpbFat16_t;
//------------------------------------------------------------------------------
typedef struct biosParameterBlockFat32
{
    uint8_t bytesPerSector[2];
    uint8_t sectorsPerCluster;
    uint8_t reservedSectorCount[2];
    uint8_t fatCount;
    uint8_t rootDirEntryCount[2];
    uint8_t totalSectors16[2];
    uint8_t mediaType;
    uint8_t sectorsPerFat16[2];
    uint8_t sectorsPerTrtack[2];
    uint8_t headCount[2];
    uint8_t hidddenSectors[4];
    uint8_t totalSectors32[4];

    uint8_t sectorsPerFat32[4];
    uint8_t fat32Flags[2];
    uint8_t fat32Version[2];
    uint8_t fat32RootCluster[4];
    uint8_t fat32FSInfoSector[2];
    uint8_t fat32BackBootSector[2];
    uint8_t fat32Reserved[12];

    uint8_t physicalDriveNumber;
    uint8_t extReserved;
    uint8_t extSignature;
    uint8_t volumeSerialNumber[4];
    uint8_t volumeLabel[11];
    uint8_t volumeType[8];
} BpbFat32_t;
//------------------------------------------------------------------------------
typedef struct partitionBootSectorFat
{
    uint8_t jmpInstruction[3];
    char oemName[8];
    union {
        uint8_t bpb[109];
        BpbFat16_t bpb16;
        BpbFat32_t bpb32;
    } bpb;
    uint8_t bootCode[390];
    uint8_t signature[2];
} PbsFat_t;
//------------------------------------------------------------------------------
const uint32_t FSINFO_LEAD_SIGNATURE = 0X41615252;
const uint32_t FSINFO_STRUCT_SIGNATURE = 0x61417272;
const uint32_t FSINFO_TRAIL_SIGNATURE = 0xAA550000;
typedef struct FsInfoSector
{
    uint8_t leadSignature[4];
    uint8_t reserved1[480];
    uint8_t structSignature[4];
    uint8_t freeCount[4];
    uint8_t nextFree[4];
    uint8_t reserved2[12];
    uint8_t trailSignature[4];
} FsInfo_t;
//==============================================================================
/** Attributes common to FAT and exFAT */
const uint8_t FS_ATTRIB_READ_ONLY = 0x01;
const uint8_t FS_ATTRIB_HIDDEN = 0x02;
const uint8_t FS_ATTRIB_SYSTEM = 0x04;
const uint8_t FS_ATTRIB_DIRECTORY = 0x10;
const uint8_t FS_ATTRIB_ARCHIVE = 0x20;
// Attributes that users can change.
const uint8_t FS_ATTRIB_USER_SETTABLE = FS_ATTRIB_READ_ONLY | FS_ATTRIB_HIDDEN | FS_ATTRIB_SYSTEM | FS_ATTRIB_ARCHIVE;
// Attributes to copy when a file is opened.
const uint8_t FS_ATTRIB_COPY = FS_ATTRIB_USER_SETTABLE | FS_ATTRIB_DIRECTORY;
//==============================================================================
/** name[0] value for entry that is free and no allocated entries follow */
const uint8_t FAT_NAME_FREE = 0X00;
/** name[0] value for entry that is free after being "deleted" */
const uint8_t FAT_NAME_DELETED = 0XE5;
// Directory attribute of volume label.
const uint8_t FAT_ATTRIB_LABEL = 0x08;
const uint8_t FAT_ATTRIB_LONG_NAME = 0X0F;
/** Filename base-name is all lower case */
const uint8_t FAT_CASE_LC_BASE = 0X08;
/** Filename extension is all lower case.*/
const uint8_t FAT_CASE_LC_EXT = 0X10;

typedef struct
{
    uint8_t name[11];
    uint8_t attributes;
    uint8_t caseFlags;
    uint8_t createTimeMs;
    uint8_t createTime[2];
    uint8_t createDate[2];
    uint8_t accessDate[2];
    uint8_t firstClusterHigh[2];
    uint8_t modifyTime[2];
    uint8_t modifyDate[2];
    uint8_t firstClusterLow[2];
    uint8_t fileSize[4];
} DirFat_t;

static inline bool isFatFile(const DirFat_t *dir)
{
    return (dir->attributes & (FS_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) == 0;
}
static inline bool isFatFileOrSubdir(const DirFat_t *dir)
{
    return (dir->attributes & FAT_ATTRIB_LABEL) == 0;
}
static inline uint8_t isFatLongName(const DirFat_t *dir)
{
    return dir->attributes == FAT_ATTRIB_LONG_NAME;
}
static inline bool isFatSubdir(const DirFat_t *dir)
{
    return (dir->attributes & (FS_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) == FS_ATTRIB_DIRECTORY;
}
//------------------------------------------------------------------------------
/**
 * Order mask that indicates the entry is the last long dir entry in a
 * set of long dir entries. All valid sets of long dir entries must
 * begin with an entry having this mask.
 */
const uint8_t FAT_ORDER_LAST_LONG_ENTRY = 0X40;
/** Max long file name length */

const uint8_t FAT_MAX_LFN_LENGTH = 255;
typedef struct
{
    uint8_t order;
    uint8_t unicode1[10];
    uint8_t attributes;
    uint8_t mustBeZero1;
    uint8_t checksum;
    uint8_t unicode2[12];
    uint8_t mustBeZero2[2];
    uint8_t unicode3[4];
} DirLfn_t;
//==============================================================================
inline uint32_t exFatChecksum(uint32_t sum, uint8_t data)
{
    return (sum << 31) + (sum >> 1) + data;
}
//------------------------------------------------------------------------------
typedef struct biosParameterBlockExFat
{
    uint8_t mustBeZero[53];
    uint8_t partitionOffset[8];
    uint8_t volumeLength[8];
    uint8_t fatOffset[4];
    uint8_t fatLength[4];
    uint8_t clusterHeapOffset[4];
    uint8_t clusterCount[4];
    uint8_t rootDirectoryCluster[4];
    uint8_t volumeSerialNumber[4];
    uint8_t fileSystemRevision[2];
    uint8_t volumeFlags[2];
    uint8_t bytesPerSectorShift;
    uint8_t sectorsPerClusterShift;
    uint8_t numberOfFats;
    uint8_t driveSelect;
    uint8_t percentInUse;
    uint8_t reserved[7];
} BpbExFat_t;
//------------------------------------------------------------------------------
typedef struct ExFatBootSector
{
    uint8_t jmpInstruction[3];
    char oemName[8];
    BpbExFat_t bpb;
    uint8_t bootCode[390];
    uint8_t signature[2];
} ExFatPbs_t;
//------------------------------------------------------------------------------
const uint32_t EXFAT_EOC = 0XFFFFFFFF;

const uint8_t EXFAT_TYPE_BITMAP = 0X81;
typedef struct
{
    uint8_t type;
    uint8_t flags;
    uint8_t reserved[18];
    uint8_t firstCluster[4];
    uint8_t size[8];
} DirBitmap_t;
//------------------------------------------------------------------------------
const uint8_t EXFAT_TYPE_UPCASE = 0X82;
typedef struct
{
    uint8_t type;
    uint8_t reserved1[3];
    uint8_t checksum[4];
    uint8_t reserved2[12];
    uint8_t firstCluster[4];
    uint8_t size[8];
} DirUpcase_t;
//------------------------------------------------------------------------------
const uint8_t EXFAT_TYPE_LABEL = 0X83;
typedef struct
{
    uint8_t type;
    uint8_t labelLength;
    uint8_t unicode[22];
    uint8_t reserved[8];
} DirLabel_t;
//------------------------------------------------------------------------------
// Last entry in directory.
const uint8_t EXFAT_TYPE_END_DIR = 0X00;
// Entry is used if bit is set.
const uint8_t EXFAT_TYPE_USED = 0X80;
const uint8_t EXFAT_TYPE_FILE = 0X85;
// File attribute reserved since used for FAT volume label.
const uint8_t EXFAT_ATTRIB_RESERVED = 0x08;

typedef struct
{
    uint8_t type;
    uint8_t setCount;
    uint8_t setChecksum[2];
    uint8_t attributes[2];
    uint8_t reserved1[2];
    uint8_t createTime[2];
    uint8_t createDate[2];
    uint8_t modifyTime[2];
    uint8_t modifyDate[2];
    uint8_t accessTime[2];
    uint8_t accessDate[2];
    uint8_t createTimeMs;
    uint8_t modifyTimeMs;
    uint8_t createTimezone;
    uint8_t modifyTimezone;
    uint8_t accessTimezone;
    uint8_t reserved2[7];
} DirFile_t;

const uint8_t EXFAT_TYPE_STREAM = 0XC0;
const uint8_t EXFAT_FLAG_ALWAYS1 = 0x01;
const uint8_t EXFAT_FLAG_CONTIGUOUS = 0x02;
typedef struct
{
    uint8_t type;
    uint8_t flags;
    uint8_t reserved1;
    uint8_t nameLength;
    uint8_t nameHash[2];
    uint8_t reserved2[2];
    uint8_t validLength[8];
    uint8_t reserved3[4];
    uint8_t firstCluster[4];
    uint8_t dataLength[8];
} DirStream_t;

const uint8_t EXFAT_TYPE_NAME = 0XC1;
const uint8_t EXFAT_MAX_NAME_LENGTH = 255;
typedef struct
{
    uint8_t type;
    uint8_t mustBeZero;
    uint8_t unicode[30];
} DirName_t;
