using System;
using System.Collections.Generic;
using System.Globalization;
#if !NET
using System.IO;
#endif
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Messages;
namespace Renci.SshNet.Common
{
    /// 
    /// Collection of different extension methods.
    /// 
    internal static class Extensions
    {
#pragma warning disable S4136 // Method overloads should be grouped together
        internal static byte[] ToArray(this ServiceName serviceName)
#pragma warning restore S4136 // Method overloads should be grouped together
        {
            switch (serviceName)
            {
                case ServiceName.UserAuthentication:
                    return SshData.Ascii.GetBytes("ssh-userauth");
                case ServiceName.Connection:
                    return SshData.Ascii.GetBytes("ssh-connection");
                default:
                    throw new NotSupportedException(string.Format("Service name '{0}' is not supported.", serviceName));
            }
        }
        internal static ServiceName ToServiceName(this byte[] data)
        {
            var sshServiceName = SshData.Ascii.GetString(data, 0, data.Length);
            switch (sshServiceName)
            {
                case "ssh-userauth":
                    return ServiceName.UserAuthentication;
                case "ssh-connection":
                    return ServiceName.Connection;
                default:
                    throw new NotSupportedException(string.Format("Service name '{0}' is not supported.", sshServiceName));
            }
        }
        internal static BigInteger ToBigInteger(this ReadOnlySpan data)
        {
#if NET
            return new BigInteger(data, isBigEndian: true);
#else
            var reversed = data.ToArray();
            Array.Reverse(reversed);
            return new BigInteger(reversed);
#endif
        }
        internal static BigInteger ToBigInteger(this byte[] data)
        {
#if NET
            return new BigInteger(data, isBigEndian: true);
#else
            var reversed = new byte[data.Length];
            Buffer.BlockCopy(data, 0, reversed, 0, data.Length);
            Array.Reverse(reversed);
            return new BigInteger(reversed);
#endif
        }
        /// 
        /// Initializes a new instance of the  structure using the SSH BigNum2 Format.
        /// 
        public static BigInteger ToBigInteger2(this byte[] data)
        {
#if NET
            return new BigInteger(data, isBigEndian: true, isUnsigned: true);
#else
            if ((data[0] & (1 << 7)) != 0)
            {
                var buf = new byte[data.Length + 1];
                Buffer.BlockCopy(data, 0, buf, 1, data.Length);
                Array.Reverse(buf);
                return new BigInteger(buf);
            }
            return data.ToBigInteger();
#endif
        }
#if !NET
        public static byte[] ToByteArray(this BigInteger bigInt, bool isUnsigned = false, bool isBigEndian = false)
        {
            var data = bigInt.ToByteArray();
            if (isUnsigned && data[data.Length - 1] == 0)
            {
                data = data.Take(data.Length - 1);
            }
            if (isBigEndian)
            {
                Array.Reverse(data);
            }
            return data;
        }
#endif
#if !NET
        public static long GetBitLength(this BigInteger bigint)
        {
            // Taken from https://github.com/dotnet/runtime/issues/31308
            return (long)Math.Ceiling(BigInteger.Log(bigint.Sign < 0 ? -bigint : bigint + 1, 2));
        }
#endif
        // See https://github.com/dotnet/runtime/blob/9b57a265c7efd3732b035bade005561a04767128/src/libraries/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs#L51
        public static byte[] ExportKeyParameter(this BigInteger value, int length)
        {
            var target = value.ToByteArray(isUnsigned: true, isBigEndian: true);
            // The BCL crypto is expecting exactly-sized byte arrays (sized to "length").
            // If our byte array is smaller than required, then size it up.
            // Otherwise, just return as is: if it is too large, we'll let the BCL throw the error.
            if (target.Length < length)
            {
                var correctlySized = new byte[length];
                Buffer.BlockCopy(target, 0, correctlySized, length - target.Length, target.Length);
                return correctlySized;
            }
            return target;
        }
        /// 
        /// Sets a wait handle, swallowing any resulting .
        /// Used in cases where set and dispose may race.
        /// 
        /// The wait handle to set.
        public static void SetIgnoringObjectDisposed(this EventWaitHandle waitHandle)
        {
            try
            {
                _ = waitHandle.Set();
            }
            catch (ObjectDisposedException)
            {
                // ODE intentionally ignored.
            }
        }
        internal static void ValidatePort(this uint value, [CallerArgumentExpression(nameof(value))] string argument = null)
        {
            if (value > IPEndPoint.MaxPort)
            {
                throw new ArgumentOutOfRangeException(argument,
                                                      string.Format(CultureInfo.InvariantCulture, "Specified value cannot be greater than {0}.", IPEndPoint.MaxPort));
            }
        }
        internal static void ValidatePort(this int value, [CallerArgumentExpression(nameof(value))] string argument = null)
        {
            if (value < IPEndPoint.MinPort)
            {
                throw new ArgumentOutOfRangeException(argument, string.Format(CultureInfo.InvariantCulture, "Specified value cannot be less than {0}.", IPEndPoint.MinPort));
            }
            if (value > IPEndPoint.MaxPort)
            {
                throw new ArgumentOutOfRangeException(argument, string.Format(CultureInfo.InvariantCulture, "Specified value cannot be greater than {0}.", IPEndPoint.MaxPort));
            }
        }
        /// 
        /// Returns a specified number of contiguous bytes from a given offset.
        /// 
        /// The array to return a number of bytes from.
        /// The zero-based offset in  at which to begin taking bytes.
        /// The number of bytes to take from .
        /// 
        /// A  array that contains the specified number of bytes at the specified offset
        /// of the input array.
        /// 
        ///  is .
        /// 
        /// When  is zero and  equals the length of ,
        /// then  is returned.
        /// 
        public static byte[] Take(this byte[] value, int offset, int count)
        {
            ThrowHelper.ThrowIfNull(value);
            if (count == 0)
            {
                return Array.Empty();
            }
            if (offset == 0 && value.Length == count)
            {
                return value;
            }
            var taken = new byte[count];
            Buffer.BlockCopy(value, offset, taken, 0, count);
            return taken;
        }
        /// 
        /// Returns a specified number of contiguous bytes from the start of the specified byte array.
        /// 
        /// The array to return a number of bytes from.
        /// The number of bytes to take from .
        /// 
        /// A  array that contains the specified number of bytes at the start of the input array.
        /// 
        ///  is .
        /// 
        /// When  equals the length of , then 
        /// is returned.
        /// 
        public static byte[] Take(this byte[] value, int count)
        {
            ThrowHelper.ThrowIfNull(value);
            if (count == 0)
            {
                return Array.Empty();
            }
            if (value.Length == count)
            {
                return value;
            }
            var taken = new byte[count];
            Buffer.BlockCopy(value, 0, taken, 0, count);
            return taken;
        }
        public static bool IsEqualTo(this byte[] left, byte[] right)
        {
            ThrowHelper.ThrowIfNull(left);
            ThrowHelper.ThrowIfNull(right);
            return left.AsSpan().SequenceEqual(right);
        }
        /// 
        /// Trims the leading zero from a byte array.
        /// 
        /// The value.
        /// 
        ///  without leading zeros.
        /// 
        public static byte[] TrimLeadingZeros(this byte[] value)
        {
            ThrowHelper.ThrowIfNull(value);
            for (var i = 0; i < value.Length; i++)
            {
                if (value[i] == 0)
                {
                    continue;
                }
                // if the first byte is non-zero, then we return the byte array as is
                if (i == 0)
                {
                    return value;
                }
                var remainingBytes = value.Length - i;
                var cleaned = new byte[remainingBytes];
                Buffer.BlockCopy(value, i, cleaned, 0, remainingBytes);
                return cleaned;
            }
            return value;
        }
        /// 
        /// Pads with leading zeros if needed.
        /// 
        /// The data.
        /// The length to pad to.
        public static byte[] Pad(this byte[] data, int length)
        {
            if (length <= data.Length)
            {
                return data;
            }
            var newData = new byte[length];
            Buffer.BlockCopy(data, 0, newData, newData.Length - data.Length, data.Length);
            return newData;
        }
        public static byte[] Concat(this byte[] first, byte[] second)
        {
            if (first is null || first.Length == 0)
            {
                return second;
            }
            if (second is null || second.Length == 0)
            {
                return first;
            }
            var concat = new byte[first.Length + second.Length];
            Buffer.BlockCopy(first, 0, concat, 0, first.Length);
            Buffer.BlockCopy(second, 0, concat, first.Length, second.Length);
            return concat;
        }
        internal static bool CanRead(this Socket socket)
        {
            return SocketAbstraction.CanRead(socket);
        }
        internal static bool CanWrite(this Socket socket)
        {
            return SocketAbstraction.CanWrite(socket);
        }
        internal static bool IsConnected(this Socket socket)
        {
            if (socket is null)
            {
                return false;
            }
            return socket.Connected;
        }
        internal static string Join(this IEnumerable values, string separator)
        {
            // Used to avoid analyzers asking to "use an overload with a char parameter"
            // which is not available on all targets.
            return string.Join(separator, values);
        }
#if !NET
        internal static bool TryAdd(this Dictionary dictionary, TKey key, TValue value)
        {
            if (!dictionary.ContainsKey(key))
            {
                dictionary.Add(key, value);
                return true;
            }
            return false;
        }
        internal static bool Remove(this Dictionary dictionary, TKey key, out TValue value)
        {
            if (dictionary.TryGetValue(key, out value))
            {
                _ = dictionary.Remove(key);
                return true;
            }
            value = default;
            return false;
        }
        internal static ArraySegment Slice(this ArraySegment arraySegment, int index)
        {
            return new ArraySegment(arraySegment.Array, arraySegment.Offset + index, arraySegment.Count - index);
        }
        internal static ArraySegment Slice(this ArraySegment arraySegment, int index, int count)
        {
            return new ArraySegment(arraySegment.Array, arraySegment.Offset + index, count);
        }
        internal static T[] ToArray(this ArraySegment arraySegment)
        {
            if (arraySegment.Count == 0)
            {
                return Array.Empty();
            }
            var array = new T[arraySegment.Count];
            Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
            return array;
        }
#pragma warning disable CA1859 // Use concrete types for improved performance
        internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)
#pragma warning restore CA1859
        {
            var totalRead = 0;
            while (totalRead < count)
            {
                var read = stream.Read(buffer, offset + totalRead, count - totalRead);
                if (read == 0)
                {
                    throw new EndOfStreamException();
                }
                totalRead += read;
            }
        }
#endif
    }
}