using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter20.Listing20_01.Tests
{
    [TestClass]
    public class ProgramTests
    {
        public static bool IsIncrementDecrementLikelySynchronized(
            Func<string[], int> func, long iterations)
        {
            string[] args = { iterations.ToString() };
            return func(args) == 0;
        }


        /// <summary>
        /// Zwraca true, jeśli operacje inkrementacji/dekrementacji nie są atomowe.
        /// 
        /// WAŻNE: Ta metoda nie dowodzi, że operacja jest atomowa. Zwraca true, gdy coś
        /// nie jest atomowe. Zwrócone false oznacza, że nie wiadomo, czy operacja jest atomowa.
        /// </summary>
        /// <param name="func"></param>
        /// <returns></returns>
        public static bool IsIncrementDecrementNotAtomic(
            Func<string[], int> func)
        {
            bool unsyncrhonized = false;

            CancellationTokenSource cancellationtokenSource
                = new CancellationTokenSource();

            // Kilka prób ze zwiększaniem liczby inkrementacji i dekrementacji (na wypadek zbiegu okoliczności).
            ParallelOptions options = new ParallelOptions();
            options.CancellationToken = cancellationtokenSource.Token;
            Program.CancellationToken = cancellationtokenSource.Token;

            try
            {
                Parallel.For(1, 3, options, i =>
                {
                    string[] count = { int.MaxValue.ToString() };
                    // Inna możliwość to iterowanie od 5-9 (bez 10) i
                    // ustalanie zmiennej count za pomocą { (2 * Math.Pow(10, i)).ToString() };
                    // W ten sposób count jest zwiększane logarytmicznie do wartości int.MaxValue.

                if (func(count) != 0)
                {
                    unsyncrhonized = true;
                    cancellationtokenSource.Cancel();
                }

                });
            }
            catch (OperationCanceledException) { }

            return unsyncrhonized;
        }

        static public void VerifyOutputIncrementAndDecrement(Func<string[], int> main)
        {
            int? result = null;
            string expected = $"Inkrementacja i dekrementacja. \\d* razy...{Environment.NewLine}Count = (?<Count>\\d*)";
            string output = IntelliTect.TestTools.Console.ConsoleAssert.Execute("",
            () =>
            {
                result = main(new string[] { "1" });
            });
            MatchCollection matches = Regex.Matches(output, expected, System.Text.RegularExpressions.RegexOptions.Multiline);
            Assert.IsTrue(matches.Count > 0);
            Assert.IsTrue(matches[0].Success);
            Assert.AreEqual(result.ToString(), matches[0].Groups["Count"].Value);
        }


        [TestMethod]
        public void MainVerifyOutputIncrementAndDecrement()
        {
            VerifyOutputIncrementAndDecrement(Program.Main);
        }

        /// <summary>
        /// Informuje, że operacje inkrementacji/dekrementacji nie są atomowe.
        /// WAŻNE: Wynik może być niepewny, jeśli inkrementacji i dekrementacji przypadkowo jest tyle samo.
        /// Nie dowodzi to jednak, że operacje są zsynchronizowane (nie da się tego łatwo
        /// udowodnić w wyniku wykonania kodu).
        /// </summary>      
        [TestMethod]
        public void UnsynchronizedIncrementAndDecrement()
        {
            bool isUnsynchronized = IsIncrementDecrementNotAtomic(Program.Main);
            if (!isUnsynchronized)
            {
                Assert.Inconclusive("Nieoczekiwanie liczba inkrementacji i dekrementacji była taka sama, choć nie zastosowano blokad.");
            }
        }
    }
}
