// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if !NETCOREAPP
using Microsoft.Win32.SafeHandles;

namespace Microsoft.Testing.Extensions.Diagnostics;

internal static class MiniDumpWriteDump
{
    public static void CollectDumpUsingMiniDumpWriteDump(int pid, string outputFile, MiniDumpTypeOption type)
    {
        using var process = Process.GetProcessById(pid);

        // Open the file for writing
        using var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
        NativeMethods.MinidumpExceptionInformation exceptionInfo = default;

        NativeMethods.MinidumpType dumpType = type switch
        {
            MiniDumpTypeOption.Full =>
                NativeMethods.MinidumpType.MiniDumpWithFullMemory
                | NativeMethods.MinidumpType.MiniDumpWithDataSegs
                | NativeMethods.MinidumpType.MiniDumpWithHandleData
                | NativeMethods.MinidumpType.MiniDumpWithUnloadedModules
                | NativeMethods.MinidumpType.MiniDumpWithFullMemoryInfo
                | NativeMethods.MinidumpType.MiniDumpWithThreadInfo
                | NativeMethods.MinidumpType.MiniDumpWithTokenInformation,
            MiniDumpTypeOption.Heap =>
                NativeMethods.MinidumpType.MiniDumpWithPrivateReadWriteMemory
                | NativeMethods.MinidumpType.MiniDumpWithDataSegs
                | NativeMethods.MinidumpType.MiniDumpWithHandleData
                | NativeMethods.MinidumpType.MiniDumpWithUnloadedModules
                | NativeMethods.MinidumpType.MiniDumpWithFullMemoryInfo
                | NativeMethods.MinidumpType.MiniDumpWithThreadInfo
                | NativeMethods.MinidumpType.MiniDumpWithTokenInformation,
            MiniDumpTypeOption.Mini => NativeMethods.MinidumpType.MiniDumpWithThreadInfo,
            _ => NativeMethods.MinidumpType.MiniDumpNormal,
        };

        // Retry the write dump on ERROR_PARTIAL_COPY
        for (int i = 0; i < 5; i++)
        {
            // Dump the process!
            if (NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, dumpType, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero))
            {
                break;
            }

            int err = Marshal.GetHRForLastWin32Error();
            if (err != NativeMethods.ErrorPartialCopy)
            {
                Marshal.ThrowExceptionForHR(err);
            }
        }
    }

    private static class NativeMethods
    {
        public const int ErrorPartialCopy = unchecked((int)0x8007012b);

        [DllImport("Dbghelp.dll", SetLastError = true)]
        public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeFileHandle hFile, MinidumpType dumpType, ref MinidumpExceptionInformation exceptionParam, IntPtr userStreamParam, IntPtr callbackParam);

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        public readonly struct MinidumpExceptionInformation
        {
            public readonly uint ThreadId;
            public readonly IntPtr ExceptionPointers;
            public readonly int ClientPointers;
        }

        [Flags]
        public enum MinidumpType : uint
        {
            MiniDumpNormal = 0,
            MiniDumpWithDataSegs = 1 << 0,
            MiniDumpWithFullMemory = 1 << 1,
            MiniDumpWithHandleData = 1 << 2,
            MiniDumpFilterMemory = 1 << 3,
            MiniDumpScanMemory = 1 << 4,
            MiniDumpWithUnloadedModules = 1 << 5,
            MiniDumpWithIndirectlyReferencedMemory = 1 << 6,
            MiniDumpFilterModulePaths = 1 << 7,
            MiniDumpWithProcessThreadData = 1 << 8,
            MiniDumpWithPrivateReadWriteMemory = 1 << 9,
            MiniDumpWithoutOptionalData = 1 << 10,
            MiniDumpWithFullMemoryInfo = 1 << 11,
            MiniDumpWithThreadInfo = 1 << 12,
            MiniDumpWithCodeSegs = 1 << 13,
            MiniDumpWithoutAuxiliaryState = 1 << 14,
            MiniDumpWithFullAuxiliaryState = 1 << 15,
            MiniDumpWithPrivateWriteCopyMemory = 1 << 16,
            MiniDumpIgnoreInaccessibleMemory = 1 << 17,
            MiniDumpWithTokenInformation = 1 << 18,
            MiniDumpWithModuleHeaders = 1 << 19,
            MiniDumpFilterTriage = 1 << 20,
            MiniDumpWithAvxXStateContext = 1 << 21,
            MiniDumpWithIptTrace = 1 << 22,
            MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22),
        }
    }

    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Copy-paste")]
    internal enum MiniDumpTypeOption
    {
        // This is a copy of DumpTypeOption, we need both of those types, because this
        // dumper is included as file in both MiniDumpTool, and BlameDataCollector, and
        // blame references MiniDumpTool, so we get the enum defined twice otherwise.
        Full,
        Heap,
        Mini,
    }
}
#endif
