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

using System;
using System.Runtime.InteropServices;

namespace Microsoft.SqlTools.Credentials
{

#if !WINDOWS_ONLY_BUILD

    internal static partial class Interop
    {
        /// <summary>Common Unix errno error codes.</summary>
        internal enum Error
        {
            // These values were defined in src/Native/System.Native/fxerrno.h
            //
            // They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
            // which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
            // prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
            // correlate these to the underlying platform values or obtain the corresponding error message.
            // 

            SUCCESS          = 0,

            E2BIG            = 0x10001,           // Argument list too long.
            EACCES           = 0x10002,           // Permission denied.
            EADDRINUSE       = 0x10003,           // Address in use.
            EADDRNOTAVAIL    = 0x10004,           // Address not available.
            EAFNOSUPPORT     = 0x10005,           // Address family not supported.
            EAGAIN           = 0x10006,           // Resource unavailable, try again (same value as EWOULDBLOCK),
            EALREADY         = 0x10007,           // Connection already in progress.
            EBADF            = 0x10008,           // Bad file descriptor.
            EBADMSG          = 0x10009,           // Bad message.
            EBUSY            = 0x1000A,           // Device or resource busy.
            ECANCELED        = 0x1000B,           // Operation canceled.
            ECHILD           = 0x1000C,           // No child processes.
            ECONNABORTED     = 0x1000D,           // Connection aborted.
            ECONNREFUSED     = 0x1000E,           // Connection refused.
            ECONNRESET       = 0x1000F,           // Connection reset.
            EDEADLK          = 0x10010,           // Resource deadlock would occur.
            EDESTADDRREQ     = 0x10011,           // Destination address required.
            EDOM             = 0x10012,           // Mathematics argument out of domain of function.
            EDQUOT           = 0x10013,           // Reserved.
            EEXIST           = 0x10014,           // File exists.
            EFAULT           = 0x10015,           // Bad address.
            EFBIG            = 0x10016,           // File too large.
            EHOSTUNREACH     = 0x10017,           // Host is unreachable.
            EIDRM            = 0x10018,           // Identifier removed.
            EILSEQ           = 0x10019,           // Illegal byte sequence.
            EINPROGRESS      = 0x1001A,           // Operation in progress.
            EINTR            = 0x1001B,           // Interrupted function.
            EINVAL           = 0x1001C,           // Invalid argument.
            EIO              = 0x1001D,           // I/O error.
            EISCONN          = 0x1001E,           // Socket is connected.
            EISDIR           = 0x1001F,           // Is a directory.
            ELOOP            = 0x10020,           // Too many levels of symbolic links.
            EMFILE           = 0x10021,           // File descriptor value too large.
            EMLINK           = 0x10022,           // Too many links.
            EMSGSIZE         = 0x10023,           // Message too large.
            EMULTIHOP        = 0x10024,           // Reserved.
            ENAMETOOLONG     = 0x10025,           // Filename too long.
            ENETDOWN         = 0x10026,           // Network is down.
            ENETRESET        = 0x10027,           // Connection aborted by network.
            ENETUNREACH      = 0x10028,           // Network unreachable.
            ENFILE           = 0x10029,           // Too many files open in system.
            ENOBUFS          = 0x1002A,           // No buffer space available.
            ENODEV           = 0x1002C,           // No such device.
            ENOENT           = 0x1002D,           // No such file or directory.
            ENOEXEC          = 0x1002E,           // Executable file format error.
            ENOLCK           = 0x1002F,           // No locks available.
            ENOLINK          = 0x10030,           // Reserved.
            ENOMEM           = 0x10031,           // Not enough space.
            ENOMSG           = 0x10032,           // No message of the desired type.
            ENOPROTOOPT      = 0x10033,           // Protocol not available.
            ENOSPC           = 0x10034,           // No space left on device.
            ENOSYS           = 0x10037,           // Function not supported.
            ENOTCONN         = 0x10038,           // The socket is not connected.
            ENOTDIR          = 0x10039,           // Not a directory or a symbolic link to a directory.
            ENOTEMPTY        = 0x1003A,           // Directory not empty.
            ENOTSOCK         = 0x1003C,           // Not a socket.
            ENOTSUP          = 0x1003D,           // Not supported (same value as EOPNOTSUP).
            ENOTTY           = 0x1003E,           // Inappropriate I/O control operation.
            ENXIO            = 0x1003F,           // No such device or address.
            EOVERFLOW        = 0x10040,           // Value too large to be stored in data type.
            EPERM            = 0x10042,           // Operation not permitted.
            EPIPE            = 0x10043,           // Broken pipe.
            EPROTO           = 0x10044,           // Protocol error.
            EPROTONOSUPPORT  = 0x10045,           // Protocol not supported.
            EPROTOTYPE       = 0x10046,           // Protocol wrong type for socket.
            ERANGE           = 0x10047,           // Result too large.
            EROFS            = 0x10048,           // Read-only file system.
            ESPIPE           = 0x10049,           // Invalid seek.
            ESRCH            = 0x1004A,           // No such process.
            ESTALE           = 0x1004B,           // Reserved.
            ETIMEDOUT        = 0x1004D,           // Connection timed out.
            ETXTBSY          = 0x1004E,           // Text file busy.
            EXDEV            = 0x1004F,           // Cross-device link.
            ESOCKTNOSUPPORT  = 0x1005E,           // Socket type not supported.
            EPFNOSUPPORT     = 0x10060,           // Protocol family not supported.
            ESHUTDOWN        = 0x1006C,           // Socket shutdown.
            EHOSTDOWN        = 0x10070,           // Host is down.
            ENODATA          = 0x10071,           // No data available.

