// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Connections;
using Newtonsoft.Json;

namespace Microsoft.AspNetCore.SignalR.Crankier
{
    public class Runner : IRunner
    {
        private readonly Agent _agent;
        private readonly string _targetUrl;
        private readonly int _numberOfWorkers;
        private readonly int _numberOfConnections;
        private readonly int _sendDurationSeconds;
        private readonly HttpTransportType _transportType;

        public Runner(Agent agent, string targetUrl, int numberOfWorkers, int numberOfConnections, int sendDurationInSeconds, HttpTransportType transportType)
        {
            _agent = agent;
            _targetUrl = targetUrl;
            _numberOfWorkers = numberOfWorkers;
            _numberOfConnections = numberOfConnections;
            _sendDurationSeconds = sendDurationInSeconds;
            _transportType = transportType;
        }

        public async Task RunAsync()
        {
            _agent.Runner = this;

            await _agent.StartWorkersAsync(_targetUrl, _numberOfWorkers, _transportType, _numberOfConnections);

            // Begin writing worker status information
            var writeStatusCts = new CancellationTokenSource();
            var writeStatusTask = WriteConnectionStatusAsync(writeStatusCts.Token);

            // Wait until all connections are connected
            while (_agent.GetWorkerStatus().Aggregate(0, (state, status) => state + status.Value.ConnectedCount) <
                _agent.TotalConnectionsRequested)
            {
                await Task.Delay(1000);
            }

            // Stay connected for the duration of the send phase
            await Task.Delay(TimeSpan.FromSeconds(_sendDurationSeconds));

            // Disconnect
            await _agent.StopWorkersAsync();

            // Stop writing worker status information
            writeStatusCts.Cancel();
            await writeStatusTask;
        }

        private Task WriteConnectionStatusAsync(CancellationToken cancellationToken)
        {
            return Task.Run(async () =>
            {
                var peakConnections = 0;
                while (!cancellationToken.IsCancellationRequested)
                {
                    var statusDictionary = _agent.GetWorkerStatus();

                    // Total things up
                    var status = new StatusInformation();
                    foreach (var value in statusDictionary.Values)
                    {
                        status = status.Add(value);
                    }

                    peakConnections = Math.Max(peakConnections, status.ConnectedCount);
                    status.PeakConnections = peakConnections;

                    Trace.WriteLine(JsonConvert.SerializeObject(status));

                    await Task.Delay(1000);
                }
            });
        }

        public Task PongWorkerAsync(int workerId, int value)
        {
            throw new NotImplementedException();
        }

        public Task LogAgentAsync(string format, params object[] arguments)
        {
            Trace.WriteLine(string.Format(format, arguments));
            return Task.CompletedTask;
        }

        public Task LogWorkerAsync(int workerId, string format, params object[] arguments)
        {
            Trace.WriteLine(string.Format("({0}) {1}", workerId, string.Format(format, arguments)));
            return Task.CompletedTask;
        }
    }
}
