// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename       : MBR.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Partitioning scheme plugins.
//
// --[ Description ] ----------------------------------------------------------
//
//     Manages Intel/Microsoft MBR (aka Master Boot Record).
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
using Aaru.Logging;
using Marshal = Aaru.Helpers.Marshal;

namespace Aaru.Partitions;

// TODO: Support AAP extensions
/// <inheritdoc />
/// <summary>Implements decoding of Intel/Microsoft Master Boot Record and extensions</summary>
public sealed class MBR : IPartition
{
    const ulong GPT_MAGIC = 0x5452415020494645;

    const ushort MBR_MAGIC   = 0xAA55;
    const ushort NEC_MAGIC   = 0xA55A;
    const ushort DM_MAGIC    = 0x55AA;
    const string MODULE_NAME = "Master Boot Record (MBR) plugin";

    [SuppressMessage("ReSharper", "StringLiteralTypo")]
    static readonly string[] _mbrTypes =
    [
        // 0x00
        Localization.Empty, Localization.FAT12, Localization.XENIX_root, Localization.XENIX_usr,

        // 0x04
        Localization.FAT16_32_MiB, Localization.Extended, Localization.FAT16, Localization.IFS_HPFS_NTFS,

        // 0x08
        Localization.AIX_boot_OS2_Commodore_DOS, Localization.AIX_data_Coherent_QNX,
        Localization.Coherent_swap_OPUS_OS_2_Boot_Manager, Localization.FAT32,

        // 0x0C
        Localization.FAT32_LBA, Localization.Unknown_partition_type, Localization.FAT16_LBA, Localization.Extended_LBA,

        // 0x10
        Localization.OPUS, Localization.Hidden_FAT12, Localization.Compaq_diagnostics_recovery_partition,
        Localization.Unknown_partition_type,

        // 0x14
        Localization.Hidden_FAT16_32_MiB_AST_DOS, Localization.Unknown_partition_type, Localization.Hidden_FAT16,
        Localization.Hidden_IFS_HPFS_NTFS,

        // 0x18
        Localization.AST_Windows_swap, Localization.Willowtech_Photon_coS, Localization.Unknown_partition_type,
        Localization.Hidden_FAT32,

        // 0x1C
        Localization.Hidden_FAT32_LBA, Localization.Unknown_partition_type, Localization.Hidden_FAT16_LBA,
        Localization.Unknown_partition_type,

        // 0x20
        Localization.Willowsoft_Overture_File_System, Localization.Oxygen_FSo2, Localization.Oxygen_Extended,
        Localization.SpeedStor_reserved,

        // 0x24
        Localization.NEC_DOS, Localization.Unknown_partition_type, Localization.SpeedStor_reserved,
        Localization.Hidden_NTFS,

        // 0x28
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x2C
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x30
        Localization.Unknown_partition_type, Localization.SpeedStor_reserved, Localization.Unknown_partition_type,
        Localization.SpeedStor_reserved,

        // 0x34
        Localization.SpeedStor_reserved, Localization.Unknown_partition_type, Localization.SpeedStor_reserved,
        Localization.Unknown_partition_type,

        // 0x38
        Localization.Theos, Localization.Plan_9, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x3C
        Localization.Partition_Magic, Localization.Hidden_NetWare, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x40
        Localization.VENIX_80286, Localization.PReP_Boot, Localization.Secure_File_System, Localization.PTS_DOS,

        // 0x44
        Localization.Unknown_partition_type, Localization.Priam_EUMEL_Elan, Localization.EUMEL_Elan,
        Localization.EUMEL_Elan,

        // 0x48
        Localization.EUMEL_Elan, Localization.Unknown_partition_type,
        Localization.ALFS_THIN_lightweight_filesystem_for_DOS, Localization.Unknown_partition_type,

        // 0x4C
        Localization.Unknown_partition_type, Localization.QNX_4, Localization.QNX_4, Localization.QNX_4_Oberon,

        // 0x50
        Localization.Ontrack_DM_RO_FAT, Localization.Ontrack_DM_RW_FAT, Localization.CPM_Microport_UNIX,
        Localization.Ontrack_DM_6,

        // 0x54
        Localization.Ontrack_DM_6, Localization.EZ_Drive, Localization.Golden_Bow_VFeature,
        Localization.Unknown_partition_type,

        // 0x58
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x5C
        Localization.Priam_EDISK, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x60
        Localization.Unknown_partition_type, Localization.SpeedStor, Localization.Unknown_partition_type,
        Localization.GNU_Hurd_System_V_386ix,

        // 0x64
        Localization.NetWare_286, Localization.NetWare, Localization.NetWare_386, Localization.NetWare,

        // 0x68
        Localization.NetWare, Localization.NetWare_NSS, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x6C
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x70
        Localization.DiskSecure_Multi_Boot, Localization.Unknown_partition_type, Localization.UNIX_7th_Edition,
        Localization.Unknown_partition_type,

        // 0x74
        Localization.Unknown_partition_type, Localization.IBM_PC_IX, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x78
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x7C
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x80
        Localization.Old_MINIX, Localization.MINIX_Old_Linux, Localization.Linux_swap_Solaris, Localization.Linux,

        // 0x84
        Localization.Hidden_by_OS2_APM_hibernation, Localization.Linux_extended, Localization.NT_Stripe_Set,
        Localization.NT_Stripe_Set,

        // 0x88
        Localization.Linux_Plaintext, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x8C
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Linux_LVM,
        Localization.Unknown_partition_type,

        // 0x90
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Amoeba_Hidden_Linux,

        // 0x94
        Localization.Amoeba_bad_blocks, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x98
        Localization.Unknown_partition_type, Localization.Mylex_EISA_SCSI, Localization.Unknown_partition_type,
        Localization.Unknown_partition_type,

        // 0x9C
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.BSD_OS,

        // 0xA0
        Localization.Hibernation, Localization.HP_Volume_Expansion, Localization.Unknown_partition_type,
        Localization.HP_Volume_Expansion,

        // 0xA4
        Localization.HP_Volume_Expansion, Localization.FreeBSD, Localization.OpenBSD, Localization.NeXTStep,

        // 0xA8
        Localization.Apple_UFS, Localization.NetBSD, Localization.Olivetti_DOS_FAT12, Localization.Apple_Boot,

        // 0xAC
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.Apple_HFS,

        // 0xB0
        Localization.BootStar, Localization.HP_Volume_Expansion, Localization.Unknown_partition_type,
        Localization.HP_Volume_Expansion,

        // 0xB4
        Localization.HP_Volume_Expansion, Localization.Unknown_partition_type, Localization.HP_Volume_Expansion,
        Localization.BSDi,

        // 0xB8
        "BSDi swap", Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.PTS_BootWizard,

        // 0xBC
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Solaris_boot,
        Localization.Solaris,

        // 0xC0
        Localization.Novell_DOS_DR_DOS_secured, Localization.DR_DOS_secured_FAT12, Localization.DR_DOS_reserved,
        Localization.DR_DOS_reserved,

        // 0xC4
        Localization.DR_DOS_secured_FAT16_32_MiB, Localization.Unknown_partition_type,
        Localization.DR_DOS_secured_FAT16, Localization.Syrinx,

        // 0xC8
        Localization.DR_DOS_reserved, Localization.DR_DOS_reserved, Localization.DR_DOS_reserved,
        Localization.DR_DOS_secured_FAT32,

        // 0xCC
        Localization.DR_DOS_secured_FAT32_LBA, Localization.DR_DOS_reserved, Localization.DR_DOS_secured_FAT16_LBA,
        Localization.DR_DOS_secured_extended_LBA,

        // 0xD0
        Localization.Multiuser_DOS_secured_FAT12, Localization.Multiuser_DOS_secured_FAT12,
        Localization.Unknown_partition_type, Localization.Unknown_partition_type,

        // 0xD4
        Localization.Multiuser_DOS_secured_FAT16_32_MiB, Localization.Multiuser_DOS_secured_extended,
        Localization.Multiuser_DOS_secured_FAT16, Localization.Unknown_partition_type,

        // 0xD8
        Localization.CPM, Localization.Unknown_partition_type, Localization.Filesystem_less_data,
        Localization.CPM_CCPM_CTOS,

        // 0xDC
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Dell_partition,
        Localization.BootIt_EMBRM,

        // 0xE0
        Localization.Unknown_partition_type, Localization.SpeedStor, Localization.DOS_read_only, Localization.SpeedStor,

        // 0xE4
        Localization.SpeedStor, Localization.Tandy_DOS, Localization.SpeedStor, Localization.Unknown_partition_type,

        // 0xE8
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.BeOS,

        // 0xEC
        Localization.Unknown_partition_type, Localization.Sprytx, Localization.Guid_Partition_Table,
        Localization.EFI_system_partition,

        // 0xF0
        Localization.Linux_boot, Localization.SpeedStor, Localization.DOS_3_3_secondary_Unisys_DOS,
        Localization.SpeedStor,

        // 0xF4
        Localization.SpeedStor, Localization.Prologue, Localization.SpeedStor, Localization.Unknown_partition_type,

        // 0xF8
        Localization.Unknown_partition_type, Localization.Unknown_partition_type, Localization.Unknown_partition_type,
        Localization.VMware_VMFS,

        // 0xFC
        Localization.VMWare_VMKCORE, Localization.Linux_RAID_FreeDOS, Localization.SpeedStor_LANStep_PS2_IML,
        Localization.Xenix_bad_block
    ];

