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

using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;
using System.Collections;
//using System.Diagnostics;

namespace Concurrent
{
    class Program
    {
        static void CollectionBag()
        {
            ConcurrentBag<int> cb = new ConcurrentBag<int>();
            List<int> l = new List<int>();

            Task[] zadania = new Task[10000];

            for (int i = 0; i < 10000; i++)
            {
                zadania[i] = Task.Factory.StartNew(
                    () =>
                    {
                        cb.Add(i);
                        l.Add(i);
                    });
            }

            Task.WaitAll(zadania);

            Console.WriteLine("Ilość elementów (CollectionBag): {0}", cb.Count);
            Console.WriteLine("Ilość elementów (List): {0}", l.Count);
        }

        static void KolejkaStos()
        {
            //zapełnianie kolekcji
            int rozmiar = 10;
            ConcurrentQueue<int> kolejka = new ConcurrentQueue<int>();
            ConcurrentStack<int> stos = new ConcurrentStack<int>();
            for (int i = 0; i < rozmiar; ++i)
            {
                kolejka.Enqueue(i);
                stos.Push(i);
            }

            //pobieranie elementów
            int element;
            string s = "Elementy zdjęte z kolejki (" + kolejka.Count + " elementów):\n";

            for (int i = 0; i < rozmiar; ++i)
            {
                kolejka.TryDequeue(out element);
                s += element.ToString() + " ";
            }
            s += "\n\nElementy zdjęte ze stosu (" + stos.Count + " elementów):\n";
            for (int i = 0; i < rozmiar; ++i)
            {
                stos.TryPop(out element);
                s += element.ToString() + " ";
            }

            Console.WriteLine(s);
        }

        static void BlockingCollection()
        {
            BlockingCollection<int> kolekcja = new BlockingCollection<int>(3);

            Action producent = () =>
            {
                for (int i = 0; i < 5; i++)
                {
                    kolekcja.Add(i);
                    Console.WriteLine("Dodano element {0}", i);
                }
                Thread.Sleep(5000);
                kolekcja.CompleteAdding();
            };
            Action konsument = () =>
            {
                Thread.Sleep(5000);
                foreach (int i in kolekcja.GetConsumingEnumerable())
                {
                    Console.WriteLine("Pobrano element {0}", i);
                }
            };

            Task[] zadania = new Task[2];
            zadania[0] = Task.Factory.StartNew(producent);
            zadania[1] = Task.Factory.StartNew(konsument);

            Task.WaitAll(zadania);
        }

        public class MojStos<T> : IProducerConsumerCollection<T>
        {
            private object obiekt = new object();

            private Stack<T> stos = null;

            public MojStos()
            {
                stos = new Stack<T>();
            }

            public MojStos(IEnumerable<T> kolekcja)
            {
                stos = new Stack<T>(kolekcja);
            }

            public void Push(T element)
            {
                lock (obiekt) stos.Push(element);
            }

            public bool TryPop(out T element)
            {
                bool wynik = true;
                lock (obiekt)
                {
                    if (stos.Count == 0) { element = default(T); wynik = false; }
                    else element = stos.Pop();
                }
                return wynik;
            }

            public bool TryTake(out T element)
            {
                return TryPop(out element);
            }

            public bool TryAdd(T element)
            {
                Push(element);
                return true;
            }

            public T[] ToArray()
            {
                T[] wynik = null;
                lock (obiekt) wynik = stos.ToArray();
                return wynik;
            }

            public void CopyTo(T[] tablica, int indeks)
            {
                lock (obiekt) stos.CopyTo(tablica, indeks);
            }

            public IEnumerator<T> GetEnumerator()
            {
                Stack<T> stos_kopia = null;
                lock (obiekt) stos_kopia = new Stack<T>(stos);
                return stos_kopia.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<T>)this).GetEnumerator();
            }

            public bool IsSynchronized
            {
                get { return true; }
            }

            public object SyncRoot
            {
                get { return obiekt; }
            }

            public int Count
            {
                get { return stos.Count; }
            }

