/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   TestCaseExtension Library, Copyright 2015 Bryan Chadwick            *
 *                                                                       *
 *   FILE: .\TestCaseInvoker.cs                                          *
 *                                                                       *
 *   This file is part of TestCaseExtension.                             *
 *                                                                       *
 *   TestCaseExtension is free software: you can redistribute it and/or  *
 *   modify it under the terms of the GNU General Public License         *
 *   as published by the Free Software Foundation, either version        *
 *   3 of the License, or (at your option) any later version.            *
 *                                                                       *
 *   TestCaseExtension is distributed in the hope that it will be useful,*
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with TestCaseExtension.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *                                                                       *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestCaseExtension.Internal;

namespace TestCaseExtension
{
    /// <summary>Responsible for Invoking <c>TestMethods</c> within a <c>TestCaseClass</c>.  Handles
    ///        both methods with and without <c>TestCase</c>/<c>TestCaseSource</c> attrubutes.</summary>
    public class TestCaseInvoker : ITestMethodInvoker
    {
        private readonly TestMethodInvokerContext invokerContext;

        /// <summary>Create a TestInvoker with the given invokerContext</summary>
        public TestCaseInvoker(TestMethodInvokerContext invokerContext)
        {
            this.invokerContext = invokerContext;
        }

        /// <summary>Invokes a <c>TestMethod</c> with the given arguments.</summary>
        public TestMethodInvokerResult Invoke(params object[] args)
        {
            MethodInfo method = invokerContext.TestMethodInfo;
            var results = new TestMethodInvokerResult();

            if (method.IsExplicitOrIgnored())
            {
                return results;
            }

            IList<TestCaseAttribute> testCases = method.GetAttributes<TestCaseAttribute>();
            IList<TestCaseSourceAttribute> testCaseSources = method.GetAttributes<TestCaseSourceAttribute>();
            if (!testCases.Any() && !testCaseSources.Any())
            {
                // Default: invoke the TestMethod mormally
                return invokerContext.InnerInvoker.Invoke(null);
            }

            // Invoke each TestCase(...) with its arguments
            var failures = new TestCaseFailureException();
            foreach (TestCaseData testCase in GetTestCaseData(testCases, testCaseSources))
            {
                TestMethodInvokerResult result = invokerContext.InnerInvoker.Invoke(testCase.TestArguments);
                var failure = result.Exception.DeepestException();
                if (failure != null)
                {
                    failures.LogFailure(testCase.TestArguments, invokerContext.TestMethodInfo, failure);
                }
            }

            if (failures.HasFailures)
            {
                results.Exception = failures;
            }
            return results;
        }
        
        private IEnumerable<TestCaseData> GetTestCaseData(IEnumerable<TestCaseAttribute> cases, IEnumerable<TestCaseSourceAttribute> sources)
        {
            return cases.Select(attr => new TestCaseData(attr.TestArguments).SetName(attr.Description))
                .Concat(sources.SelectMany(GetTestCaseDataFromSource));
        }

        private IEnumerable<TestCaseData> GetTestCaseDataFromSource(TestCaseSourceAttribute source)
        {
            string typeName = invokerContext.TestContext.FullyQualifiedTestClassName;
            string sourceName = source.MethodOrPropertyName;

            Type sourceProvider = source.ProvidingType ?? TestExtensionUtil.GetType(typeName);
            if (sourceProvider == null)
            {
                throw new TestCaseSourceException("TestCaseClass type {0} could not be found for TestCaseSource {1}",
                                                  typeName, sourceName);
            }

            MemberInfo[] members = sourceProvider.GetStaticMembers(sourceName);
            if (members.Length == 0)
            {
                throw new TestCaseSourceException("No static member found for TestCaseSource {0}.{1}",
                    sourceProvider.GenericTypeString(), sourceName);
            }
            if (members.Length > 1)
            {
                throw new TestCaseSourceException("Multiple members found matching TestCaseSource {0}.{1}",
                    sourceProvider.GenericTypeString(), sourceName);
            }

            MemberInfo testCaseSource = members[0];
            Type memberType = testCaseSource.GetMemberType();
            if (!typeof(IEnumerable<TestCaseData>).IsAssignableFrom(memberType))
            {
                throw new TestCaseSourceException("TestCaseSource {0} type {1} is incompatible with {2}",
                    sourceName, memberType.GenericTypeString(), typeof(IEnumerable<TestCaseData>).GenericTypeString());
            }
            return sourceProvider.ValueOf<IEnumerable<TestCaseData>>(members[0]);
        }
    }
}