using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Net3_Proxy
{
    public static class Extensions
    {
        public static T GetCustomAttribute<T>(this ParameterInfo element) where T : Attribute
            => (T)GetCustomAttribute(element, typeof(T));

        public static Attribute GetCustomAttribute(this MemberInfo element, Type attributeType)
            => Attribute.GetCustomAttribute(element, attributeType);

        public static Attribute GetCustomAttribute(this ParameterInfo element, Type attributeType) 
            => Attribute.GetCustomAttribute(element, attributeType);

        public static StringBuilder Clear(this StringBuilder sb)
            => sb.Remove(0, sb.Length);

        public static bool HasFlag<E>(this E e, E o) where E : Enum
        {
            var ei = Convert.ToUInt64(e);
            var oi = Convert.ToUInt64(o);
            return (ei & oi) == oi;
        }
    }

    public static class DirectoryInfoExtensions
    {
        public static IEnumerable<FileInfo> EnumerateFiles(this DirectoryInfo self)
        {
            return self.EnumerateFiles("*", SearchOption.TopDirectoryOnly);
        }

        public static IEnumerable<FileInfo> EnumerateFiles(this DirectoryInfo self, string searchPattern)
        {
            return self.EnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly);
        }

        public static IEnumerable<FileInfo> EnumerateFiles(this DirectoryInfo self, string searchPattern, SearchOption searchOption)
        {
            if (searchPattern == null)
            {
                throw new ArgumentNullException("searchPattern");
            }
            return CreateEnumerateFilesIterator(self, searchPattern, searchOption);
        }


        private static IEnumerable<FileInfo> CreateEnumerateFilesIterator(DirectoryInfo self, string searchPattern, SearchOption searchOption)
        {
            foreach (string fileName in Directory.GetFiles(self.FullName, searchPattern, searchOption))
                yield return new FileInfo(fileName);
            yield break;
        }
    }

    public static class StreamExtensions
    {
        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task CopyToAsync(this Stream src, Stream destination) => CopyToAsync(src, destination, 81920);

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task CopyToAsync(this Stream src, Stream destination, int bufferSize) => CopyToAsync(src, destination, bufferSize, CancellationToken.None);

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task CopyToAsync(this Stream src, Stream destination, int bufferSize, CancellationToken cancellationToken)
        {
            if (destination == null)
            {
                throw new ArgumentNullException("destination");
            }
            if (bufferSize <= 0)
            {
                throw new ArgumentOutOfRangeException("bufferSize", "Positive number required.");
            }
            if (!src.CanRead && !src.CanWrite)
            {
                throw new ObjectDisposedException(null, "Cannot access a closed Stream.");
            }
            if (!destination.CanRead && !destination.CanWrite)
            {
                throw new ObjectDisposedException("destination", "Cannot access a closed Stream.");
            }
            if (!src.CanRead)
            {
                throw new NotSupportedException("Stream does not support reading.");
            }
            if (!destination.CanWrite)
            {
                throw new NotSupportedException("Stream does not support writing.");
            }
            return CopyToAsyncInternal(src, destination, bufferSize, cancellationToken);
        }

        private static async Task CopyToAsyncInternal(Stream src, Stream destination, int bufferSize, CancellationToken cancellationToken)
        {
            byte[] buffer = new byte[bufferSize];
            int bytesRead;
            while ((bytesRead = await src.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) != 0)
            {
                await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);
            }
        }

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task<int> ReadAsync(this Stream src, byte[] buffer, int offset, int count)
        {
            return ReadAsync(src, buffer, offset, count, CancellationToken.None);
        }

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task<int> ReadAsync(this Stream src, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (!cancellationToken.IsCancellationRequested)
            {
                return BeginEndReadAsync(src, buffer, offset, count);
            }
            return new Task<int>(() => 0, cancellationToken);
        }

        private static Task<int> BeginEndReadAsync(Stream src, byte[] buffer, int offset, int count)
            => Task<int>.Factory.FromAsync(
                (byte[] buffer_, int offset_, int count_, AsyncCallback callback, object state) =>
                    src.BeginRead(buffer_, offset_, count_, callback, state),
                (IAsyncResult asyncResult) => src.EndRead(asyncResult), 
                buffer, 
                offset, 
                count,
                new object());

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task WriteAsync(this Stream src, byte[] buffer, int offset, int count)
        {
            return WriteAsync(src, buffer, offset, count, CancellationToken.None);
        }

        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static Task WriteAsync(this Stream src, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (!cancellationToken.IsCancellationRequested)
            {
                return BeginEndWriteAsync(src, buffer, offset, count);
            }
            return new Task<int>(() => 0, cancellationToken);
        }

        private static Task BeginEndWriteAsync(Stream src, byte[] buffer, int offset, int count)
            => Task.Factory.FromAsync(
                (byte[] buffer_, int offset_, int count_, AsyncCallback callback, object state) =>
                    src.BeginWrite(buffer_, offset_, count_, callback, state),
                (IAsyncResult asyncResult) => src.EndWrite(asyncResult),
                buffer,
                offset,
                count,
                new object());

    }
}
