// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Reactive.Disposables;
using System.Reactive.Linq;
#if WINDOWS8
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif
namespace Microsoft.Reactive.Testing
{
    /// 
    /// Helper class to write asserts in unit tests for applications and libraries built using Reactive Extensions.
    /// 
    public static class ReactiveAssert
    {
        static string Message(IEnumerable actual, IEnumerable expected)
        {
            var sb = new StringBuilder();
            sb.AppendLine();
            sb.Append("Expected: [");
            sb.Append(string.Join(", ", expected.Select(x => x.ToString()).ToArray()));
            sb.Append("]");
            sb.AppendLine();
            sb.Append("Actual..: [");
            sb.Append(string.Join(", ", actual.Select(x => x.ToString()).ToArray()));
            sb.Append("]");
            sb.AppendLine();
            return sb.ToString();
        }
        /// 
        /// Asserts that both enumerable sequences have equal length and equal elements.
        /// 
        /// The type of the elements in the sequence.
        /// Expected sequence.
        /// Actual sequence to compare against the expected one.
        ///  or  is null.
        public static void AreElementsEqual(IEnumerable expected, IEnumerable actual)
        {
            if (expected == null)
                throw new ArgumentNullException("expected");
            if (actual == null)
                throw new ArgumentNullException("actual");
            if (!expected.SequenceEqual(actual))
                Assert.Fail(Message(actual, expected));
        }
        /// 
        /// Asserts that both enumerable sequences have equal length and equal elements.
        /// 
        /// The type of the elements in the sequence.
        /// Expected sequence.
        /// Actual sequence to compare against the expected one.
        /// Error message for assert failure.
        ///  or  is null.
        public static void AreElementsEqual(IEnumerable expected, IEnumerable actual, string message)
        {
            if (expected == null)
                throw new ArgumentNullException("expected");
            if (actual == null)
                throw new ArgumentNullException("actual");
            if (!expected.SequenceEqual(actual))
                Assert.Fail(message);
        }
        /// 
        /// Asserts that both observable sequences have equal length and equal notifications.
        /// 
        /// The type of the elements in the sequence.
        /// Expected sequence.
        /// Actual sequence to compare against the expected one.
        ///  or  is null.
        public static void AreElementsEqual(IObservable expected, IObservable actual)
        {
            if (expected == null)
                throw new ArgumentNullException("expected");
            if (actual == null)
                throw new ArgumentNullException("actual");
            AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable());
        }
        /// 
        /// Asserts that both observable sequences have equal length and equal elements.
        /// 
        /// The type of the elements in the sequence.
        /// Expected sequence.
        /// Actual sequence to compare against the expected one.
        /// Error message for assert failure.
        ///  or  is null.
        public static void AreElementsEqual(IObservable expected, IObservable actual, string message)
        {
            if (expected == null)
                throw new ArgumentNullException("expected");
            if (actual == null)
                throw new ArgumentNullException("actual");
            AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable(), message);
        }
        /// 
        /// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof.
        /// 
        /// Type of the exception to check for.
        /// Action to run.
        ///  is null.
        public static void Throws(Action action) where TException : Exception
        {
            if (action == null)
                throw new ArgumentNullException("action");
            var failed = false;
            try
            {
                action();
                failed = true;
            }
            catch (TException)
            {
            }
            catch (Exception ex)
            {
                Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace));
            }
            if (failed)
                Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name));
        }
        /// 
        /// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof.
        /// 
        /// Type of the exception to check for.
        /// Action to run.
        /// Error message for assert failure.
        ///  is null.
        public static void Throws(Action action, string message) where TException : Exception
        {
            if (action == null)
                throw new ArgumentNullException("action");
            var failed = false;
            try
            {
                action();
                failed = true;
            }
            catch (TException)
            {
            }
            catch
            {
                Assert.Fail(message);
            }
            if (failed)
                Assert.Fail(message);
        }
        /// 
        /// Asserts that the given action throws the specified exception.
        /// 
        /// Type of the exception to check for.
        /// Exception to assert being thrown.
        /// Action to run.
        ///  is null.
        public static void Throws(TException exception, Action action) where TException : Exception
        {
            if (action == null)
                throw new ArgumentNullException("action");
            var failed = false;
            try
            {
                action();
                failed = true;
            }
            catch (TException ex)
            {
                Assert.AreSame(exception, ex);
            }
            catch (Exception ex)
            {
                Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace));
            }
            if (failed)
                Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name));
        }
        /// 
        /// Asserts that the given action throws the specified exception.
        /// 
        /// Type of the exception to check for.
        /// Exception to assert being thrown.
        /// Action to run.
        /// Error message for assert failure.
        ///  is null.
        public static void Throws(TException exception, Action action, string message) where TException : Exception
        {
            if (action == null)
                throw new ArgumentNullException("action");
            var failed = false;
            try
            {
                action();
                failed = true;
            }
            catch (TException ex)
            {
                Assert.AreSame(exception, ex);
            }
            catch
            {
                Assert.Fail(message);
            }
            if (failed)
                Assert.Fail(message);
        }
        /// 
        /// Asserts that both enumerable sequences have equal length and equal elements.
        /// 
        /// The type of the elements in the sequence.
        /// Actual sequence to compare against the expected one.
        /// Expected sequence.
        ///  or  is null.
        public static void AssertEqual(this IEnumerable actual, IEnumerable expected)
        {
            if (actual == null)
                throw new ArgumentNullException("actual");
            if (expected == null)
                throw new ArgumentNullException("expected");
            ReactiveAssert.AreElementsEqual(expected, actual);
        }
        /// 
        /// Asserts the enumerable sequence has the expected elements.
        /// 
        /// The type of the elements in the sequence.
        /// Actual sequence to compare against the expected elements.
        /// Expected elements.
        ///  or  is null.
        public static void AssertEqual(this IEnumerable actual, params T[] expected)
        {
            if (actual == null)
                throw new ArgumentNullException("actual");
            if (expected == null)
                throw new ArgumentNullException("expected");
            ReactiveAssert.AreElementsEqual(expected, actual);
        }
        /// 
        /// Asserts that both observable sequences have equal length and equal notifications.
        /// 
        /// The type of the elements in the sequence.
        /// Actual sequence to compare against the expected one.
        /// Expected sequence.
        ///  or  is null.
        public static void AssertEqual(this IObservable actual, IObservable expected)
        {
            if (actual == null)
                throw new ArgumentNullException("actual");
            if (expected == null)
                throw new ArgumentNullException("expected");
            ReactiveAssert.AreElementsEqual(expected, actual);
        }
    }
}