using BestHTTP.Extensions;
using BestHTTP.Logger;
using BestHTTP.PlatformSupport.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace BestHTTP.Core
{
    public enum RequestEvents
    {
        Upgraded,
        DownloadProgress,
        UploadProgress,
        StreamingData,
        StateChange,
        Resend,
        Headers
    }

    public
#if CSHARP_7_OR_LATER
        readonly
#endif
        struct RequestEventInfo
    {
        public readonly HTTPRequest SourceRequest;
        public readonly RequestEvents Event;

        public readonly HTTPRequestStates State;

        public readonly long Progress;
        public readonly long ProgressLength;

        public readonly byte[] Data;
        public readonly int DataLength;

        public RequestEventInfo(HTTPRequest request, RequestEvents @event)
        {
            this.SourceRequest = request;
            this.Event = @event;

            this.State = HTTPRequestStates.Initial;

            this.Progress = this.ProgressLength = 0;

            this.Data = null;
            this.DataLength = 0;
        }

        public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState)
        {
            this.SourceRequest = request;
            this.Event = RequestEvents.StateChange;
            this.State = newState;

            this.Progress = this.ProgressLength = 0;
            this.Data = null;
            this.DataLength = 0;
        }

        public RequestEventInfo(HTTPRequest request, RequestEvents @event, long progress, long progressLength)
        {
            this.SourceRequest = request;
            this.Event = @event;
            this.State = HTTPRequestStates.Initial;

            this.Progress = progress;
            this.ProgressLength = progressLength;
            this.Data = null;
            this.DataLength = 0;
        }

        public RequestEventInfo(HTTPRequest request, byte[] data, int dataLength)
        {
            this.SourceRequest = request;
            this.Event = RequestEvents.StreamingData;
            this.State = HTTPRequestStates.Initial;

            this.Progress = this.ProgressLength = 0;
            this.Data = data;
            this.DataLength = dataLength;
        }

        public override string ToString()
        {
            return string.Format("[RequestEventInfo SourceRequest: {0}, Event: {1}, State: {2}, Progress: {3}, ProgressLength: {4}, Data: {5}]",
                this.SourceRequest.CurrentUri, this.Event, this.State, this.Progress, this.ProgressLength, this.DataLength);
        }
    }

    internal static class RequestEventHelper
    {
        private static ConcurrentQueue<RequestEventInfo> requestEventQueue = new ConcurrentQueue<RequestEventInfo>();

#pragma warning disable 0649
        public static Action<RequestEventInfo> OnEvent;
#pragma warning restore

        public static void EnqueueRequestEvent(RequestEventInfo @event)
        {
            requestEventQueue.Enqueue(@event);
        }

        internal static void Clear()
        {
            requestEventQueue.Clear();
        }

        internal static void ProcessQueue()
        {
            RequestEventInfo requestEvent;
            while (requestEventQueue.TryDequeue(out requestEvent))
            {
                if (HTTPManager.Logger.Level == Loglevels.All)
                    HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString());

                if (OnEvent != null)
                {
                    try
                    {
                        OnEvent(requestEvent);
                    }
                    catch (Exception ex)
                    {
                        HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex);
                    }
                }

                HTTPRequest source = requestEvent.SourceRequest;
                switch (requestEvent.Event)
                {
                    case RequestEvents.StreamingData:
                        {
                            var response = source.Response;
                            if (response != null)
                                System.Threading.Interlocked.Decrement(ref response.UnprocessedFragments);

                            bool reuseBuffer = true;
                            try
                            {
                                if (source.OnStreamingData != null)
                                    reuseBuffer = source.OnStreamingData(source, response, requestEvent.Data, requestEvent.DataLength);
                            }
                            catch (Exception ex)
                            {
                                HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.StreamingData", ex);
                            }

                            if (reuseBuffer)
                                BufferPool.Release(requestEvent.Data);
                            break;
                        }

                    case RequestEvents.DownloadProgress:
                        try
                        {
                            if (source.OnDownloadProgress != null)
                                source.OnDownloadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
                        }
                        catch (Exception ex)
                        {
                            HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex);
                        }
                        break;

                    case RequestEvents.UploadProgress:
                        try
                        {
                            if (source.OnUploadProgress != null)
                                source.OnUploadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
                        }
                        catch (Exception ex)
                        {
                            HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex);
                        }
                        break;

                    case RequestEvents.Upgraded:
                        try
                        {
                            if (source.OnUpgraded != null)
                                source.OnUpgraded(source, source.Response);
                        }
                        catch (Exception ex)
                        {
                            HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Upgraded", ex);
                        }

                        IProtocol protocol = source.Response as IProtocol;
                        if (protocol != null)
                            ProtocolEventHelper.AddProtocol(protocol);
                        break;

                    case RequestEvents.Resend:
                        source.State = HTTPRequestStates.Initial;
                        
                        var host = HostManager.GetHost(source.CurrentUri.Host);

                        host.Send(source);

                        break;

                    case RequestEvents.Headers:
                        {
                            try
                            {
                                var response = source.Response;
                                if (source.OnHeadersReceived != null && response != null)
                                    source.OnHeadersReceived(source, response);
                            }
                            catch (Exception ex)
                            {
                                HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex);
                            }
                            break;
                        }

                    case RequestEvents.StateChange:
                        RequestEventHelper.HandleRequestStateChange(requestEvent);
                        break;
                }
            }
        }

        private static bool AbortRequestWhenTimedOut(object context)
        {
            HTTPRequest request = context as HTTPRequest;

            if (request.State != HTTPRequestStates.Processing)
                return false; // don't repeat

            // Protocols will shut down themself
            if (request.Response is IProtocol)
                return false;

            if (request.IsTimedOut)
            {
                HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString());
                request.Abort();

                return false; // don't repeat
            }

            return true;  // repeat
        }

        internal static void HandleRequestStateChange(RequestEventInfo @event)
        {
            HTTPRequest source = @event.SourceRequest;

            switch (@event.State)
            {
                case HTTPRequestStates.Processing:
                    if ((!source.UseStreaming && source.UploadStream == null) || source.EnableTimoutForStreaming)
                        BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), @event.SourceRequest, AbortRequestWhenTimedOut));
                    break;

                case HTTPRequestStates.Aborted:
                case HTTPRequestStates.ConnectionTimedOut:
                case HTTPRequestStates.TimedOut:
                case HTTPRequestStates.Error:
                case HTTPRequestStates.Finished:
                    if (source.Callback != null)
                    {
                        try
                        {
                            source.Callback(source, source.Response);
                        }
                        catch (Exception ex)
                        {
                            HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex);
                        }

                        //if (source.Response != null && source.Response.Data != null)
                        //    VariableSizedBufferPool.Release(source.Response.Data);
                    }

                    source.Dispose();

                    HostManager.GetHost(source.CurrentUri.Host)
                                .GetHostDefinition(HostDefinition.GetKeyForRequest(source))
                                .TryToSendQueuedRequests();
                    break;
            }
        }
    }
}