            // POSIX permits these to have the same value and we make them always equal so
            // that CoreFX cannot introduce a dependency on distinguishing between them that
            // would not work on all platforms.
            EOPNOTSUPP      = ENOTSUP,            // Operation not supported on socket.
            EWOULDBLOCK     = EAGAIN,             // Operation would block.
        }


        // Represents a platform-agnostic Error and underlying platform-specific errno
        internal struct ErrorInfo
        {
            private Error _error;
            private int _rawErrno;

            internal ErrorInfo(int errno)
            {
                _error = Interop.Sys.ConvertErrorPlatformToPal(errno);
                _rawErrno = errno;
            }

            internal ErrorInfo(Error error)
            {
                _error = error;
                _rawErrno = -1;
            }

            internal Error Error
            {
                get { return _error; }
            }

            internal int RawErrno
            {
                get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
            }

            internal string GetErrorMessage()
            {
                return Interop.Sys.StrError(RawErrno);
            }

            public override string ToString()
            {
                return string.Format(
                    "RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
                    RawErrno, Error, GetErrorMessage());
            }
        }

        internal partial class Sys
        {
            internal static Error GetLastError()
            {
                return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
            }

            internal static ErrorInfo GetLastErrorInfo()
            {
                return new ErrorInfo(Marshal.GetLastWin32Error());
            }

            internal static string StrError(int platformErrno)
            {
                int maxBufferLength = 1024; // should be long enough for most any UNIX error
                IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
                try
                {
                IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);

                if (message == IntPtr.Zero)
                {
                    // This means the buffer was not large enough, but still contains
                    // as much of the error message as possible and is guaranteed to
                    // be null-terminated. We're not currently resizing/retrying because
                    // maxBufferLength is large enough in practice, but we could do
                    // so here in the future if necessary.
                    message = buffer;
                }
                
                string returnMsg = Marshal.PtrToStringAnsi(message);
                return returnMsg;
                }
                finally
                {
                    // Deallocate the buffer we created
                    Marshal.FreeHGlobal(buffer);
                }
            }

            [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
            internal static extern Error ConvertErrorPlatformToPal(int platformErrno);

            [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
            internal static extern int ConvertErrorPalToPlatform(Error error);

            [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
            private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
        }
    }

    // NOTE: extension method can't be nested inside Interop class.
    internal static class InteropErrorExtensions
    {
        // Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
        // vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
        // errors. Errors originated from the system should be obtained
        // via GetLastErrorInfo(), not GetLastError().Info() as that will
        // convert twice, which is not only inefficient but also lossy if
        // we ever encounter a raw errno that no equivalent in the Error
        // enum.
        public static Interop.ErrorInfo Info(this Interop.Error error)
        {
            return new Interop.ErrorInfo(error);
        }
    }

#endif

}