            public void CopyTo(Array tablica, int indeks)
            {
                lock (obiekt) ((ICollection)stos).CopyTo(tablica, indeks);
            }
        }

        static void BlockingCollection_TestMojegoStosu()
        {
            //BlockingCollection<int> kolekcja = new BlockingCollection<int>(3);
            BlockingCollection<int> kolekcja = new BlockingCollection<int>(
                new MojStos<int>(), 
                3);

            Action producent = () =>
            {
                for (int i = 0; i < 5; i++)
                {
                    kolekcja.Add(i);
                    Console.WriteLine("Dodano element {0}", i);
                }
                Thread.Sleep(5000);
                kolekcja.CompleteAdding();
            };
            Action konsument = () =>
            {
                Thread.Sleep(5000);
                foreach (int i in kolekcja.GetConsumingEnumerable())
                {
                    Console.WriteLine("Pobrano element {0}", i);
                }
            };

            Task[] zadania = new Task[2];
            zadania[0] = Task.Factory.StartNew(producent);
            zadania[1] = Task.Factory.StartNew(konsument);

            Task.WaitAll(zadania);
        }

        static void Agregacja()
        {
            /*
            List<string> lista = new List<string>();

            lista.Add("Ala ");
            lista.Add("ma ");
            lista.Add("kota ");

            string wynik = lista.Aggregate(
                "",
                (suma, element) => string.Concat(suma, element),
                (suma) => suma
                );

            Console.WriteLine(wynik);
            */

            int[] wspolrzedne = { 3, 4 };

            double odleglosc = wspolrzedne.AsParallel().Aggregate(
                    0,                                  //wartość początkowa
                    (suma, i) => suma + i * i,          //redukcja pośrednia
                    (suma1, suma2) => suma1 + suma2,    //redukcja końcowa
                    (wynik) => Math.Sqrt((double)wynik) //konwersja
                    );

            Console.WriteLine(odleglosc);
        }

        static void Pi(int zakres)
        {
            Console.WriteLine("Ścisła wartość Pi:     {0}", Math.PI);

            //const int zakres = 10000000;
            //Stopwatch stoper = new Stopwatch();

            Func<int, double> znak = (i) =>
            {
                if (i % 2 == 1)
                    return -1;
                return 1;
            };
            Func<int, double> nparz = (i) =>
            {
                return (double)i * 2 + 1;
            };
            Func<int, double> ciag = (i) =>
            {
                return znak(i) / nparz(i);
            };

            IEnumerable<double> zapytanie = from i in new ConcurrentBag<int>(Enumerable.Range(0, zakres))
                                            select ciag(i);

            int czas = Environment.TickCount;
            var wynik1 = 4 * zapytanie.Sum();
            czas = Environment.TickCount - czas;
            Console.WriteLine("Wynik sekw. Sum      : {0} (czas: {1}ms)", wynik1, czas);

            czas = Environment.TickCount;
            var wynik2 = 4 * zapytanie.AsParallel().Sum();
            czas = Environment.TickCount - czas;
            Console.WriteLine("Wynik równ. Sum      : {0} (czas: {1}ms)", wynik2, czas);

            czas = Environment.TickCount;
            var wynik3 = 4 * new ConcurrentBag<int>(Enumerable.Range(0, zakres)).Aggregate<int, double, double>(
                (double)0,
                (suma, i) => suma + ciag(i),
                (suma) => suma
                    );
            czas = Environment.TickCount - czas;
            Console.WriteLine("Wynik sekw. Aggregate: {0} (czas: {1}ms)", wynik3, czas);

            czas = Environment.TickCount;
            var wynik4 = 4 * new ConcurrentBag<int>(Enumerable.Range(0, zakres)).AsParallel().Aggregate(
                (double)0,
                (suma, i) => suma + ciag(i),
                (suma1, suma2) => suma1 + suma2,
                (suma) => suma
                    );
            czas = Environment.TickCount - czas;
            Console.WriteLine("Wynik równ. Aggregate: {0} (czas: {1}ms)", wynik4, czas);

            Console.WriteLine("Naciśnij dowolny klawisz...");
            Console.ReadKey();
        }

        //Funkcja porównująca, dodatkowo „obciążona” 
        static bool Funkcja(string s)
        {
            Thread.SpinWait(10000000);
            if (s.ToUpper().StartsWith("W") == true)
            {
                return true;
            }
            return false;
        }        

        static void Plinq()
        {
            string[] lista = { "Toruń", "Olsztyn", "Katowice", "Kraków", "Warszawa", "Bydgoszcz", "Gdańsk", "Szczecin", "Wrocław", "Poznań", "Włocławek", "Inowrocław", "Olecko" };
            var zapytanie = from m in lista.AsParallel()
                            where m.StartsWith("W")
                            orderby m.ToUpper()
                            select m;

            var zapytanie1 = lista
                   .AsParallel()
                   .Select(m => { return m; })
                   .Where(m => { return m.StartsWith("W"); })
                   .OrderBy(m => { return m.ToUpper(); });


            string s = "Miasta na literę \"W\":\n";
            foreach (string m in zapytanie) s += m + "\n";
            Console.WriteLine(s);

            var zapytanie2 = from m in lista.AsParallel()
                where Funkcja(m)
                orderby m.ToUpper()
                select m;
        }

        static bool Funkcja(int i)
        {
            Thread.SpinWait(10000000);
            if (i % 2 == 0)
            {
                return true;
            }
            return false;
        }

        static void Plinq_PrzerwanieZapytania()
        {
            //IEnumerable<int> lista = Enumerable.Range(0, 100);
            IEnumerable<int> lista = Enumerable.Range(0, 100);
            CancellationTokenSource cts = new CancellationTokenSource();
            CancellationToken ct = cts.Token;

            var zapytanie = lista
                    .AsParallel().WithCancellation(ct)
                    .Select(m => { return m; })
                    .Where(m => { return Funkcja(m); })
                    .OrderBy(m => { return m; });
            cts.Cancel();
            try
            {
                foreach (var i in zapytanie)
                {
                    if (ct.IsCancellationRequested)
                    {
                        throw new AggregateException(
                                      new Exception("Błąd!"),
                                      new OperationCanceledException()
                                  );
                    }
                    Console.WriteLine(i);
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Wystąpiło przerwanie zapytania");
            }
            catch (AggregateException ae)
            {
                foreach (Exception e in ae.InnerExceptions)
                {
                    if (e.GetType() == typeof(OperationCanceledException))
                    {
                        Console.WriteLine("Wystąpiło przerwanie");
                    }
                    else
                    {
                        //Inny wyjątek
                        Console.WriteLine("Inny błąd: {0}", e.Message);
                    }
                }
            }
        }

        static void ForAll()
        {
            Enumerable.Range(0, 100)
                .AsParallel()
                .Where(i => { return i % 2 == 0; })
                .ForAll(i => { Console.WriteLine(i); });
        }

        static void Main(string[] args)
        {            
            //CollectionBag();
            //KolejkaStos();
            //BlockingCollection();
            //BlockingCollection_TestMojegoStosu();
            //Agregacja();            
            Pi(10000000);
            //Plinq();
            //Plinq_PrzerwanieZapytania();
            //ForAll();
        }
    }
}
