//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
{
    public static class MyStringExtensions
    {
        // metoda rozszerzenia dodana do typu String,
        // bez dodatkowych argumentów
        public static string GetSHA1Hash(
            this string text)
        {
            if (string.IsNullOrEmpty(text))
                return null;
            
            SHA1Managed sha1 = new SHA1Managed();

            byte[] bytes = sha1.ComputeHash(
                new UnicodeEncoding().GetBytes(text));

            return Convert.ToBase64String(bytes);
        }

        // metoda rozszerzenia dodana do typu String,
        // pojedynczy argument — ciąg znaków: url.
        public static string CreateHyperlink(
            this string text, 
            string url)
        {
            return String.Format(
                "<a href='{0}'>{1}</a>", url, text);
        }
    }

    [Title("Rozdział 2 - Wprowadzenie do LINQ to Objects")]
    [Prefix("Listing_2_")]
    public class Chapter02Samples : SampleHarness
    {
        [Category("Funkcje języka")]
        [Title("Listing 2-1 : Dodawanie metody GetSHA1Hash do typu string jako przykład metody rozszerzenia")]
        [Description("Dodawanie metody GetSHA1Hash do typu string jako przykład metody rozszerzenia.")]
        [LinkedClass("SampleQueries.MyStringExtensions")]
        public void Listing_2_1_ExtensionMethods()
        {
            // Haszowanie SHA1 ciągu znaków. 
            // GetSHA1Hash wprowadzona jako metoda rozszerzenia
            string password = "ClearTextPassword";
            string hashedPassword = password.GetSHA1Hash();

            // wyświetlanie wyników w konsoli
            Console.WriteLine("- Haszowanie SHA1 ciągu znaków -");
            Console.WriteLine("Oryginał: " + password);
            Console.WriteLine("Po haszowaniu: " + hashedPassword);
        }

        [Category("Funkcje języka")]
        [Title("Listing 2 : Metody rozszerzeń")]
        [Description("Ten przykład pokazuje, jak dodawać metody rozszerzeń do różnych typów.")]
        [LinkedClass("SampleQueries.MyStringExtensions")]
        public void Listing_2_ExtensionMethods()
        {
            // tworzenie fragmentu z łączem HTML
            // CreateHyperlink wprowadzony za pomocą metody rozszerzenia
            string name = "Hooked on LINQ";
            string link = name.CreateHyperlink(
                "http://www.hookedonlinq.com");

            Console.WriteLine("- Ciąg znaków na łącze -");
            Console.WriteLine("Oryginał: " + name);
            Console.WriteLine("Tekst łącza: " + link);
            Console.WriteLine();
        }
  
        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 = "Mateusz",    LastName = "Gajewski",      DateOfBirth = new DateTime(1945,10,19), Phone = "885 983 885", Email = "gajewski@aspiring-technology.com", State = "MA" },
                    new Contact {FirstName = "Sławomir",   LastName = "Gajewski",      DateOfBirth = new DateTime(1945,10,19), Phone = "885 983 885", Email = "gajewski@aspiring-technology.com", State = "WM" },
                    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 = "Adam",       LastName = "Gadomski",      DateOfBirth = new DateTime(1959,10,03), Phone = "115 999 115", Email = "adamg@aspiring-technology.com", State = "OP" },  
                    new Contact {FirstName = "Adam",       LastName = "Gadomski",      DateOfBirth = new DateTime(1959,10,03), Phone = "115 999 115", Email = "adamg@aspiring-technology.com", State = "MA" },  
                    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 = "Hawrat",        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) }
                };
            }
        }



        [Category("Funkcje języka")]
        [Title("Listing 2-2 : Składnia inicjalizatorów obiektów — 'przed' i 'po'")]
        [Description("Ten przykład pokazuje składnię inicjalizatorów obiektów — 'przed' i 'po'.")]
        [LinkedClass("Contact")]
        public void Listing_2_2_ObjectInitializers()
        {
            // stara składnia inicjalizacji => wiele instrukcji
            Contact contactOld = new Contact();
            contactOld.LastName = "Magennis";
            contactOld.DateOfBirth = new DateTime(1973, 12, 09);

            // nowa składnia inicjalizacji => jedna instrukcja
            Contact contactNew = new Contact
            {
                LastName = "Magennis",
                DateOfBirth = new DateTime(1973, 12, 09)
            };
        }

        [Category("Funkcje języka")]
        [Title("Listing 2-3 :Składnia inicjalizacji kolekcji — 'przed' i 'po'")]
        [Description("Ten przykład pokazuje składnię inicjalizacji kolekcji — 'przed' i 'po'.")]
        [LinkedClass("Contact")]
        public void Listing_2_3_CollectionInitializers()
        {
            // stara składnia inicjalizacji => wiele instrukcji
            List<string> stringsOld = new List<string>();
            stringsOld.Add("string 1");
            stringsOld.Add("string 2");

            // nowa składnia inicjalizacji => jedna instrukcja
            List<string> stringsNew = new List<string> {
                "string 1",
                "string 2" };

            // łączenie inicjalizacji obiektu i kolekcji
            // tworzenie listy kontaktów i dodawanie dwóch rekordów
            List<Contact> list = new List<Contact> {
                new Contact { 
                    LastName = "Magielska",
                    DateOfBirth = new DateTime(1973,12,09) 
                },
                new Contact {
                    LastName = "Dąbrowski",
                    DateOfBirth = new DateTime(1978,1,05)
                }
            };
        }

        [Category("Funkcje języka")]
        [Title("Listing 2-4 : Deklarowanie zmiennych lokalnych z domyślnym określeniem typów")]
        [Description("Ten przykład pokazuje zmienne lokalne z domyślnie określonymi typami.")]
        [LinkedClass("Contact")]
        public void Listing_2_4_ImplicitlyTypedLocalVariables()
        {
            // domyślne określanie typów zmiennych lokalnych
            var anInt = 1;
            var aString = "Test";
            var listContact = new List<Contact>();
            var intArray = new int[] { 0, 1, 2, 3, 4 };

            // deklaracje powyżej są odpowiednikami następujących
            int anIntOld = 1;
            string aStringOld = "Test";
            List<Contact> listContactOld = new List<Contact>();
            int[] intArrayOld = new int[] { 0, 1, 2, 3, 4 };

            // zmienna list uzyskuje typ: List<Contact>,
            // możemy zobaczyć, jak inicjalizacja obiektu i kolekcji
            // obywa się z użyciem słowa kluczowego var.
            var list = new List<Contact> {
                new Contact { 
                    LastName = "Magielska",
                    DateOfBirth = new DateTime(1973,12,09) 
                },
                new Contact {
                    LastName = "Dąbrowski",
                    DateOfBirth = new DateTime(1978,1,05)
                }
            };

            /* Poniższe deklaracje nie zostaną skompilowane: 
            (informacje dzięki uprzejmości specyfikacji C# 3.0)
             
            var x; // brak inicjalizera do ustalenia typu              
            var z = null; // null nie ma typu
            var u = 
              delegate { return 1; }; // funkcja anonimowa nie ma typu  
            var v = v++; // inicjalizer nie może odwoływać się do samego siebie       
            var y = {1, 2, 3}; // inicjalizer tablicy nie jest dozwolony
            
            */

            // tylko w celu usunięcia ostrzeżeń kompilatora
            anInt = anIntOld;
            aString = aStringOld;
        }



        [Category("Funkcje języka")]
        [Title("Listing 2-5 : Deklarowanie i używanie typów anonimowych")]
        [Description("Ten przykład pokazuje deklarowanie i używanie typów anonimowych.")]
        public void Listing_2_5_AnonymousTypes()
        {
            // prosta deklaracja typu anonimowego
            Console.WriteLine("- Prosty typ anonimowy -");
            var item = new { Name = "Samochód", Price = 9989.00 };

            Console.WriteLine("Typ: {0}, Nazwa: {1}, Cena: {2}",
                item.GetType().ToString(), item.Name, item.Price);

            // deklarowanie i praca z tablicą typów anonimowych
            Console.WriteLine();
            Console.WriteLine("- Iterowanie przez tablicę typów anonimowych -");
            var list = new[] {
                new { LastName = "Magielska",
                      DateOfBirth = new DateTime(1973,12,09) 
                },
                new { LastName = "Dąbrowski",
                      DateOfBirth = new DateTime(1978,1,05)
                }
            };

            foreach (var x in list)
                Console.WriteLine("{0} ({1})",
                    x.LastName, x.DateOfBirth);

            // optymalizacja przez kompilator - ze specyfikacji C#
            // wewnątrz tego samego programu, dwa anonimowe inicjalizatory obiektów
            // tworzące sekwencję właściwości 
            // tych samych nazw i typów czasu kompilacji w tej samej kolejności
            // stworzą instancje tego samego typu anonimowego.
            var p1 = new { Name = "Kosiarka", Price = 495.00 };
            var p2 = new { Name = "Łopata", Price = 26.95 };
            p1 = p2; // prawidłowe: ten sam typ anonimowy
        }

        [Category("Funkcje języka")]
        [Title("Listing 2-6 : Przykłady metody anonimowej (C# 2.0) i wyrażenia lambda (C# 3.0)")]
        [Description("Przykłady metody anonimowej (C# 2.0) i wyrażenia lambda (C# 3.0).")]
        public void Listing_2_6_LambdaExpressions()
        {
            // łącznie kodu ze zdarzeniem kliknięcia przycisku
            //   metoda anonimowa -
            Button button = new Button();
            button.Click += delegate(object sender, EventArgs args)
            {
                MessageBox.Show("Kliknięcie");
            };

            //   wyrażenie lambda -
            button.Click += (object sender, EventArgs args) =>
                MessageBox.Show("Kliknięcie");

            var data = Contact.SampleData().ToList();

            // przekazywanie kodu do metody ForEach dla List<T>
            //   metoda anonimowa -
            data.ForEach(delegate(Contact c) 
                { c.LastName = c.LastName.ToUpper(); });

            //   wyrażenie lambda -
            data.ForEach(c => c.LastName = c.LastName.ToUpper());

            data.ForEach( c => Console.WriteLine("{0}, {1}", 
                c.LastName, c.FirstName) );

            // przekazywanie kodu do metody rozszerzenia
            //  metoda anonimowa -
            var q1 = data
                     .Where(
                        delegate(Contact c){return c.State == "WA";});

            // wyrażenie lambda -
            var q2 = data
                     .Where(c => c.State == "WA");
        }
        
        [Category("Funkcje języka")]
        [Title("Listing 2-7 : Wyrażenia zapytań")]
        [Description("Przykładowe wyrażenie zapytania pokazuje nowe konstrukcje języka razem.")]
        [LinkedClass("Person")]
        public void Listing_2_7_QueryExpressions()
        {
            // uwaga: Person.SampleData() zwraca wypełnioną
            // przykładową kolekcję danych List<Person>

            // składnia wyrażenia zapytania LINQ
            var query1 = from c in Person.SampleData()
                         where c.State == "WM"
                         select new
                         {
                             Name = c.FirstName + " " + c.LastName,
                             State = c.State
                         };

            // składnia metody rozszerzenia LINQ, identyczna z powyższą
            var query2 = Person.SampleData()
                         .Where(c => c.State == "WM")
                         .Select(c => new
                         {
                             Name = c.FirstName + " " + c.LastName,
                             State = c.State
                         });

            foreach (var item in query1)
                Console.WriteLine("{0}, ({1})", item.Name, item.State);
        }

        public class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string State { get; set; }

            public static List<Person> SampleData()
            {
                // uwaga: dla uproszczenia przykładowe dane korzystają z inicjalizera kolekcji C#3.0 
                return new List<Person> {
                    new Person { FirstName = "Troy",   LastName = "Magennis",  State = "WA" },
                    new Person { FirstName = "Janet",  LastName = "Doherty",   State = "MA" },
                    new Person { FirstName = "James",  LastName = "Wann",      State = "CA" },
                    new Person { FirstName = "Tara",   LastName = "Wann",      State = "WA" }
                };
            }
        }

        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-8 : Proste zapytanie LINQ wykonane na tablicy liczb całkowitych")]
        [Description("Przykład demonstruje proste zapytanie LINQ wykonane na tablicy liczb całkowitych.")]
        public void Listing_2_8_SimpleQueryExpression()
        {
            int[] nums = new int[] { 0, 4, 2, 6, 3, 8, 3, 1 };

            var result = from n in nums
                         where n < 5
                         orderby n
                         select n;

            foreach (int i in result)
                Console.WriteLine(i);
        }

        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-9 : Prosta funkcja LINQ obliczająca sumę wszystkich wartości w tablicy liczb całkowitych ")]
        [Description("Przykład demonstruje proste zapytanie z operatorem agregacji LINQ obliczające sumę wszystkich wartości w tablicy liczb całkowitych.")]
        public void Listing_2_9_SimpleAggregationQueryExpression()
        {
            int[] nums = new int[] { 0, 4, 2, 6, 3, 8, 3, 1 };
            int result = nums.Sum();
            Console.WriteLine(result);
        }

        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-10 : Kontakty w wieku mniejszym niż 35 lat")]
        [Description("Przykład demonstruje, jak wyszukać wszystkie kontakty w wieku mniejszym niż 35 lat, w kolejności od najmłodszego do najstarszego.")]
        public void Listing_2_10_ContactsLessThan35()
        {
            List<Contact> contacts = Contact.SampleData();

            var q = from c in contacts
                    where c.DateOfBirth.AddYears(35) > DateTime.Now
                    orderby c.DateOfBirth descending
                    select string.Format("{0} {1} ur. {2}",
                              c.FirstName,
                              c.LastName,
                              c.DateOfBirth.ToString("dd-MMM-yyyy")
                            );            
            foreach (string s in q)
                Console.WriteLine(s);
        }

        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-11 : Proste grupowanie na podstawie wartości pola.")]
        [Description("Przykład demonstruje, jak utworzyć podgrupy na podstawie wartości wyrażenia (wszystkie rekordy spełniające warunki wyrażenia).")]
        public void Listing_2_11_ContactGrouping()
        {
            List<Contact> contacts = Contact.SampleData();

            var q = from c in contacts
                    group c by c.State;

            foreach (var group in q)
            {
                Console.WriteLine("Województwo: " + group.Key);
                foreach (Contact c in group)
                    Console.WriteLine("  {0} {1}",
                        c.FirstName,
                        c.LastName);
            }

        }

        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-12 : Proste złączenie na podstawie wspólnej wartości.")]
        [Description("Przykład demonstruje, jak złączyć dwie kolekcje w pamięci na podstawie wspólnej wartośći.")]
        public void Listing_2_12_JoiningData()
        {
            List<Contact> contacts = Contact.SampleData();
            List<CallLog> callLog = CallLog.SampleData();

            var q = from call in callLog
                    join contact in contacts on
                         call.Number equals contact.Phone
                    select new  {
                        contact.FirstName,
                        contact.LastName,
                        call.When,
                        call.Duration
                    };

            foreach (var c in q)
                Console.WriteLine(
                    "{0} – {1} {2} ({3}min)",
                    c.When.ToString("ddMMM HH:m"), c.FirstName,
                    c.LastName, c.Duration);
        }


        [Category("LINQ to Objects — pięciominutowy opis")]
        [Title("Listing 2-13 : Sumowanie danych z dwóch kolekcji.")]
        [Description("Podsumowanie połączeń przychodzących pokazuje filtrowanie, sortowanie, grupowanie, złączanie i wybieranie za pomocą wartości agregacji.")]
        public void Listing_2_13_SummarizingDataFromMultipleCollections()
        {
            List<Contact> contacts = Contact.SampleData();
            List<CallLog> callLog = CallLog.SampleData();

            var q = from call in callLog
                    where call.Incoming == true
                    group call by call.Number into g
                    join contact in contacts on 
                         g.Key equals contact.Phone
                    orderby contact.FirstName, contact.LastName
                    select new  {
                        contact.FirstName,
                        contact.LastName,
                        Count = g.Count(),
                        Avg = g.Average(c => c.Duration),
                        Total = g.Sum(c => c.Duration)
                    };

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

        }
    }
}