/*
 * Copyright (c) Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once
#include <vapours.hpp>
#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp>

namespace ams::fssystem {

    /* ACCURATE_TO_VERSION: 14.3.0.0 */

    struct Hash {
        static constexpr size_t Size = IHash256Generator::HashSize;
        u8 value[Size];
    };
    static_assert(sizeof(Hash) == Hash::Size);
    static_assert(util::is_pod<Hash>::value);

    using NcaDigest = Hash;

    struct NcaHeader {
        enum class ContentType : u8 {
            Program    = 0,
            Meta       = 1,
            Control    = 2,
            Manual     = 3,
            Data       = 4,
            PublicData = 5,

            Start = Program,
            End   = PublicData,
        };

        enum class DistributionType : u8 {
            Download = 0,
            GameCard = 1,

            Start = Download,
            End   = GameCard,
        };

        enum class EncryptionType : u8 {
            Auto = 0,
            None = 1,
        };

        enum DecryptionKey {
            DecryptionKey_AesXts   = 0,
            DecryptionKey_AesXts1  = DecryptionKey_AesXts,
            DecryptionKey_AesXts2  = 1,
            DecryptionKey_AesCtr   = 2,
            DecryptionKey_AesCtrEx = 3,
            DecryptionKey_AesCtrHw = 4,
            DecryptionKey_Count,
        };

        struct FsInfo {
            u32 start_sector;
            u32 end_sector;
            u32 hash_sectors;
            u32 reserved;
        };
        static_assert(sizeof(FsInfo) == 0x10);
        static_assert(util::is_pod<FsInfo>::value);

        static constexpr u32 Magic0 = util::FourCC<'N','C','A','0'>::Code;
        static constexpr u32 Magic1 = util::FourCC<'N','C','A','1'>::Code;
        static constexpr u32 Magic2 = util::FourCC<'N','C','A','2'>::Code;
        static constexpr u32 Magic3 = util::FourCC<'N','C','A','3'>::Code;

        static constexpr u32 Magic              = Magic3;

        static constexpr size_t Size                             = 1_KB;
        static constexpr s32    FsCountMax                       = 4;
        static constexpr size_t HeaderSignCount                  = 2;
        static constexpr size_t HeaderSignSize                   = 0x100;
        static constexpr size_t EncryptedKeyAreaSize             = 0x100;
        static constexpr size_t SectorSize                       = 0x200;
        static constexpr size_t SectorShift                      = 9;
        static constexpr size_t RightsIdSize                     = 0x10;
        static constexpr size_t XtsBlockSize                     = 0x200;
        static constexpr size_t CtrBlockSize                     = 0x10;

        static_assert(SectorSize == (1 << SectorShift));

        /* Data members. */
        u8 header_sign_1[HeaderSignSize];
        u8 header_sign_2[HeaderSignSize];
        u32 magic;
        DistributionType distribution_type;
        ContentType content_type;
        u8 key_generation;
        u8 key_index;
        u64 content_size;
        u64 program_id;
        u32 content_index;
        u32 sdk_addon_version;
        u8 key_generation_2;
        u8 header1_signature_key_generation;
        u8 reserved_222[2];
        u32 reserved_224[3];
        u8 rights_id[RightsIdSize];
        FsInfo fs_info[FsCountMax];
        Hash fs_header_hash[FsCountMax];
        u8 encrypted_key_area[EncryptedKeyAreaSize];

        static constexpr u64 SectorToByte(u32 sector) {
            return static_cast<u64>(sector) << SectorShift;
        }

        static constexpr u32 ByteToSector(u64 byte) {
            return static_cast<u32>(byte >> SectorShift);
        }

        u8 GetProperKeyGeneration() const;
    };
    static_assert(sizeof(NcaHeader) == NcaHeader::Size);
    static_assert(util::is_pod<NcaHeader>::value);

    struct NcaBucketInfo {
        static constexpr size_t HeaderSize = 0x10;
        fs::Int64 offset;
        fs::Int64 size;
        u8 header[HeaderSize];
    };
    static_assert(util::is_pod<NcaBucketInfo>::value);

    struct NcaPatchInfo {
        static constexpr size_t Size   = 0x40;
        static constexpr size_t Offset = 0x100;

        fs::Int64 indirect_offset;
        fs::Int64 indirect_size;
        u8  indirect_header[NcaBucketInfo::HeaderSize];
        fs::Int64 aes_ctr_ex_offset;
        fs::Int64 aes_ctr_ex_size;
        u8  aes_ctr_ex_header[NcaBucketInfo::HeaderSize];

        bool HasIndirectTable() const;
        bool HasAesCtrExTable() const;
    };
    static_assert(util::is_pod<NcaPatchInfo>::value);

    union NcaAesCtrUpperIv {
        u64 value;
        struct {
            u32 generation;
            u32 secure_value;
        } part;
    };
    static_assert(util::is_pod<NcaAesCtrUpperIv>::value);

    struct NcaSparseInfo {
        NcaBucketInfo bucket;
        fs::Int64 physical_offset;
        u16 generation;
        u8  reserved[6];

        s64 GetPhysicalSize() const {
            return this->bucket.offset + this->bucket.size;
        }

        u32 GetGeneration() const {
            return static_cast<u32>(this->generation) << 16;
        }

        const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const {
            NcaAesCtrUpperIv sparse_upper_iv = upper_iv;
            sparse_upper_iv.part.generation = this->GetGeneration();
            return sparse_upper_iv;
        }
    };
    static_assert(util::is_pod<NcaSparseInfo>::value);

    struct NcaCompressionInfo {
        NcaBucketInfo bucket;
        u8 reserved[8];
    };
    static_assert(util::is_pod<NcaCompressionInfo>::value);

    struct NcaMetaDataHashDataInfo {
        fs::Int64 offset;
        fs::Int64 size;
        Hash hash;
    };
    static_assert(util::is_pod<NcaMetaDataHashDataInfo>::value);

    struct NcaFsHeader {
        static constexpr size_t Size           = 0x200;
        static constexpr size_t HashDataOffset = 0x8;

        struct Region {
            fs::Int64 offset;
            fs::Int64 size;
        };
        static_assert(util::is_pod<Region>::value);

        enum class FsType : u8 {
            RomFs       = 0,
            PartitionFs = 1,
        };

        enum class EncryptionType : u8 {
            Auto                  = 0,
            None                  = 1,
            AesXts                = 2,
            AesCtr                = 3,
            AesCtrEx              = 4,
            AesCtrSkipLayerHash   = 5,
            AesCtrExSkipLayerHash = 6,
        };

        enum class HashType : u8 {
            Auto                          = 0,
            None                          = 1,
            HierarchicalSha256Hash        = 2,
            HierarchicalIntegrityHash     = 3,
            AutoSha3                      = 4,
            HierarchicalSha3256Hash       = 5,
            HierarchicalIntegritySha3Hash = 6,
        };

        enum class MetaDataHashType : u8 {
            None                  = 0,
            HierarchicalIntegrity = 1,
        };

        union HashData {
            struct HierarchicalSha256Data {
                static constexpr size_t HashLayerCountMax = 5;
                static const size_t MasterHashOffset;

                Hash fs_data_master_hash;
                s32 hash_block_size;
                s32 hash_layer_count;
                Region hash_layer_region[HashLayerCountMax];
            } hierarchical_sha256_data;
            static_assert(util::is_pod<HierarchicalSha256Data>::value);

            struct IntegrityMetaInfo {
                static const size_t MasterHashOffset;

                u32 magic;
                u32 version;
                u32 master_hash_size;

                struct LevelHashInfo {
                    u32 max_layers;

                    struct HierarchicalIntegrityVerificationLevelInformation {
                        static constexpr size_t IntegrityMaxLayerCount = 7;
                        fs::Int64 offset;
                        fs::Int64 size;
                        s32 block_order;
                        u8  reserved[4];
                    } info[HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1];

                    struct SignatureSalt {
                        static constexpr size_t Size = 0x20;
                        u8 value[Size];
                    } seed;
                } level_hash_info;

                Hash master_hash;
            } integrity_meta_info;
            static_assert(util::is_pod<IntegrityMetaInfo>::value);

            u8 padding[NcaPatchInfo::Offset - HashDataOffset];
        };

        u16 version;
        FsType fs_type;
        HashType hash_type;
        EncryptionType encryption_type;
        MetaDataHashType meta_data_hash_type;
        u8 reserved[2];
        HashData hash_data;
        NcaPatchInfo patch_info;
        NcaAesCtrUpperIv aes_ctr_upper_iv;
        NcaSparseInfo sparse_info;
        NcaCompressionInfo compression_info;
        NcaMetaDataHashDataInfo meta_data_hash_data_info;
        u8 pad[0x30];

        bool IsSkipLayerHashEncryption() const {
            return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
        }

        Result GetHashTargetOffset(s64 *out) const {
            switch (this->hash_type) {
                case HashType::HierarchicalIntegrityHash:
                case HashType::HierarchicalIntegritySha3Hash:
                    *out = this->hash_data.integrity_meta_info.level_hash_info.info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2].offset;
                    R_SUCCEED();
                case HashType::HierarchicalSha256Hash:
                case HashType::HierarchicalSha3256Hash:
                    *out = this->hash_data.hierarchical_sha256_data.hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - 1].offset;
                    R_SUCCEED();
                default:
                    R_THROW(fs::ResultInvalidNcaFsHeader());
            }
        }
    };
    static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
    static_assert(util::is_pod<NcaFsHeader>::value);
    static_assert(AMS_OFFSETOF(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);

    inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
    inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset      = AMS_OFFSETOF(NcaFsHeader, hash_data.integrity_meta_info.master_hash);

    struct NcaMetaDataHashData {
        s64 layer_info_offset;
        NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
    };
    static_assert(sizeof(NcaMetaDataHashData) == sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
    static_assert(util::is_pod<NcaMetaDataHashData>::value);

}
