﻿namespace StateMachine.Tests.Async
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Exceptions;
    using Xunit;

    public enum Action
    {
        Assign,
        MarkAsComplete,
        Reject
    }

    public class BasicFunctionality
    {
        public class BaseState : IAsyncState<object> 
        {
            public Task OnEnterAsync(object context)
            {
                return Task.FromResult(true);
            }

            public Task OnExitAsync(object context)
            {
                return Task.FromResult(true);
            }
        }

        public class Draft : BaseState
        {
        }

        public class InProgress : BaseState
        {
        }

        public class Complete : BaseState
        {
        }

        private readonly AsyncStateMachine<object, Action>.Builder builder;
        public BasicFunctionality()
        {
            builder = new AsyncStateMachine<object, Action>.Builder();

            builder.State<Draft>()
                   .IsInitialState()
                   .On(Action.Assign)
                   .GoesTo<InProgress>();

            builder.State<InProgress>()
                   .On(Action.MarkAsComplete)
                   .GoesTo<Complete>()
                   .On(Action.Reject)
                   .GoesTo<Draft>();
        }

        [Fact]
        public async Task RegularUseCase()
        {
            var machine = await builder.BuildAsync();
            var list = new List<Tuple<Type, Type>>();
            machine.TransitionCompleted += (sender, args) => list.Add(Tuple.Create(args.Source, args.Target));
            await machine.TriggerAsync(Action.Assign);
            await machine.TriggerAsync(Action.Reject);
            await machine.TriggerAsync(Action.Assign);
            await machine.TriggerAsync(Action.MarkAsComplete);
            Assert.Equal(4, list.Count);
            Assert.True(list.SequenceEqual(new[]
            {
                Tuple.Create(typeof (Draft), typeof (InProgress)),
                Tuple.Create(typeof (InProgress), typeof (Draft)),
                Tuple.Create(typeof (Draft), typeof (InProgress)),
                Tuple.Create(typeof (InProgress), typeof (Complete))
            }));
        }

        [Fact(DisplayName = "Event should be raised when the transition is complete")]
        public async Task EventIsRaisedWhenTransitionIsComplete()
        {
            bool eventRaised = false;
            var machine = await builder.BuildAsync();
            machine.TransitionCompleted += (sender, args) => eventRaised = true;
            await machine.TriggerAsync(Action.Assign);
            Assert.True(eventRaised);
        }

        [Fact(DisplayName = "If there is no custom handler set for invalid trigger, exception will be thrown.")]
        public async Task ExceptionIsThrownWhenTheTriggerIsNotValidAndActionIsNotSet()
        {
            var machine = await builder.BuildAsync();
            await Assert.ThrowsAsync<ExecutionException>(() => machine.TriggerAsync(Action.Reject));
        }
    }
}