    static bool GetMinix(IMediaImage imagePlugin, ulong start, ulong divider, ulong sectorOffset, uint sectorSize,
                         out List<Partition> partitions)
    {
        partitions = [];

        ErrorNumber errno = imagePlugin.ReadSector(start, false, out byte[] sector, out _);

        if(errno != ErrorNumber.NoError) return false;

        ExtendedBootRecord mnx = Marshal.ByteArrayToStructureLittleEndian<ExtendedBootRecord>(sector);

        AaruLogging.Debug(MODULE_NAME, "mnx.magic == MBR_Magic = {0}", mnx.magic == MBR_MAGIC);

        if(mnx.magic != MBR_MAGIC) return false;

        var anyMnx = false;

        foreach(PartitionEntry mnxEntry in mnx.entries)
        {
            var   mnxValid      = true;
            var   startSector   = (byte)(mnxEntry.start_sector & 0x3F);
            var   startCylinder = (ushort)((mnxEntry.start_sector & 0xC0) << 2 | mnxEntry.start_cylinder);
            var   endSector     = (byte)(mnxEntry.end_sector & 0x3F);
            var   endCylinder   = (ushort)((mnxEntry.end_sector & 0xC0) << 2 | mnxEntry.end_cylinder);
            ulong mnxStart      = mnxEntry.lba_start;
            ulong mnxSectors    = mnxEntry.lba_sectors;

            AaruLogging.Debug(MODULE_NAME, "mnx_entry.status {0}",         mnxEntry.status);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.type {0}",           mnxEntry.type);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.lba_start {0}",      mnxEntry.lba_start);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.lba_sectors {0}",    mnxEntry.lba_sectors);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.start_cylinder {0}", startCylinder);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.start_head {0}",     mnxEntry.start_head);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.start_sector {0}",   startSector);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.end_cylinder {0}",   endCylinder);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.end_head {0}",       mnxEntry.end_head);
            AaruLogging.Debug(MODULE_NAME, "mnx_entry.end_sector {0}",     endSector);

            mnxValid &= mnxEntry.status is 0x00 or 0x80;
            mnxValid &= mnxEntry.type is 0x81 or 0x80;

            mnxValid &= mnxEntry.lba_start      != 0 ||
                        mnxEntry.lba_sectors    != 0 ||
                        mnxEntry.start_cylinder != 0 ||
                        mnxEntry.start_head     != 0 ||
                        mnxEntry.start_sector   != 0 ||
                        mnxEntry.end_cylinder   != 0 ||
                        mnxEntry.end_head       != 0 ||
                        mnxEntry.end_sector     != 0;

            if(mnxEntry is { lba_start: 0, lba_sectors: 0 } && mnxValid)
            {
                mnxStart = CHS.ToLBA(startCylinder,
                                     mnxEntry.start_head,
                                     startSector,
                                     imagePlugin.Info.Heads,
                                     imagePlugin.Info.SectorsPerTrack);

                mnxSectors = CHS.ToLBA(endCylinder,
                                       mnxEntry.end_head,
                                       mnxEntry.end_sector,
                                       imagePlugin.Info.Heads,
                                       imagePlugin.Info.SectorsPerTrack) -
                             mnxStart;
            }

            // For optical media
            mnxStart   /= divider;
            mnxSectors /= divider;

            AaruLogging.Debug(MODULE_NAME, "mnx_start {0}",   mnxStart);
            AaruLogging.Debug(MODULE_NAME, "mnx_sectors {0}", mnxSectors);

            if(!mnxValid) continue;

            var part = new Partition();

            if(mnxStart > 0 && mnxSectors > 0)
            {
                part.Start  = mnxStart + sectorOffset;
                part.Length = mnxSectors;
                part.Offset = part.Start  * sectorSize;
                part.Size   = part.Length * sectorSize;
            }
            else
                mnxValid = false;

            if(!mnxValid) continue;

            anyMnx           = true;
            part.Type        = "MINIX";
            part.Name        = Localization.MINIX;
            part.Description = mnxEntry.status == 0x80 ? Localization.Partition_is_bootable : "";
            part.Scheme      = "MINIX";

            partitions.Add(part);
        }

        return anyMnx;
    }

    static string DecodeMbrType(byte type) => _mbrTypes[type];

