#nullable enable
using System;
using System.Runtime.InteropServices;
using IPA.Logging;

namespace IPA.Utilities
{
    /// <summary>
    /// Provides utilities for managing various critical sections.
    /// </summary>
    public static class CriticalSection
    {

        internal static void Configure()
        {
            Logger.Default.Debug("Configuring exit handlers");

            ResetExitHandlers();
        }

        private static void Reset(object? sender, EventArgs? e)
        {
            _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
            WinHttp.SetPeekMessageHook(null);
        }

        #region Execute section

        private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit;
        internal static void ResetExitHandlers()
        {
            _ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
            _ = Win32.SetConsoleCtrlHandler(registeredHandler, true);
            WinHttp.SetPeekMessageHook(PeekMessageHook);

            AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
            AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
        }

        private static void OnProcessExit(object sender, EventArgs args)
        {
            WinHttp.SetIgnoreUnhandledExceptions(true);
        }

        private static class WinHttp
        {
            public delegate bool PeekMessageHook(
                bool isW,
                uint result,
                [MarshalAs(UnmanagedType.LPStruct)]
                in Win32.MSG message,
                IntPtr hwnd,
                uint filterMin,
                uint filterMax,
                ref Win32.PeekMessageParams removeMsg);

            [DllImport("bsipa-doorstop")]
            public static extern void SetPeekMessageHook(
                [MarshalAs(UnmanagedType.FunctionPtr)]
                PeekMessageHook? hook);

            [DllImport("bsipa-doorstop")]
            public static extern void SetIgnoreUnhandledExceptions(
                [MarshalAs(UnmanagedType.Bool)] bool ignore);
        }

        private static Win32.ConsoleCtrlDelegate? _handler = null;
        private static volatile bool isInExecuteSection = false;

        // returns true to continue looping and calling PeekMessage
        private static bool PeekMessageHook(
                bool isW,
                uint result,
                [MarshalAs(UnmanagedType.LPStruct)]
                in Win32.MSG message,
                IntPtr hwnd,
                uint filterMin,
                uint filterMax,
                ref Win32.PeekMessageParams removeMsg)
        {
            if (isInExecuteSection)
            {
                if (result == 0) return false;

                switch (message.message)
                {
                    case Win32.WM.CLOSE:
                        if (removeMsg != Win32.PeekMessageParams.PM_REMOVE)
                        {
                            removeMsg = Win32.PeekMessageParams.PM_REMOVE;
                            exitRecieved = true;
                            return true;
                        }
                        else
                        {
                            removeMsg = Win32.PeekMessageParams.PM_NOREMOVE;
                            return true;
                        }

                    default:
                        return false;
                }
            }

            return false;
        }

        private static bool HandleExit(Win32.CtrlTypes type)
        {
            if (_handler != null)
                return _handler(type);

            return false;
        } 

        private static volatile bool exitRecieved = false;

        /// <summary>
        /// A struct that allows <c>using</c> blocks to manage an execute section.
        /// </summary>
        public struct AutoExecuteSection : IDisposable
        {
            private readonly bool constructed;
            internal AutoExecuteSection(bool val) 
            {
                constructed = val && !isInExecuteSection;
                if (constructed)
                    EnterExecuteSection();
            }

            void IDisposable.Dispose()
            {
                if (constructed)
                    ExitExecuteSection();
            }
        }

        /// <summary>
        /// Creates an <see cref="AutoExecuteSection"/> for automated management of an execute section.
        /// </summary>
        /// <returns>the new <see cref="AutoExecuteSection"/> that manages the section</returns>
        public static AutoExecuteSection ExecuteSection() => new AutoExecuteSection(true);

        /// <summary>
        /// Enters a critical execution section. Does not nest.
        /// </summary>
        /// <note>
        /// During a critical execution section, the program must execute until the end of the section before
        /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
        /// will terminate at the end of the section.
        /// </note>
        public static void EnterExecuteSection()
        {
            ResetExitHandlers();

            exitRecieved = false;
            _handler = sig => exitRecieved = true;
            isInExecuteSection = true;
        }

        /// <summary>
        /// Exits a critical execution section. Does not nest.
        /// </summary>
        /// <note>
        /// During a critical execution section, the program must execute until the end of the section before
        /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process
        /// will terminate at the end of the section.
        /// </note>
        public static void ExitExecuteSection()
        {
            _handler = null;
            isInExecuteSection = false;

            Reset(null, null);

            if (exitRecieved)
                Environment.Exit(1);
        }

        #endregion
    }
}
