using System;
using System.Collections;
using System.Diagnostics;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Commands;
using UnityEngine.TestRunner.NUnitExtensions.Runner;
using UnityEngine.TestRunner.TestProtocol;

namespace UnityEngine.TestTools
{
    internal class RetryCommand  : DelegatingTestCommand, IEnumerableTestMethodCommand
    {
        public RetryCommand(TestCommand innerCommand)
            : base(innerCommand)
        {
        }

        public override TestResult Execute(ITestExecutionContext context)
        {
            throw new NotImplementedException("Use ExecuteEnumerable");
        }

        public IEnumerable ExecuteEnumerable(ITestExecutionContext context)
        {
            var unityContext = (UnityTestExecutionContext)context;
            if (unityContext.RetryRepeatState?.GetHashCode() == null)
            {
                unityContext.RetryRepeatState = new EnumerableTestState();
            }

            while(unityContext.RetryRepeatState.Retry < unityContext.RetryCount + 1)
            {
                if (innerCommand is IEnumerableTestMethodCommand)
                {
                    var executeEnumerable = ((IEnumerableTestMethodCommand)innerCommand).ExecuteEnumerable(context);
                    foreach (var iterator in executeEnumerable)
                    {
                        yield return iterator;
                    }
                }
                else
                {
                    context.CurrentResult = innerCommand.Execute(context);
                }

                if (context.CurrentResult.ResultState != ResultState.Failure || context.CurrentResult.ResultState == ResultState.Error)
                {
                    unityContext.RetryRepeatState.Retry++;
                    break;
                }

                if (unityContext.Automated && unityContext.RetryRepeatState.Retry < unityContext.RetryCount)
                {
                    ReportTestFinishStartPair(unityContext);
                }
                unityContext.RetryRepeatState.Retry++;
            }
            
            SetIterationProperty(unityContext, unityContext.RetryRepeatState.Retry - 1);
            unityContext.RetryRepeatState.Retry = 0;
        }

        private static void ReportTestFinishStartPair(UnityTestExecutionContext unityContext)
        {
            unityContext.CurrentResult.StartTime = unityContext.StartTime;
            unityContext.CurrentResult.EndTime = DateTime.UtcNow;
            long tickCount = Stopwatch.GetTimestamp() - unityContext.StartTicks;
            double seconds = (double) tickCount / Stopwatch.Frequency;
            unityContext.CurrentResult.Duration = seconds;
            SetIterationProperty(unityContext, unityContext.RetryRepeatState.Retry);
            unityContext.Listener.TestFinished(unityContext.CurrentResult);

            // Start new test iteration
            unityContext.CurrentResult = unityContext.CurrentTest.MakeTestResult();
            unityContext.StartTime = DateTime.UtcNow;
            unityContext.StartTicks = Stopwatch.GetTimestamp();
            unityContext.Listener.TestStarted(unityContext.CurrentTest);
        }

        private static void SetIterationProperty(UnityTestExecutionContext unityContext, int iteration)
        {
            unityContext.CurrentResult.Test.Properties.Set("retryIteration", iteration);
        }
    }
}
