﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Common.Core;
using Microsoft.R.ExecutionTracing;
using Microsoft.R.StackTracing;

namespace Microsoft.R.Host.Client.Test {
    internal static class RSessionExtensions {
        public static async Task NextPromptShouldBeBrowseAsync(this IRSession session) {
            var tracer = await session.TraceExecutionAsync();      

            // Grab the next available prompt, and verify that it's Browse>
            using (var inter = await session.BeginInteractionAsync()) {
                inter.Contexts.IsBrowser().Should().BeTrue("Next available prompt should be a Browse> prompt");
            }

            // Now wait until session tells us that it has noticed the prompt and processed it.
            // Note that even if we register the handler after it has already noticed, but
            // before the interaction completed, it will still invoke the handler.
            var browseTask = EventTaskSources.IRExecutionTracer.Browse.Create(tracer);

            // Spin until either Browse is raised, or we see a different non-Browse prompt.
            // If the latter happens, we're not going to see Browse raised for that prompt that we've
            // seen initially, because something had interfered asynchronously, which shouldn't happen
            // in a test - and if it does, the test should be considered failed at that point, because
            // the state is indeterminate.
            while (true) {
                var interTask = session.BeginInteractionAsync();
                var completedTask = await Task.WhenAny(browseTask, interTask);

                if (completedTask == browseTask) {
                    interTask.ContinueWith(t => t.Result.Dispose(), TaskContinuationOptions.OnlyOnRanToCompletion).DoNotWait();
                    return;
                }

                using (var inter = await interTask) {
                    inter.Contexts.IsBrowser().Should().BeTrue();
                }
            }
        }

        public static async Task<IRStackFrame[]> ShouldHaveTracebackAsync(this IRSession session, TracebackBuilder builder) {
            var expected = builder.ToArray();
            var actual = (await session.TracebackAsync()).ToArray();
            actual.ShouldBeEquivalentTo(expected, options => builder.Configure(options));
            return actual;
        }

        public static async Task ShouldBeAtAsync(this IRSession session, string fileName, int lineNumber) {
            var actual = (await session.TracebackAsync()).Last();
            actual.FileName.Should().Be(fileName);
            actual.LineNumber.Should().Be(lineNumber);
        }

        public static Task ShouldBeAtAsync(this IRSession session, RSourceLocation location, int offset = 0) {
            return session.ShouldBeAtAsync(location.FileName, location.LineNumber + offset);
        }

        public static Task<IRBreakpoint> CreateBreakpointAsync(this IRExecutionTracer tracer, SourceFile sf, int lineNumber) {
            return tracer.CreateBreakpointAsync(new RSourceLocation(sf.FilePath, lineNumber));
        }
    }
}
