using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Threads
{
    // Listing 17-3
    class Notifier
    {
        private readonly ThreadLocal<bool> _isCallbackInProgress =
            new ThreadLocal<bool>();

        private Action _callback;

        public Notifier(Action callback)
        {
            _callback = callback;
        }

        public void Notify()
        {
            if (_isCallbackInProgress.Value)
            {
                throw new InvalidOperationException(
                    "W tym wątku powiadomienie jest już wykonywane!");
            }
            try
            {
                _isCallbackInProgress.Value = true;
                _callback();
            }
            finally
            {
                _isCallbackInProgress.Value = false;
            }
        }
    }

    class Example3
    {
        public static void DetectReentrancy()
        {
            Action a = null;
            Notifier n = new Notifier(() => a());

            a = () => { };
            n.Notify();

            try
            {
                // Oczekujemy, że poniższy kod wykryje wywołanie wielobieżne
                a = () => n.Notify();
                n.Notify();
            }
            catch (Exception x)
            {
                Console.WriteLine(x);
            }

            using (var wait = new ManualResetEvent(false))
            {
                a = () =>
                {
                    Console.WriteLine("Wywołanie zwrotne w trakcie realizacji");
                    wait.WaitOne();
                    Console.WriteLine("Opuszczamy wywołanie zwrotne");
                };
                Task.Factory.StartNew(() =>
                {
                    n.Notify();
                });
                // Zapewniamy, że oba wywołania zwrotne skończą się kiedy zrobimy już to o co nam chodziło.
                Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(500);
                    wait.Set();
                });

                Thread.Sleep(100);
                // To wywołanie sprawi, że drugie wywołanie zwrotne zostanie wykonane w tym wątku,
                // w czasie gdy pierwsze wywołanie zwrotne będzie wykonywane w innym wątku. 
                // Jest to możliwe gdyż klasa Notifier blokuje wywołania wielobieżne wyłącznie 
                // w ramach tego samego wątku.
                n.Notify();
            }
        }
    }
}
