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

using System.Threading.Tasks;
using System.Threading;

namespace Parallel_Petle
{
    class Program
    {
        static void PetlaFor()
        {
            Parallel.For(
                0,
                10,
                (i) => { Console.WriteLine("Iteracja nr {0}, zadanie nr {1}", i, Task.CurrentId); }
                );
        }

        static void PetlaFor_LiczbyPierwsze()
        {
            Console.Write("Sprawdź, czy liczba pierwsza: ");
            int n = Int32.Parse(Console.ReadLine());

            bool pierwsza = true;
            Parallel.For(2, (int)Math.Sqrt(n) + 1, (i) =>
                {
                    if (n % (int)i == 0)
                    {
                        Console.WriteLine("{0} dzieli się przez {1}", n, i);
                        pierwsza = false;
                    }
                });
            if (pierwsza)
            {
                Console.WriteLine("Liczba {0} jest liczbą pierwszą", n);
            }
        }

        static void PetlaForEach_RozmiaryPlikow()
        {
            string[] pliki = System.IO.Directory.GetFiles(@"c:\windows", "*");

            Parallel.ForEach<string>(pliki, (plik) =>
                {
                    System.IO.FileInfo info = new System.IO.FileInfo(plik);
                    Console.WriteLine("{0}\t\t{1}", plik, info.Length);
                });
        }

        static void MetodaInvoke()
        {
            Action a = () => { Console.WriteLine("Zadanie nr {0}", Task.CurrentId); };
            Parallel.Invoke(a,a,a,a,a,a,a,a,a,a);
        }

        static void PrzerywaniePetli_LiczbyPierwsze()
        {
            Console.Write("Sprawdź, czy liczba pierwsza: ");
            int n = Int32.Parse(Console.ReadLine());

            CancellationTokenSource cts = new CancellationTokenSource();
            CancellationToken ct = cts.Token;

            ParallelOptions po = new ParallelOptions();
            po.CancellationToken = ct;

            Console.WriteLine("sprawdzam od 2 do {0}", (int)Math.Sqrt(n));

            try
            {
                Parallel.For(2, (int)Math.Sqrt(n) + 1, (i) =>
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(new Random().Next(1000));
                        if (n % (int)i == 0)
                        {
                            Console.WriteLine("{0} dzieli się przez {1}", n, i);
                            cts.Cancel();
                        }
                        ct.ThrowIfCancellationRequested();
                    });

                Console.WriteLine("Liczba {0} jest liczbą pierwszą", n);
            }
            catch (AggregateException)
            {
                Console.WriteLine("Liczba {0} nie jest liczbą pierwszą", n);
            }
        }

        static void KontrolaWykonywaniaPetli_LiczbyPierwsze()
        {
            Console.Write("Sprawdź, czy liczba pierwsza: ");
            int n = Int32.Parse(Console.ReadLine());

            if (Parallel.For(2, (int)Math.Sqrt(n) + 1, (i, stanPetli) =>
            {
                if (n % (int)i == 0)
                {
                    Console.WriteLine("{0} dzieli się przez {1}", n, i);
                    stanPetli.Stop();
                }
            }).IsCompleted)
            {
                Console.WriteLine("Liczba {0} jest liczbą pierwszą", n);
            }
        }

        #region Obliczanie Pi metoda Monte Carlo
        static double ObliczPiSekwencyjnie(long n)
        {
            Random r = new Random();
            double x, y;
            long k = 0;

            for (long i = 0; i < n; i++)
            {
                x = r.NextDouble();
                y = r.NextDouble();
                if (x * x + y * y < 1) k++;
            }
            return 4.0 * k / n;
        }

        static double ObliczPiRownolegle(long n)
        {
            Random r = new Random();
            double x, y;
            long k = 0;

            Parallel.For(
                0, //fromInclusive
                n, //toExclusive
                () => 0, //localInit
                (i, stanPetli, sumaCzesciowa) =>  //body
                {
                    //x = r.NextDouble(); y = r.NextDouble();
                    lock (r) { x = r.NextDouble(); y = r.NextDouble(); }

                    //if (x * x + y * y < 1) sumaCzesciowa++;
                    if (x * x + y * y < 1) Interlocked.Increment(ref sumaCzesciowa);
                    //Console.WriteLine("Zadanie {0}, suma częściowa = {1}", Task.CurrentId, sumaCzesciowa);
                    return sumaCzesciowa;
                },
                //(sumaCzesciowa) => { k += sumaCzesciowa; } //localFinally                    
                (sumaCzesciowa) => { Interlocked.Add(ref k, sumaCzesciowa); } //localFinally                    
                );

            return 4.0 * k / n;
        }

        static Random gr = new Random();

        private static long obliczSekwencje(long n)
        {
            Random r;
            lock (gr)
            {
                r = new Random(gr.Next());
            }
            long k = 0;
            double x, y;

            for (long i = 0; i < n; i++)
            {
                x = r.NextDouble();
                y = r.NextDouble();
                if (x * x + y * y < 1) k++;
            }

            return k;
        }

        public static double ObliczPiRownolegle2(int ileSekwencji, long n)
        {
            long k = 0;
            long nSekwencja = n / ileSekwencji;

            Parallel.For(
                0,
                ileSekwencji,
                () => { return 0; },
                (long i, ParallelLoopState stanPetli, long suma) =>
                {
                    suma += obliczSekwencje(nSekwencja);
                    return suma;
                },
                (suma) => { Interlocked.Add(ref k, suma); }
            );
            return 4.0 * k / n;
        }

        #endregion

        static void Main(string[] args)
        {
            //PetlaFor();
            //PetlaFor_LiczbyPierwsze();
            //PetlaForEach_RozmiaryPlikow();
            //MetodaInvoke();
            //PrzerywaniePetli_LiczbyPierwsze();
            //KontrolaWykonywaniaPetli_LiczbyPierwsze();
            
            long iloscProb = (long)1E7;
            int iloscProcesorow = System.Environment.ProcessorCount;
            Console.WriteLine("Ilość prób: {0}\nIlość procesorów: {1}\n", iloscProb, iloscProcesorow);

            int czasStart = System.Environment.TickCount;
            Console.WriteLine("Pi = {0}", ObliczPiSekwencyjnie(iloscProb));
            int czasKoniec = System.Environment.TickCount;
            int roznica = czasKoniec - czasStart;
            Console.WriteLine("Czas obliczeń: {0}", roznica);

            czasStart = System.Environment.TickCount;
            Console.WriteLine("Pi = {0}", ObliczPiRownolegle(iloscProb));
            czasKoniec = System.Environment.TickCount;
            roznica = czasKoniec - czasStart;
            Console.WriteLine("Czas obliczeń: {0}", roznica);

            czasStart = System.Environment.TickCount;
            Console.WriteLine("Pi = {0}", ObliczPiRownolegle2(iloscProcesorow, iloscProb));
            czasKoniec = System.Environment.TickCount;
            roznica = czasKoniec - czasStart;
            Console.WriteLine("Czas obliczeń: {0}", roznica);

            Console.WriteLine("Naciśnij jakiś klawisz...");
            Console.Read();
        }
    }
}