#region Nested type: DiskManagerMasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct DiskManagerMasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 252)]
        public readonly byte[] boot_code;
        /// <summary>
        ///     <see cref="MBR.DM_MAGIC" />
        /// </summary>
        public readonly ushort dm_magic;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: ExtendedBootRecord

    // TODO: IBM Boot Manager
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct ExtendedBootRecord
    {
        /// <summary>Boot code, almost always unused</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 446)]
        public readonly byte[] boot_code;
        /// <summary>Partitions or pointers</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: MasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct MasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 446)]
        public readonly byte[] boot_code;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: ModernMasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct ModernMasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 218)]
        public readonly byte[] boot_code;
        /// <summary>Set to 0</summary>
        public readonly ushort zero;
        /// <summary>Original physical drive</summary>
        public readonly byte drive;
        /// <summary>Disk timestamp, seconds</summary>
        public readonly byte seconds;
        /// <summary>Disk timestamp, minutes</summary>
        public readonly byte minutes;
        /// <summary>Disk timestamp, hours</summary>
        public readonly byte hours;
        /// <summary>Boot code, continuation</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 216)]
        public readonly byte[] boot_code2;
        /// <summary>Disk serial number</summary>
        public readonly uint serial;
        /// <summary>Set to 0</summary>
        public readonly ushort zero2;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: NecMasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct NecMasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 380)]
        public readonly byte[] boot_code;
        /// <summary>
        ///     <see cref="MBR.NEC_MAGIC" />
        /// </summary>
        public readonly ushort nec_magic;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: PartitionEntry

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct PartitionEntry
    {
        /// <summary>Partition status, 0x80 or 0x00, else invalid</summary>
        public readonly byte status;
        /// <summary>Starting head [0,254]</summary>
        public readonly byte start_head;
        /// <summary>Starting sector [1,63]</summary>
        public readonly byte start_sector;
        /// <summary>Starting cylinder [0,1023]</summary>
        public readonly byte start_cylinder;
        /// <summary>Partition type</summary>
        public readonly byte type;
        /// <summary>Ending head [0,254]</summary>
        public readonly byte end_head;
        /// <summary>Ending sector [1,63]</summary>
        public readonly byte end_sector;
        /// <summary>Ending cylinder [0,1023]</summary>
        public readonly byte end_cylinder;
        /// <summary>Starting absolute sector</summary>
        public readonly uint lba_start;
        /// <summary>Total sectors</summary>
        public readonly uint lba_sectors;
    }

