﻿namespace StateMachine.Tests.Sync
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Exceptions;
    using Xunit;

    public class ComplexState : IState<object>
    {
        public ComplexState(string str)
        {
        }

        public void OnEnter(object context)
        {
        }

        public void OnExit(object context)
        {
        }
    }

    public class BuildingTests
    {
        private readonly Func<Type, IState<object>> factoryFunc;

        public BuildingTests()
        {
            factoryFunc = type =>
            {
                if (type == typeof(ComplexState))
                    return new ComplexState("Test");
                if (type == typeof(Start))
                    return new Start();

                return null;
            };
        }

        [Fact]
        public void FactoryIsNeededWhenUsingStatesWithNoParameterlessConstructor()
        {
            var builder = new StateMachine<object, Trigger>.Builder();
            builder.State<ComplexState>()
                   .IsInitialState();

            Assert.Throws<InitializationException>(() => builder.Build());
        }

        [Fact]
        public void StateFactoryUsedForComplexType()
        {
            var builder = new StateMachine<object, Trigger>.Builder(null, factoryFunc);

            builder.State<Start>()
                   .IsInitialState()
                   .On(Trigger.MoveNext)
                   .GoesTo<ComplexState>();

            var sm = builder.Build();
            var list = new List<KeyValuePair<Type, Type>>();
            sm.TransitionCompleted += (sender, args) => list.Add(new KeyValuePair<Type, Type>(args.Source, args.Target));
            sm.Trigger(Trigger.MoveNext);
            Assert.Equal(1, list.Count);
            Assert.Equal(typeof(Start), list.First().Key);
            Assert.Equal(typeof(ComplexState), list.First().Value);
        }

        [Fact(DisplayName = "Factory function cannot return null")]
        public void FactoryHasToResolveAllStates()
        {
            var builder = new StateMachine<object, Trigger>.Builder(null, factoryFunc);

            builder.State<Start>()
                   .IsInitialState()
                   .On(Trigger.MoveNext)
                   .GoesTo<ComplexState>();

            builder.State<ComplexState>()
                   .On(Trigger.MoveNext)
                   .GoesTo<End>();

            var sm = builder.Build();

            sm.Trigger(Trigger.MoveNext);
            Assert.Throws<StateMachineException>(() => sm.Trigger(Trigger.MoveNext));
        }

        [Fact]
        public void InitialStateHasToBeSpecified()
        {
            var builder = new StateMachine<object, Trigger>.Builder();
            builder.State<Start>()
                   .On(Trigger.MoveNext)
                   .GoesTo<End>();

            Assert.Throws<StateNotFoundException>(() => builder.Build());
        }

        [Fact(DisplayName = "Invalid trigger handler will be called (if it's specified) on invalid trigger.")]
        public void InvalidTriggerHandlerIsCalledIfSpecified()
        {
            string error = null;

            var builder = new StateMachine<object, Trigger>.Builder()
            {
                OnInvalidTrigger =
                    (type, trigger) =>
                        error = string.Format("Type {0} does not have trigger {1} specified.", type, trigger)
            };
            builder.State<Start>()
                .IsInitialState()
                .On(Trigger.MoveNext)
                .GoesTo<End>();

            var machine = builder.Build();
            machine.Trigger(Trigger.MoveNext);

            Assert.Null(error);

            machine.Trigger(Trigger.MoveNext);

            Assert.Equal("Type StateMachine.Tests.Sync.End does not have trigger MoveNext specified.", error);
        }
    }
}
