#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Globalization;
using System.Runtime.InteropServices;

namespace log4net.Util
{
  /// <summary>
  /// Represents a Win32 native error code and message.
  /// </summary>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public sealed class NativeError
  {
    /// <summary>
    /// Create an instance of the <see cref="NativeError" /> class with the specified 
    /// error number and message.
    /// </summary>
    /// <param name="number">The number of the native error.</param>
    /// <param name="message">The message of the native error.</param>
    private NativeError(int number, string? message)
    {
      Number = number;
      Message = message;
    }

    /// <summary>
    /// Gets the number of the native error.
    /// </summary>
    /// <value>
    /// The number of the native error.
    /// </value>
    /// <remarks>
    /// <para>
    /// Gets the number of the native error.
    /// </para>
    /// </remarks>
    public int Number { get; }

    /// <summary>
    /// Gets the message of the native error.
    /// </summary>
    public string? Message { get; }

    /// <summary>
    /// Creates a new instance of the <see cref="NativeError" /> class for the last Windows error.
    /// </summary>
    /// <returns>
    /// An instance of the <see cref="NativeError" /> class for the last windows error.
    /// </returns>
    /// <remarks>
    /// <para>
    /// The message for the <see cref="Marshal.GetLastWin32Error"/> error number is lookup up using the 
    /// native Win32 <c>FormatMessage</c> function.
    /// </para>
    /// </remarks>
    [System.Security.SecuritySafeCritical]
    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
    public static NativeError GetLastError()
    {
      int number = Marshal.GetLastWin32Error();
      return new NativeError(number, GetErrorMessage(number));
    }

    /// <summary>
    /// Create a new instance of the <see cref="NativeError" /> class.
    /// </summary>
    /// <param name="number">the error number for the native error</param>
    /// <returns>
    /// An instance of the <see cref="NativeError" /> class for the specified 
    /// error number.
    /// </returns>
    /// <remarks>
    /// <para>
    /// The message for the specified error number is lookup up using the 
    /// native Win32 <c>FormatMessage</c> function.
    /// </para>
    /// </remarks>
    public static NativeError GetError(int number)
    {
      return new NativeError(number, GetErrorMessage(number));
    }

    /// <summary>
    /// Retrieves the message corresponding with a Win32 message identifier.
    /// </summary>
    /// <param name="messageId">Message identifier for the requested message.</param>
    /// <returns>
    /// The message corresponding with the specified message identifier.
    /// </returns>
    /// <remarks>
    /// <para>
    /// The message will be searched for in system message-table resource(s)
    /// using the native <c>FormatMessage</c> function.
    /// </para>
    /// </remarks>
    [System.Security.SecuritySafeCritical]
    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
    public static string? GetErrorMessage(int messageId)
    {
      // Win32 constants
      int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;  // The function should allocates a buffer large enough to hold the formatted message
      int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;    // Insert sequences in the message definition are to be ignored
      int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;    // The function should search the system message-table resource(s) for the requested message

      string? msgBuf = "";        // buffer that will receive the message
      var sourcePtr = new IntPtr();  // Location of the message definition, will be ignored
      var argumentsPtr = new IntPtr();  // Pointer to array of values to insert, not supported as it requires unsafe code

      if (messageId != 0)
      {
        // If the function succeeds, the return value is the number of TCHARs stored in the output buffer, excluding the terminating null character
        int messageSize = FormatMessage(
          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
          ref sourcePtr,
          messageId,
          0,
          ref msgBuf,
          255,
          argumentsPtr);

        if (messageSize > 0)
        {
          // Remove trailing null-terminating characters (\r\n) from the message
          msgBuf = msgBuf.TrimEnd(Newlines);
        }
        else
        {
          // A message could not be located.
          msgBuf = null;
        }
      }
      else
      {
        msgBuf = null;
      }

      return msgBuf;
    }

    /// <summary>
    /// Return error information string
    /// </summary>
    /// <returns>error information string</returns>
    /// <remarks>
    /// <para>
    /// Return error information string
    /// </para>
    /// </remarks>
    public override string ToString()
    {
      return string.Format(CultureInfo.InvariantCulture, "0x{0:x8}", Number) + (Message is not null ? ": " + Message : string.Empty);
    }

    /// <summary>
    /// Formats a message string.
    /// </summary>
    /// <param name="dwFlags">Formatting options, and how to interpret the <paramref name="lpSource" /> parameter.</param>
    /// <param name="lpSource">Location of the message definition.</param>
    /// <param name="dwMessageId">Message identifier for the requested message.</param>
    /// <param name="dwLanguageId">Language identifier for the requested message.</param>
    /// <param name="lpBuffer">If <paramref name="dwFlags" /> includes FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer using the <c>LocalAlloc</c> function, and places the pointer to the buffer at the address specified in <paramref name="lpBuffer" />.</param>
    /// <param name="nSize">If the FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter specifies the maximum number of TCHARs that can be stored in the output buffer. If FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies the minimum number of TCHARs to allocate for an output buffer.</param>
    /// <param name="Arguments">Pointer to an array of values that are used as insert values in the formatted message.</param>
    /// <remarks>
    /// <para>
    /// The function requires a message definition as input. The message definition can come from a 
    /// buffer passed into the function. It can come from a message table resource in an 
    /// already-loaded module. Or the caller can ask the function to search the system's message 
    /// table resource(s) for the message definition. The function finds the message definition 
    /// in a message table resource based on a message identifier and a language identifier. 
    /// The function copies the formatted message text to an output buffer, processing any embedded 
    /// insert sequences if requested.
    /// </para>
    /// <para>
    /// To prevent the usage of unsafe code, this stub does not support inserting values in the formatted message.
    /// </para>
    /// </remarks>
    /// <returns>
    /// <para>
    /// If the function succeeds, the return value is the number of TCHARs stored in the output 
    /// buffer, excluding the terminating null character.
    /// </para>
    /// <para>
    /// If the function fails, the return value is zero. To get extended error information, 
    /// call <see cref="M:Marshal.GetLastWin32Error()" />.
    /// </para>
    /// </returns>
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern int FormatMessage(
      int dwFlags,
      ref IntPtr lpSource,
      int dwMessageId,
      int dwLanguageId,
      ref string lpBuffer,
      int nSize,
      IntPtr Arguments);

    private static readonly char[] Newlines = { '\r', '\n' };
  }
}