#endregion

#region Nested type: SerializedMasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct SerializedMasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 440)]
        public readonly byte[] boot_code;
        /// <summary>Disk serial number</summary>
        public readonly uint serial;
        /// <summary>Set to 0</summary>
        public readonly ushort zero;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region Nested type: TimedMasterBootRecord

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    readonly struct TimedMasterBootRecord
    {
        /// <summary>Boot code</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 218)]
        public readonly byte[] boot_code;
        /// <summary>Set to 0</summary>
        public readonly ushort zero;
        /// <summary>Original physical drive</summary>
        public readonly byte drive;
        /// <summary>Disk timestamp, seconds</summary>
        public readonly byte seconds;
        /// <summary>Disk timestamp, minutes</summary>
        public readonly byte minutes;
        /// <summary>Disk timestamp, hours</summary>
        public readonly byte hours;
        /// <summary>Boot code, continuation</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 222)]
        public readonly byte[] boot_code2;
        /// <summary>Partitions</summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public readonly PartitionEntry[] entries;
        /// <summary>
        ///     <see cref="MBR.MBR_MAGIC" />
        /// </summary>
        public readonly ushort magic;
    }

#endregion

#region IPartition Members

    /// <inheritdoc />
    public string Name => Localization.MBR_Name;

    /// <inheritdoc />
    public Guid Id => new("5E8A34E8-4F1A-59E6-4BF7-7EA647063A76");

    /// <inheritdoc />
    public string Author => Authors.NATALIA_PORTILLO;

    /// <inheritdoc />
    [SuppressMessage("ReSharper", "UnusedVariable")]
    public bool GetInformation(IMediaImage imagePlugin, out List<Partition> partitions, ulong sectorOffset)
    {
        ulong counter = 0;

        partitions = [];

        if(imagePlugin.Info.SectorSize < 512) return false;

        uint sectorSize = imagePlugin.Info.SectorSize;

        // Divider of sector size in MBR between real sector size
        ulong divider = 1;

        if(imagePlugin.Info.MetadataMediaType == MetadataMediaType.OpticalDisc)
        {
            sectorSize = 512;
            divider    = 4;
        }

        ErrorNumber errno = imagePlugin.ReadSector(sectorOffset, false, out byte[] sector, out _);

        if(errno != ErrorNumber.NoError) return false;

        MasterBootRecord      mbr     = Marshal.ByteArrayToStructureLittleEndian<MasterBootRecord>(sector);
        TimedMasterBootRecord mbrTime = Marshal.ByteArrayToStructureLittleEndian<TimedMasterBootRecord>(sector);

        SerializedMasterBootRecord mbrSerial =
            Marshal.ByteArrayToStructureLittleEndian<SerializedMasterBootRecord>(sector);

        ModernMasterBootRecord mbrModern = Marshal.ByteArrayToStructureLittleEndian<ModernMasterBootRecord>(sector);
        NecMasterBootRecord    mbrNec    = Marshal.ByteArrayToStructureLittleEndian<NecMasterBootRecord>(sector);

        DiskManagerMasterBootRecord mbrOntrack =
            Marshal.ByteArrayToStructureLittleEndian<DiskManagerMasterBootRecord>(sector);

        AaruLogging.Debug(MODULE_NAME, "xmlmedia = {0}",     imagePlugin.Info.MetadataMediaType);
        AaruLogging.Debug(MODULE_NAME, "mbr.magic = {0:X4}", mbr.magic);

        if(mbr.magic != MBR_MAGIC) return false; // Not MBR

        errno = imagePlugin.ReadSector(1 + sectorOffset, false, out byte[] hdrBytes, out _);

        if(errno != ErrorNumber.NoError) return false;

        var signature = BitConverter.ToUInt64(hdrBytes, 0);

        AaruLogging.Debug(MODULE_NAME, "gpt.signature = 0x{0:X16}", signature);

        if(signature == GPT_MAGIC) return false;

        if(imagePlugin.Info.MetadataMediaType == MetadataMediaType.OpticalDisc)
        {
            errno = imagePlugin.ReadSector(sectorOffset, false, out hdrBytes, out _);

            if(errno != ErrorNumber.NoError) return false;

            signature = BitConverter.ToUInt64(hdrBytes, 512);
            AaruLogging.Debug(MODULE_NAME, "gpt.signature @ 0x200 = 0x{0:X16}", signature);

            if(signature == GPT_MAGIC) return false;
        }

        PartitionEntry[] entries;

        if(mbrOntrack.dm_magic == DM_MAGIC)
            entries = mbrOntrack.entries;
        else if(mbrNec.nec_magic == NEC_MAGIC)
            entries = mbrNec.entries;
        else
            entries = mbr.entries;

        foreach(PartitionEntry entry in entries)
        {
            var   startSector   = (byte)(entry.start_sector & 0x3F);
            var   startCylinder = (ushort)((entry.start_sector & 0xC0) << 2 | entry.start_cylinder);
            var   endSector     = (byte)(entry.end_sector & 0x3F);
            var   endCylinder   = (ushort)((entry.end_sector & 0xC0) << 2 | entry.end_cylinder);
            ulong lbaStart      = entry.lba_start;
            ulong lbaSectors    = entry.lba_sectors;

            // Let's start the fun...

            var valid    = true;
            var extended = false;
            var minix    = false;

            if(entry.status != 0x00 && entry.status != 0x80) return false; // Maybe a FAT filesystem

            valid &= entry.type != 0x00;

            if(entry.type is 0x05 or 0x0F or 0x15 or 0x1F or 0x85 or 0x91 or 0x9B or 0xC5 or 0xCF or 0xD5)
            {
                valid    = false;
                extended = true; // Extended partition
            }

            minix |= entry.type is 0x81 or 0x80; // MINIX partition

            valid &= entry.lba_start      != 0 ||
                     entry.lba_sectors    != 0 ||
                     entry.start_cylinder != 0 ||
                     entry.start_head     != 0 ||
                     entry.start_sector   != 0 ||
                     entry.end_cylinder   != 0 ||
                     entry.end_head       != 0 ||
                     entry.end_sector     != 0;

            if(entry is { lba_start: 0, lba_sectors: 0 } && valid)
            {
                lbaStart = CHS.ToLBA(startCylinder,
                                     entry.start_head,
                                     startSector,
                                     imagePlugin.Info.Heads,
                                     imagePlugin.Info.SectorsPerTrack);

                lbaSectors = CHS.ToLBA(endCylinder,
                                       entry.end_head,
                                       entry.end_sector,
                                       imagePlugin.Info.Heads,
                                       imagePlugin.Info.SectorsPerTrack) -
                             lbaStart;
            }

            // For optical media
            lbaStart   /= divider;
            lbaSectors /= divider;

            if(minix && lbaStart == sectorOffset) minix = false;

            if(lbaStart > imagePlugin.Info.Sectors)
            {
                valid    = false;
                extended = false;
            }

            // Some buggy implementations do some rounding errors getting a few sectors beyond device size
            if(lbaStart + lbaSectors > imagePlugin.Info.Sectors) lbaSectors = imagePlugin.Info.Sectors - lbaStart;

            AaruLogging.Debug(MODULE_NAME, "entry.status {0}",         entry.status);
            AaruLogging.Debug(MODULE_NAME, "entry.type {0}",           entry.type);
            AaruLogging.Debug(MODULE_NAME, "entry.lba_start {0}",      entry.lba_start);
            AaruLogging.Debug(MODULE_NAME, "entry.lba_sectors {0}",    entry.lba_sectors);
            AaruLogging.Debug(MODULE_NAME, "entry.start_cylinder {0}", startCylinder);
            AaruLogging.Debug(MODULE_NAME, "entry.start_head {0}",     entry.start_head);
            AaruLogging.Debug(MODULE_NAME, "entry.start_sector {0}",   startSector);
            AaruLogging.Debug(MODULE_NAME, "entry.end_cylinder {0}",   endCylinder);
            AaruLogging.Debug(MODULE_NAME, "entry.end_head {0}",       entry.end_head);
            AaruLogging.Debug(MODULE_NAME, "entry.end_sector {0}",     endSector);

            AaruLogging.Debug(MODULE_NAME, "entry.minix = {0}", minix);

            AaruLogging.Debug(MODULE_NAME, "lba_start {0}",   lbaStart);
            AaruLogging.Debug(MODULE_NAME, "lba_sectors {0}", lbaSectors);

            if(valid && minix) // Let's mix the fun
            {
                if(GetMinix(imagePlugin, lbaStart, divider, sectorOffset, sectorSize, out List<Partition> mnxParts))
                    partitions.AddRange(mnxParts);
                else
                    minix = false;
            }

            if(valid && !minix)
            {
                var part = new Partition();

                if((lbaStart > 0 || imagePlugin.Info.MetadataMediaType == MetadataMediaType.OpticalDisc) &&
                   lbaSectors > 0)
                {
                    part.Start  = lbaStart + sectorOffset;
                    part.Length = lbaSectors;
                    part.Offset = part.Start  * sectorSize;
                    part.Size   = part.Length * sectorSize;
                }
                else
                    valid = false;

                if(valid)
                {
                    part.Type        = $"0x{entry.type:X2}";
                    part.Name        = DecodeMbrType(entry.type);
                    part.Sequence    = counter;
                    part.Description = entry.status == 0x80 ? Localization.Partition_is_bootable : "";
                    part.Scheme      = Name;

                    counter++;

                    partitions.Add(part);
                }
            }

            AaruLogging.Debug(MODULE_NAME, "entry.extended = {0}", extended);

            if(!extended) continue;

            var   processingExtended = true;
            ulong chainStart         = lbaStart;

            while(processingExtended)
            {
                errno = imagePlugin.ReadSector(lbaStart, false, out sector, out _);

                if(errno != ErrorNumber.NoError) break;

                ExtendedBootRecord ebr = Marshal.ByteArrayToStructureLittleEndian<ExtendedBootRecord>(sector);

                AaruLogging.Debug(MODULE_NAME, "ebr.magic == MBR_Magic = {0}", ebr.magic == MBR_MAGIC);

                if(ebr.magic != MBR_MAGIC) break;

                ulong nextStart = 0;

                foreach(PartitionEntry ebrEntry in ebr.entries)
                {
                    var extValid = true;
                    startSector   = (byte)(ebrEntry.start_sector & 0x3F);
                    startCylinder = (ushort)((ebrEntry.start_sector & 0xC0) << 2 | ebrEntry.start_cylinder);
                    endSector     = (byte)(ebrEntry.end_sector & 0x3F);
                    endCylinder   = (ushort)((ebrEntry.end_sector & 0xC0) << 2 | ebrEntry.end_cylinder);
                    ulong extStart   = ebrEntry.lba_start;
                    ulong extSectors = ebrEntry.lba_sectors;
                    var   extMinix   = false;

                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.status {0}",         ebrEntry.status);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.type {0}",           ebrEntry.type);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.lba_start {0}",      ebrEntry.lba_start);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.lba_sectors {0}",    ebrEntry.lba_sectors);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.start_cylinder {0}", startCylinder);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.start_head {0}",     ebrEntry.start_head);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.start_sector {0}",   startSector);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.end_cylinder {0}",   endCylinder);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.end_head {0}",       ebrEntry.end_head);
                    AaruLogging.Debug(MODULE_NAME, "ebr_entry.end_sector {0}",     endSector);

                    // Let's start the fun...
                    extValid &= ebrEntry.status is 0x00 or 0x80;
                    extValid &= ebrEntry.type != 0x00;

                    extValid &= ebrEntry.lba_start      != 0 ||
                                ebrEntry.lba_sectors    != 0 ||
                                ebrEntry.start_cylinder != 0 ||
                                ebrEntry.start_head     != 0 ||
                                ebrEntry.start_sector   != 0 ||
                                ebrEntry.end_cylinder   != 0 ||
                                ebrEntry.end_head       != 0 ||
                                ebrEntry.end_sector     != 0;

                    if(ebrEntry is { lba_start: 0, lba_sectors: 0 } && extValid)
                    {
                        extStart = CHS.ToLBA(startCylinder,
                                             ebrEntry.start_head,
                                             startSector,
                                             imagePlugin.Info.Heads,
                                             imagePlugin.Info.SectorsPerTrack);

                        extSectors = CHS.ToLBA(endCylinder,
                                               ebrEntry.end_head,
                                               ebrEntry.end_sector,
                                               imagePlugin.Info.Heads,
                                               imagePlugin.Info.SectorsPerTrack) -
                                     extStart;
                    }

                    extMinix |= ebrEntry.type is 0x81 or 0x80;

                    // For optical media
                    extStart   /= divider;
                    extSectors /= divider;

                    AaruLogging.Debug(MODULE_NAME, "ext_start {0}",   extStart);
                    AaruLogging.Debug(MODULE_NAME, "ext_sectors {0}", extSectors);

                    if(ebrEntry.type is 0x05 or 0x0F or 0x15 or 0x1F or 0x85 or 0x91 or 0x9B or 0xC5 or 0xCF or 0xD5)
                    {
                        extValid  = false;
                        nextStart = chainStart + extStart;
                    }

                    extStart += lbaStart;
                    extValid &= extStart <= imagePlugin.Info.Sectors;

                    // Some buggy implementations do some rounding errors getting a few sectors beyond device size
                    if(extStart + extSectors > imagePlugin.Info.Sectors)
                        extSectors = imagePlugin.Info.Sectors - extStart;

                    if(extValid && extMinix) // Let's mix the fun
                    {
                        if(GetMinix(imagePlugin,
                                    lbaStart,
                                    divider,
                                    sectorOffset,
                                    sectorSize,
                                    out List<Partition> mnxParts))
                            partitions.AddRange(mnxParts);
                        else
                            extMinix = false;
                    }

                    if(!extValid || extMinix) continue;

                    var part = new Partition();

                    if(extStart > 0 && extSectors > 0)
                    {
                        part.Start  = extStart + sectorOffset;
                        part.Length = extSectors;
                        part.Offset = part.Start  * sectorSize;
                        part.Size   = part.Length * sectorSize;
                    }
                    else
                        extValid = false;

                    if(!extValid) continue;

                    part.Type        = $"0x{ebrEntry.type:X2}";
                    part.Name        = DecodeMbrType(ebrEntry.type);
                    part.Sequence    = counter;
                    part.Description = ebrEntry.status == 0x80 ? Localization.Partition_is_bootable : "";
                    part.Scheme      = Name;
                    counter++;

                    partitions.Add(part);
                }

                AaruLogging.Debug(MODULE_NAME, "next_start {0}", nextStart);
                processingExtended &= nextStart != 0;
                processingExtended &= nextStart <= imagePlugin.Info.Sectors;
                lbaStart           =  nextStart;
            }
        }

        // An empty MBR may exist, NeXT creates one and then hardcodes its disklabel
        return partitions.Count != 0;
    }

#endregion
}