//Copyright (C) Troy Magennis

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using SampleSupport;
using QuerySamples;
using System.Xml;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
using SampleQueries;
using System.Drawing;

namespace SampleQueries
{
    [Title("Rozdział 5 - Standardowe operatory zapytań")]
    [Prefix("Listing_5_")]
    public class Chapter05Samples : SampleHarness
    {

        #region Sample Data

        public class Contact
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
            public DateTime DateOfBirth { get; set; }
            public string State { get; set; }

            public static List<Contact> SampleData()
            {
                return new List<Contact> {
                    new Contact {FirstName = "Bartłomiej", LastName = "Gajewski",      DateOfBirth = new DateTime(1945,10,19), Phone = "885 983 885", Email = "gajewski@aspiring-technology.com", State = "MA" },
                    new Contact {FirstName = "Alfred",     LastName = "Wieczorek",     DateOfBirth = new DateTime(1973,12,09), Phone = "848 553 848", Email = "al1@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Adam",       LastName = "Gadomski",      DateOfBirth = new DateTime(1959,10,03), Phone = "115 999 115", Email = "adamg@aspiring-technology.com", State = "OP" },
                    new Contact {FirstName = "Jan",        LastName = "Detka",         DateOfBirth = new DateTime(1950,12,16), Phone = "677 602 677", Email = "jan.detka@aspiring-technology.com", State = "MA" },
                    new Contact {FirstName = "Cezary",     LastName = "Zbytek",        DateOfBirth = new DateTime(1935,02,10), Phone = "603 303 603", Email = "czbytek@aspiring-technology.com", State = "LU" },
                    new Contact {FirstName = "Stanisław",  LastName = "Kowal",         DateOfBirth = new DateTime(1950,02,20), Phone = "546 607 546", Email = "kowals@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Cyryl",      LastName = "Latos",         DateOfBirth = new DateTime(1951,10,21), Phone = "278 918 278", Email = "latos@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Bernard",    LastName = "Radliński",     DateOfBirth = new DateTime(1946,05,18), Phone = "715 920 715", Email = "bernard@aspiring-technology.com", State = "WP" },
                    new Contact {FirstName = "Maciej",     LastName = "Karaś",         DateOfBirth = new DateTime(1977,09,17), Phone = "364 202 364", Email = "mac.karas@aspiring-technology.com", State = "WP" },
                    new Contact {FirstName = "Adrian",     LastName = "Horwat",        DateOfBirth = new DateTime(1922,05,23), Phone = "165 737 165", Email = "adrianh@aspiring-technology.com", State = "SW" }
                };

            }
        }

        public class CallLog
        {
            public string Number { get; set; }
            public int Duration { get; set; }
            public bool Incoming { get; set; }
            public DateTime When { get; set; }

            public static List<CallLog> SampleData()
            {
                return new List<CallLog> {
                    new CallLog { Number = "885 983 885", Duration = 2,  Incoming = true,  When = new DateTime(2006,	8,	7,	8,	12,	0)},
                    new CallLog { Number = "165 737 165", Duration = 15, Incoming = true,  When = new DateTime(2006,	8,	7,	9,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 1,  Incoming = false, When = new DateTime(2006,	8,	7,	10,	5,	0) },
                    new CallLog { Number = "603 303 603", Duration = 2,  Incoming = false, When = new DateTime(2006,	8,	7,	10,	35,	0) },
                    new CallLog { Number = "546 607 546", Duration = 4,  Incoming = true,  When = new DateTime(2006,	8,	7,	11,	15,	0) },
                    new CallLog { Number = "885 983 885", Duration = 15, Incoming = false, When = new DateTime(2006,	8,	7,	13,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 3,  Incoming = true,  When = new DateTime(2006,	8,	7,	13,	47,	0) },
                    new CallLog { Number = "546 607 546", Duration = 1,  Incoming = false, When = new DateTime(2006,	8,	7,	20,	34,	0) },
                    new CallLog { Number = "546 607 546", Duration = 3,  Incoming = false, When = new DateTime(2006,	8,	8,	10,	10,	0) },
                    new CallLog { Number = "603 303 603", Duration = 23, Incoming = false, When = new DateTime(2006,	8,	8,	10,	40,	0) },
                    new CallLog { Number = "848 553 848", Duration = 3,  Incoming = false, When = new DateTime(2006,	8,	8,	14,	0,	0) },
                    new CallLog { Number = "848 553 848", Duration = 7,  Incoming = true,  When = new DateTime(2006,	8,	8,	14,	37,	0) },
                    new CallLog { Number = "278 918 278", Duration = 6,  Incoming = true,  When = new DateTime(2006,	8,	8,	15,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 20, Incoming = true,  When = new DateTime(2006,	8,	8,	17,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 5,  Incoming = true,  When = new DateTime(2006,	7,	12,	8,	12,	0)},
                    new CallLog { Number = "165 737 165", Duration = 12, Incoming = true,  When = new DateTime(2006,	6,	14,	9,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 10,  Incoming = false, When = new DateTime(2006,	7,	9,	10,	5,	0) },
                    new CallLog { Number = "603 303 603", Duration = 22,  Incoming = false, When = new DateTime(2006,	7,	5,	10,	35,	0) },
                    new CallLog { Number = "546 607 546", Duration = 9,  Incoming = true,  When = new DateTime(2006,	6,	7,	11,	15,	0) },
                    new CallLog { Number = "885 983 885", Duration = 10, Incoming = false, When = new DateTime(2006,	6,	7,	13,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 21,  Incoming = true,  When = new DateTime(2006,	7,	7,	13,	47,	0) },
                    new CallLog { Number = "546 607 546", Duration = 7,  Incoming = false, When = new DateTime(2006,	7,	7,	20,	34,	0) },
                    new CallLog { Number = "546 607 546", Duration = 2,  Incoming = false, When = new DateTime(2006,	6,	8,	10,	10,	0) },
                    new CallLog { Number = "603 303 603", Duration = 3,  Incoming = false, When = new DateTime(2006,	6,	8,	10,	40,	0) },
                    new CallLog { Number = "848 553 848", Duration = 32,  Incoming = false, When = new DateTime(2006,	7,	8,	14,	0,	0) },
                    new CallLog { Number = "848 553 848", Duration = 13,  Incoming = true,  When = new DateTime(2006,	7,	8,	14,	37,	0) },
                    new CallLog { Number = "278 918 278", Duration = 16,  Incoming = true,  When = new DateTime(2006,	5,	8,	15,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 24, Incoming = true,  When = new DateTime(2006,	6,	8,	17,	12,	0) }
                };
            }
        }
        #endregion

        [Category("Operatory agregacji")]
        [Title("Listing 5_1 : Operator Aggregate")]
        [Description("Ten przykład demonstruje, jak używać operatora Aggregate.")]
        public void Listing_5_1_AggregateOperator()
        {
            var nums = new int[] { 1, 2, 3 };

            var sum = nums.Aggregate(
                0, // wartość ziarna dla acc (acc = 0 dla pierwszego elementu)
                (acc, i) => acc + i, // funkcja akumulatora
                acc => acc + 100 // funkcja selektora (opcjonalna)
            );

            Console.WriteLine("Suma plus 100 = {0}", sum);
        }

        [Category("Operatory agregacji")]
        [Title("Listing 5-2 :  Operator Aggregate z więcej niż jedną wartością ")]
        [Description("Ten przykład demonstruje, jak użyć operatora Aggregate do przechowania kilku wartości z akumulatorze.")]
        public void Listing_5_2_AggregateOperatorMultipleValueAccumulator()
        {
            var nums = new int[] { 1, 2, 3 };

            int count = 0;

            var avg = nums.Aggregate(

                0, // akumulator dla sumy, początkowo 0

                // funkcja akumulatora
                (acc, i) =>
                {
                    count += 1; // bieżąca liczba
                    acc += i; // bieżąca suma
                    return acc;
                },

                // funkcja selektora, zwraca 0 przy braku elementów
                acc => count > 0 ? acc / count : 0
            );

            Console.WriteLine("Średnia  = {0}", avg);
        }

        [Category("Operatory agregacji")]
        [Title("Listing 5-3 : Operatory Sum, Max, Min i Average")]
        [Description("Ten przykład demonstruje, jak używać operatorów Max, Min, Sum i Average.")]
        public void Listing_5_3_AvgMaxMinSumOperators()
        {
            var nums = Enumerable.Range(5, 50);

            var avg = nums.Average();
            var max = nums.Max();
            var min = nums.Min();
            var sum = nums.Sum();

            Console.WriteLine("{0} do {1} (Średnia  = {2}, Suma = {3})",
                min, max, avg, sum);

            var letters = new char[] { 'v', 'e', 'x', 'a' };

            var min2 = letters.Min();
            var max2 = letters.Max();

            Console.WriteLine("{0} do {1}",
                            min2, max2);
        }

        [Category("Operatory agregacji")]
        [Title("Listing 5-4 : Operatory Aggregate w podsumowaniach danych w zapytaniach grupujących dane")]
        [Description("Ten przykład demonstruje, jak używać operatorów Max, Min, Sum i Average.")]
        public void Listing_5_4_AvgMaxMinSumOperators2()
        {
            List<CallLog> callLog = CallLog.SampleData();

            var q = from call in callLog
                    where call.Incoming == true
                    group call by call.Number into g
                    select new
                    {
                        Number = g.Key,
                        Count = g.Count(),
                        Avg = g.Average(c => c.Duration),
                        Total = g.Sum(c => c.Duration)
                    };

            foreach (var call in q)
                Console.WriteLine(
                    "{0} - Połączenia:{1}, Czas:{2}min, Średni:{3}min",
                    call.Number,
                    call.Count, call.Total, Math.Round(call.Avg, 2));
        }

        public void NullableTypeSample()
        {
            Nullable<int> i1 = null;
            int? i2 = null;

            Console.WriteLine("{0}, {1}", i1, i2);

            IEnumerable<int> empty = Enumerable.Empty<int>();
        }

        [Category("Operatory Count/LongCount")]
        [Title("Listing 5-5 : Operator Count")]
        [Description("Ten przykład demonstruje, jak używać operatorów Count i LongCount.")]
        public void Listing_5_5_CountOperator()
        {
            var nums = Enumerable.Range(1, int.MaxValue);

            int c1 = nums.Count();

            // ta sekwencja spowoduje przepełnienie
            // operatora Count, zamiast niego użyj operatora LongCount.
            long c2 = nums.Concat(nums).LongCount();

            Console.WriteLine("{0}, {1}", c1, c2);
        }

        [Category("Operatory Count/LongCount")]
        [Title("Listing 5 : Operator Count z predykatem")]
        [Description("Ten przykład demonstruje, jak używać operatorów Count i LongCount z predykatem.")]
        public void Listing_5_CountOperatorWithPredicate()
        {
            var nums = Enumerable.Range(1, int.MaxValue);

            // Liczenie elementów podzielnych przez 10
            var q3 = nums.Count(i => i % 10 == 0);
            //var q4 = nums.Where(i => i % 10 == 0).Count();

            Console.WriteLine("{0}", q3);

            /* Przetestowałem różnicę czasu i była niewielka...
            Console.WriteLine("q3 = {0}ms, q4 = {1}ms",
                MeasureTime(delegate { nums.Count(i => i % 10 == 0); }, 1000),
                MeasureTime(delegate { nums.Where(i => i % 10 == 0).Count(); }, 1000));
            */

        }

        [Category("Operatory konwersji")]
        [Title("Listing 5 : AsEnumerable")]
        [Description("Ten przykład demonstruje, jak używać operatora AsEnumerable.")]
        public void Listing_5_AsEnumerableOperator()
        {
            List<int> list = new List<int> { 1, 2, 3, 4, 5 };

            // Zmienna enumerable będzie zawierać te same dane, ale 
            // jej typ będzie określony w czasie kompilacji jako IEnumerable<T>
            IEnumerable<int> enumerable = list.AsEnumerable();

        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-6 : Cast")]
        [Description("Ten przykład demonstruje, jak używać operatora Cast.")]
        public void Listing_5_6_CastOperator()
        {
            ArrayList list = new ArrayList();
            list.Add(1);
            list.Add(2);
            list.Add(3);

            // ArrayList nie implementuje IEnumerable<T>.
            // Oznacza to, że nie mamy dostępu do operatorów LING
            // dla instancji kolekcji list
            // do czasu wykonania rzutowania jej za pomocą Cast<T>.
            // Rzutowanie ArrayList na IEnumerable<int> 
            var avg = (from i in list.Cast<int>()
                       where i < 3
                       select i).Average();
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-7 : OfType")]
        [Description("Ten przykład demonstruje, jak używać operatora OfType.")]
        public void Listing_5_7_OfTypeOperator()
        {
            ArrayList list = new ArrayList();
            list.Add(1);
            list.Add(2);
            list.Add(3);
            list.Add("jeden");
            list.Add("dwa");
            list.Add("trzy");


            // ArrayList nie implementuje IEnumerable<T>.
            // Oznacza to, że nie mamy dostępu do operatorów LING
            // dla instancji kolekcji list
            // do czasu wykonania rzutowania jej za pomocą OfType<T>.
            // Operator Cast zgłosiłby wyjątek, 
            // ponieważ nie wszystkie elementy można bezpiecznie rzutować;
            // operatora OfType użyj, kiedy bezpieczeństwo typów nie jest gwarantowane.
            var avg = (from i in list.OfType<int>()
                       where i < 3
                       select i).Average();

            Console.WriteLine("Średnia = {0}", avg);
        }

        public class Shape { }
        public class Rectangle : Shape { }
        public class Circle : Shape { }

        [Category("Operatory konwersji")]
        [Title("Listing 5-8 : Operator OfType filtrujący na podstawie typuoperator to filter by type")]
        [Description("Ten przykład demonstruje, jak używać operatora OfType do filtrowania na podstawie typu.")]
        [LinkedClass("Shape")]
        [LinkedClass("Rectangle")]
        [LinkedClass("Circle")]
        public void Listing_5_8_OfTypeOperatorFilter()
        {
            List<Shape> shapes = new List<Shape> {
                new Rectangle(),
                new Circle(),
                new Rectangle(),
                new Circle(),
                new Rectangle() };

            var q = from rect in shapes.OfType<Rectangle>()
                    select rect;

            Console.WriteLine("Liczba obiektów Rectangle = {0}",
                q.Count());
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-9 : Oparator ToArray")]
        [Description("Ten przykład demonstruje, jak użyć operatora ToArray do skonwertowania IEnumerable<T> na Array<T>.")]
        public void Listing_5_9_ToArrayOperator()
        {
            IEnumerable<int> data = Enumerable.Range(1, 5);

            int[] q = (from i in data
                       where i < 3
                       select i).ToArray();

            Console.WriteLine("Zwraca {0}, {1}",
                q[0], q[1]);
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-10 : Operator ToDictionary operator")]
        [Description("Ten przykład demonstruje, jak użyć operatora ToDictionary do skonwertowania IEnumerable<T> na Dictionary<T,K>.")]
        public void Listing_5_10_ToDictionaryOperator()
        {
            List<Contact> contacts = Contact.SampleData();

            // umieszczanie wyników w strukturze Dictionary
            Dictionary<string, Contact> conDictionary =
                contacts.ToDictionary<Contact, string>(
                c => c.LastName + c.FirstName);

            // wszystkie rekordy kontaktów znalazły się w strukturze Dictionary
            // wyszukiwanie rekordów za pomocą kluczy jest bardzo wydajne
            Contact record = conDictionary["Karaś" + "Maciej"];

            Console.WriteLine("Karaś: data urodzenia = {0}", record.DateOfBirth);
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-11 : Operator ToDictionary z selektorem elementu")]
        [Description("Ten przykład demonstruje, jak użyć operatora ToDictionary do skonwertowania IEnumerable<T> na Dictionary<T,K> używając funkcji selektora elementu do wykonania projekcji do nowego typu.")]
        public void Listing_5_11_ToDictionaryOperatorElementType()
        {
            List<Contact> contacts = Contact.SampleData();

            // umieszczanie wyników w strukturze Dictionary
            // porównywanie kluczy bez uwzględniania wielkości liter
            Dictionary<string, DateTime> dictionary =
                contacts.ToDictionary<Contact, string, DateTime>(
                c => c.LastName + c.FirstName,
                e => e.DateOfBirth,
                StringComparer.CurrentCultureIgnoreCase);

            // obiekt dictionary przechowuje datę urodzenia jako element
            DateTime dob = dictionary["Karaś" + "Maciej"];

            Console.WriteLine("Karaś: data urodzenia = {0}", dob);
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-11 : Operator ToDictionary z selektorem elementu")]
        [Description("Ten przykład demonstruje, jak użyć operatora ToDictionary do skonwertowania IEnumerable<T> na Dictionary<T,K> używając funkcji selektora elementu do wykonania projekcji do typu anonimowego.")]
        public void Listing_5_ToDictionaryOperatorAnonymousType()
        {
            List<Contact> contacts = Contact.SampleData();

            // Umieszczenie wyników w strukturze Dictionary
            var conDictionary =
                contacts.ToDictionary<Contact, string, dynamic>(
                c => c.LastName + c.FirstName,
                e => new { DOB = e.DateOfBirth, Email = e.Email });

            // Wszystkie wyniki znajdują się w strukturze Dictionary
            // Wyszukiwanie rekordów za pomocą klusza = bardzo wydajne
            var record = conDictionary["Karaś" + "Maciej"];

            Console.WriteLine("Karaś: data urodzenia = {0}", record.DOB);
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-12 : Operator ToList")]
        [Description("Ten przykład demonstruje, jak użyć operatora List to skonwertowania IEnumerable<T> na Array<T>.")]
        public void Listing_5_12_ToListOperator()
        {
            IEnumerable<int> data = Enumerable.Range(1, 5);

            // Wykonanie zapytanie i przechwycenie wyników w List<T>
            List<int> q = (from i in data
                           where i < 3
                           select i).ToList();

            // ForEach jest metodą wykonywaną na List<T>
            q.ForEach(j => Console.WriteLine(j));
        }

        [Category("Operatory konwersji")]
        [Title("Listing 5-13 : Operator ToLookup")]
        [Description("Ten przykład demonstruje, jak użyć operatora ToLookup do skonwertowania IEnumerable<T> na ILookup<Tkey,T>.")]
        public void Listing_5_13_ToLookupOperator()
        {
            var contacts = Contact.SampleData();
            var calls = CallLog.SampleData();

            // tworzenie listy wyszukiwania dla sekwencji wewnętrznej
            var calls_lookup =
                (from c in calls
                 orderby c.When descending
                 select c)
                .ToLookup(c => c.Number);

            // złączenie jeden do wielu dla numeru telefonu
            var q3 = from c in contacts
                     orderby c.LastName
                     select new
                     {
                         LastName = c.LastName,
                         Calls = calls_lookup[c.Phone]
                     };

            foreach (var contact in q3)
            {
                Console.WriteLine("Nazwisko: {0}", contact.LastName);

                foreach (var call in contact.Calls)
                    Console.WriteLine(" - połączenie {0} dnia {1} przez {2}min.",
                        call.Incoming ? "przychodzące" : "wychodzące",
                        call.When,
                        call.Duration);
            }
        }

        [Category("Operatory elementów")]
        [Title("Listing 5-15 : Operator DefaultIfEmpty")]
        [Description("Ten przykład demonstruje, jak użyć operatora DefaultIfEmpty.")]
        public void Listing_5_15_DefaultIfEmptyOperator()
        {
            var nums = new int[] { 1, 2, 3, 4, 5 };
            var empty = new int[] { };

            // zwraca 1, ponieważ tablica nie jest pusta
            Console.WriteLine("nums.DefaultIfEmpty() = {0}",
                nums.DefaultIfEmpty().First());

            // zwraca default(int) w IEnumerable<int>
            Console.WriteLine("empty.DefaultIfEmpty() = {0}",
                empty.DefaultIfEmpty().First());

            // zwraca 100. Tablica jest pusta, ale wyraźna
            // wartość domyślna została przekazana jako argument.
            Console.WriteLine("empty.DefaultIfEmpty(100) = {0}",
                empty.DefaultIfEmpty(100).First());
        }

         [Category("Operatory elementów")]
        [Title("Listing 5-16 : Oparatory ElementAt i ElementAtOrDefault")]
        [Description("Ten przykład demonstruje, jak używać operatorów ElementAt i ElementAtOrDefault.")]
        public void Listing_5_16_ElementAtOperator()
        {
            var nums = new int[] { 1, 2, 3, 4, 5 };

            // pobierz trzeci element (liczona od zera pozycja indeksu 2)
            var third = nums.ElementAt(2);

            // ERROR: System.ArgumentOutOfRangeException, jeśli
            // indeks < 0 lub poza sekwencją
            // var error = nums.ElementAt(100);

            // zwraca default(T), 0 dla typu prostego.
            var no_error = nums.ElementAtOrDefault(100);

            Console.WriteLine("nums.ElementAt(2) = {0}",
                nums.ElementAt(2));

            Console.WriteLine("nums.ElementAtOrDefault(100) = {0}",
                nums.ElementAtOrDefault(100));
        }

        [Category("Operatory elementów")]
        [Title("Listing 5-17 : Operatory First and FirstOrDefault")]
        [Description("Ten przykład demonstruje, jak używać operatorów First i FirstOrDefault.")]
        public void Listing_5_17_FirstOperator()
        {
            var nums = new int[] { 1, 2, 3, 4, 5 };
            var empty = new int[] { };

            // pobierz pierwszy element
            var first = nums.First();

            // pobierz pierwszy element > 2
            var third = nums.First(i => i > 2);

            // ERROR: System.InvalidOperationException
            // var error = empty.First();

            // zwraca default(T), 0 dla wartości typu int.
            var no_error = empty.FirstOrDefault();

            // ERROR: System.InvalidOperationException.
            // Nie ma wartości > 10 w tej sekwencji.
            // var error = nums.First(i => i > 10);

            // Nie ma wartości > 10 w tej sekwencji. Zwrócono default(T).
            var no_error2 = nums.FirstOrDefault(i => i > 10);

            Console.WriteLine("first = {0}, third = {1}",
                first, third);

            Console.WriteLine("no_error = {0}, no_error2 = {1}",
                no_error, no_error2);
        }

        [Category("Operatory elementów")]
        [Title("Listing 5-18 : Operatory Last  LastOrDefault")]
        [Description("Ten przykład demonstruje, jak używać operatorów Last i LastOrDefault.")]
        public void Listing_5_18_LastOperator()
        {
            var nums = new int[] { 1, 2, 3, 4, 5 };
            var empty = new int[] { };

            // pobierz ostatni element
            var last = nums.Last();

            // pobierz ostatni element < 4
            var third = nums.Last(i => i < 4);

            // ERROR: System.InvalidOperationException
            // var error = empty.Last();

            // zwraca default(T), 0 dla wartości typu int.
            var no_error = empty.LastOrDefault();

            // ERROR: System.InvalidOperationException.
            // No value > 10 in this sequence.
            // var error = nums.Last(i => i > 10);

            // Nie ma wartości > 10 w tej sekwencji. Zwrócono default(T).
            var no_error2 = nums.LastOrDefault(i => i > 10);

            Console.WriteLine("last = {0}, third = {1}",
                last, third);

            Console.WriteLine("no_error = {0}, no_error2 = {1}",
                no_error, no_error2);
        }

        [Category("Operatory elementów")]
        [Title("Listing 5-19 : Operatory Single i SingleOrDefault operator")]
        [Description("Ten przykład demonstruje, jak używać operatorów how Single i SingleOrDefault.")]
        public void Listing_5_19_SingleOperator()
        {
            var single = new int[] { 5 };
            var nums = new int[] { 1, 2, 3, 4, 5 };
            var empty = new int[] { };

            // pobierz pojedynczy element
            var five = single.Single();

            // pobierz element równy 3
            var third = nums.Single(i => i == 3);

            // ERROR: System.InvalidOperationException
            // w sekwnencji nums znajduje się więcej niż jeden element
            // var error = nums.Single();
            // var error = nums.SingleOrDefault();

            // zwraca default(T), 0 dla wartości typu int.
            var no_error = empty.SingleOrDefault();

            // ERROR: System.InvalidOperationException.
            // W tej sekwencji nie ma wartości == 10.
            // var error = nums.Single(i => i == 10);

            // W tej sekwencji nie ma wartości == 10. Zostaje zwrócone default(T).
            var no_error2 = nums.SingleOrDefault(i => i == 10);

            Console.WriteLine("five = {0}, third = {1}",
                five, third);

            Console.WriteLine(
                "no_error = {0}, no_error2 = {1}",
                no_error, no_error2);
        }

        [Category("Operatory równości")]
        [Title("Listing 5-20 : Operator SquenceEqual")]
        [Description("Ten przykład demonstruje, jak używać operatora SequenceEqual.")]
        public void Listing_5_20_SequenceEqualOperator()
        {
            var n1 = new string[] { "piotr", "paweł", "maria" };
            var n2 = new string[] { "paweł", "piotr", "maria" };
            var n3 = new string[] { "PIOTR", "PAWEŁ", "MARIA" };

            // uwzględniając kolejność
            bool n1n2 = n1.SequenceEqual(n2);

            // uwzględniając wielkość liter i za pomocą domyślnego komparatora
            bool n1n3 = n1.SequenceEqual(n3);

            // przekazywanie komparatora - w tym wypadku
            // wbudowanych instancji statycznych StringComparer
            bool n1n3_2 = n1.SequenceEqual(
                n3, StringComparer.CurrentCultureIgnoreCase);

            Console.WriteLine("n1n2 = {0}, n1n3 = {1}, n1n3_2 = {2}",
                n1n2, n1n3, n1n3_2);
        }

        [Category("Operatory generujące")]
        [Title("Listing 5 : Operator Empty")]
        [Description("Ten przykład demonstruje, jak używać operatora Empty.")]
        [LinkedMethod("MyEmpty<int>")]
        public void Listing_5_EmptyOperator()
        {
            var x = MyEmpty<int>();
            Console.WriteLine("x.Count = {0}", x.Count());

            var y = Enumerable.Empty<int>();
            Console.WriteLine("y.Count = {0}", y.Count());
        }

        public static T[] MyEmpty<T>()
        {
            return new T[0];
        }

        [Category("Operatory generujące")]
        [Title("Listing 5-21 : Operator Range")]
        [Description("Ten przykład demonstruje, jak używać operatora Range.")]
        public void Listing_5_21_RangeOperator()
        {
            var months = Enumerable.Range(1, 12);

            foreach (var item in months)
                Console.Write(item + " ");

            // przydatny do wiązania danych z sekwencji liczb całkowitych z kontrolkami combo-box
            ComboBox yearsCombo = new ComboBox();
            yearsCombo.DataSource =
                Enumerable.Range(1900, 111).Reverse().ToList();

            Form form = new Form();
            form.Controls.Add(yearsCombo);
            form.ShowDialog();
        }

        [Category("Operatory generujące")]
        [Title("Listing 5-22 : Operator Range indeksujący piksele bitmapy")]
        [Description("Ten przykład demonstruje, jak używać operatora Range do iterowania przez piksele bitmapy.")]
        public void Listing_5_22_RangeOperatorWithBitmap()
        {
            // przykład użycia operatora Range do zaadresowania współrzędnych x, y na bitmapie
            string filename = Path.Combine(
                Environment.CurrentDirectory, @"data\4pixeltest.bmp");

            Bitmap bmp = (Bitmap)Image.FromFile(filename);

            var q = from x in Enumerable.Range(0, bmp.Width)
                    from y in Enumerable.Range(0, bmp.Height)
                    let pixel = bmp.GetPixel(x, y)
                    let lum = (byte)((0.2126 * pixel.R)
                                   + (0.7152 * pixel.G)
                                   + (0.0722 * pixel.B))
                    select new { x, y, lum };

            foreach (var item in q)
            {
                Console.WriteLine("{0},{1} - {2}",
                    item.x, item.y, item.lum);
            }
        }

        [Category("Operatory generujące")]
        [Title("Listing 5-23 : Operator Repeat")]
        [Description("Ten przykład demonstruje, jak używać operatora Repeat.")]
        public void Listing_5_23_RepeatOperator()
        {
            int[] i = Enumerable.Repeat(-1, 10).ToArray();
            string[] s = Enumerable.Repeat("No data", 3).ToArray();

            // UWAGA! wszystkie typy referencyjne będą wskazywać na tę samą instancję bitmapy.
            // Upewnij się, że właśnie takiego efektu potrzebujesz.
            Bitmap[] b1 = Enumerable.Repeat(new Bitmap(5, 5), 5).ToArray();

            // Zamiast tego możesz użyć operatora Repeat w konstrukcji pętli
            // i wykonać projekcję za pomocą operatora Select. W tym przypadku
            // otrzymasz pięć różnych instancji Bitmap w tablicy.
            Bitmap[] b2 = Enumerable.Repeat(0, 5)
                .Select(x => new Bitmap(5, 5)).ToArray();
        }

        [Category("Operatory scalania")]
        [Title("Listing 5-24 : Operator Zip")]
        [Description("Ten przykład demonstruje, jak używać operatora Zip..")]
        public void Listing_5_24_ZipOperator()
        {
            var letters = new string[] { "A", "B", "C", "D", "E" };
            var numbers = new int[] { 1, 2, 3 };

            var q = letters.Zip(numbers, (s, i) => s + i.ToString());

            foreach (var s in q)
                Console.WriteLine(s);
        }

        [Category("Operatory partycjonowania")]
        [Title("Listing 5-25 : Operatory Skip i Take")]
        [Description("Ten przykład demonstruje, jak używać operatorów Skip i Take do stronicowania.")]
        public void Listing_5_25_SkipTakeOperator()
        {
            int[] nums = Enumerable.Range(1, 50).ToArray();

            int page = 3;
            int pageSize = 10;

            var q = nums
                .Skip((page - 1) * pageSize)
                .Take(pageSize);

            foreach (var i in q)
                Console.Write(i + " ");

            // własna metoda rozszerzenia do stronicowania
            var q1 = nums.Page(3, 10);
        }

        [Category("Operatory partycjonowania")]
        [Title("Listing 5-26 : Operator Page")]
        [Description("Ten przykład demonstruje, jak używać operatora własnego do stronicowania.")]
        [LinkedClass("PagingExtensions")]
        public void Listing_5_26_PageOperator()
        {
            int[] nums = Enumerable.Range(1, 50).ToArray();

            // własna metoda rozszerzenia do stronicowania
            var q = nums.Page(3, 10);

            foreach (var i in q)
                Console.Write(i + " ");
        }

        [Category("Operatory partycjonowania")]
        [Title("Listing 5-27 : Operator SkipWhile")]
        [Description("Ten przykład demonstruje, jak używać operatora SkipWhile.")]
        public void Listing_5_27_SkipWhileTakeWhileOperator()
        {
            string sampleString =
            @"# linia komentarz 1
            # linia komentarz 2
            Linia danych 1
            Linia danych 2

            Ta linia jest ignorowana.
            ";


            var q = sampleString.Split('\n')
                .SkipWhile(line => line.StartsWith("#"))
                .TakeWhile(line => !string.IsNullOrEmpty(line.Trim()));

            foreach (var s in q)
                Console.WriteLine(s);
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-28 : Operator All ")]
        [Description("Ten przykład demonstruje, jak używać operatora All.")]
        public void Listing_5_28_All()
        {
            var evens = new int[] { 2, 4, 6, 8, 10 };
            var odds = new int[] { 1, 3, 5, 7, 9 };
            var nums = Enumerable.Range(1, 10);

            // odpowiednik zapytania LINQ
            bool b1 = evens.All(i => i % 2 == 0);
            bool b2 =
                evens.Count() == evens.Where(i => i % 2 == 0).Count();

            Console.WriteLine("Wszystkie parzyste? parzyste: {0}, nieparzyste: {1}, liczba: {2}",
                evens.All(i => i % 2 == 0),
                odds.All(i => i % 2 == 0),
                nums.All(i => i % 2 == 0)
            );
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-29 : Operator All z predykatem")]
        [Description("Ten przykład demonstruje, jak używać operatora All z predykatem.")]
        public void Listing_5_29_AllPredicateData()
        {
            var contacts = Contact.SampleData();

            bool allOver21 = contacts.All(
                c => c.DateOfBirth.AddYears(21) < DateTime.Now);

            bool allContactsHaveContactData = contacts.All(
                c => string.IsNullOrEmpty(c.Phone) == false &&
                     string.IsNullOrEmpty(c.Email) == false);

            Console.WriteLine("Czy wszystkie kontakty mają ponad 21 lat? {0}",
                allOver21);

            Console.WriteLine("Czy wszystkie kontakty mają adres email i numer telefonu? {0}",
                allContactsHaveContactData);
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-30 : Operator Any Operator")]
        [Description("Ten przykład demonstruje, jak używać operatora Any.")]
        public void Listing_5_30_Any()
        {
            var empty = Enumerable.Empty<int>();
            var one = new int[] { 1 };
            var many = Enumerable.Range(1, 5);

            // odpowiednik LINQ
            bool b = one.Count() > 0;

            Console.WriteLine("Pusta: {0}, Jeden: {1}, Wiele: {2}",
                empty.Any(), one.Any(), many.Any());
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-31 : Operator Any z predykatem")]
        [Description("Ten przykład demonstruje, jak używać operatora Any z predykatem.")]
        public void Listing_5_31_AnyPredicate()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Wąż", "Emu", "Rekin", 
                "Płaszczka", "Meduza" };

            // odpowiednik LINQ
            bool b = animals.Where(a => a.Contains("Meduza")).Count() > 0;

            bool anyFish = animals.Any(a => a.Contains("Meduza"));
            bool anyCats = animals.Any(a => a.Contains("Kot"));

            Console.WriteLine("Meduzy? {0}, Koty? {1}",
                anyFish, anyCats);
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-32 : Operator Any z predykatem dla danych")]
        [Description("Ten przykład demonstruje, jak używać operatora Any z predykatem dla danych.")]
        public void Listing_5_32_AnyPredicateData()
        {
            var contacts = Contact.SampleData();

            bool anyUnder21 = contacts.Any(
                c => c.DateOfBirth.AddYears(21) > DateTime.Now);

            bool anyMissingContactData = contacts.Any(
                c => string.IsNullOrEmpty(c.Phone) ||
                    string.IsNullOrEmpty(c.Email));

            Console.WriteLine("Czy którakolwiek z osób ma mniej niż 21 lat? {0}",
                anyUnder21);

            Console.WriteLine("Czy którakolwiek z osób nie ma adresu email lub numeru telefonu? {0}",
                anyMissingContactData);
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-33 : Operator Contains")]
        [Description("Ten przykład demonstruje, jak używać operatora Contains.")]
        public void Listing_5_33_Contains()
        {
            string[] names = new string[] { "piotr", "paweł", "maria" };

            bool b1 = names.Contains("PIOTR");
            bool b2 = names.Contains("piotr");

            // Można użyć własnych lub wbudowanych komparatorów.
            bool b3 = names.Contains(
                "PIOTR", StringComparer.CurrentCultureIgnoreCase);

            Console.WriteLine("PIOTR: {0}, piotr: {1}, PIOTR: {2}",
                b1, b2, b3);
        }

        [Category("Operatory kwantyfikacji")]
        [Title("Listing 5-34 : Operator Contains dla List<T>")]
        [Description("Ten przykład demonstruje, jak używać operatora Contains dla List<T>.")]
        public void Listing_5_34_ContainsList()
        {
            // List<T> zawiera już metodę instancji o nazwie Contains.
            // Metoda instancji nie posiada przeciążenia, 
            // które obsługiwałoby komparator równości,
            // a więc należy wywołać metodę rozszerzenia Contains.
            List<string> list =
                new List<string> { "piotr", "paweł", "maria" };

            // użyje metody List<T>.Contains
            bool b4 = list.Contains("PIOTR");

            // wymusi użycie metody rozszerzenia Contains
            bool b5 = list.Contains<string>("PIOTR");

            // jeszcze lepiej będzie po prostu wywołać metodę rozszerzenia
            bool b5a = Enumerable.Contains(list, "PIOTR");

            // Użyje metody rozszerzenia Contains, ponieważ
            // List<T>.Contains nie posiada przeciążenia
            // przyjmującego IEqualityComparer.
            bool b6 = list.Contains(
                "PIOTR", StringComparer.CurrentCultureIgnoreCase);

            Console.WriteLine(
                "Instancja: {0}, Rozszerzenie: {1}, Z komparatorem: {2}",
                b4, b5, b6);
        }

        // prosta funkcja mierząca czas działania
        private long MeasureTime(Action action, int iterations)
        {
            System.Diagnostics.Stopwatch watch =
                new System.Diagnostics.Stopwatch();

            watch.Start();

            for (int i = 0; i < iterations; i++)
                action();

            return watch.ElapsedMilliseconds;
        }
    }

    public static class PagingExtensions
    {
        public static IEnumerable<T> Page<T>(
            this IEnumerable<T> source,
            int page,
            int pageSize)
        {
            const int maxPageSize = 100;

            // przycięcie maksymalnej wielkości strony
            pageSize = pageSize < maxPageSize ?
                pageSize : maxPageSize;

            return source
                .Skip((page - 1) * pageSize)
                .Take(pageSize);
        }
    }


}

