//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ł 6 - Praca ze zbiorami danych")]
    [Prefix("Listing_6_")]
    public class Chapter06Samples : 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 = "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) }
                };
            }
        }

        #endregion

        [Category("Operatory zbiorów")]
        [Title("Listing 6-1 : Operatory Concat i Union")]
        [Description("Przykład demonstruje najbardziej podstawowe użycie operatorów LINQ Concat i Union LINQ.")]
        public void Listing_6_1_ConcatAndUnionOperators()
        {
            int[] first = new int[] { 1, 2, 3 };
            int[] second = new int[] { 3, 4, 5 }; 

            // Concat zwraca elementy z obu kolekcji.
            var q = first.Concat(second);

            Console.WriteLine(
                "Przykład działania Concat: 1,2,3 po konkatenacji z 3,4,5 - ");

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

            // Union zwraca wynik konkatenacji różniących się elementów z obu kolekcji.
            var q1 = first.Union(second);

            Console.WriteLine();
            Console.WriteLine(
                "Przykład działania Union: 1,2,3 po stworzeniu unii z 3,4,5 - ");

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

        [Category("Operatory zbiorów")]
        [Title("Listing 6-2 : Operator Concat dodający wartości do sekwencji")]
        [Description("Przykład demonstruje, jak dodawać wpisy do kolekcji za pomocą operatora Concat.")]
        public void Listing_6_2_ConcatOperator()
        {
            // początkowa lista stanów
            string[] status = new string[] { 
                "Nie rozpoczęto", "Rozpoczęto", "Zakończono" };

            // pierwszy wpis, który chcemy dodać
            string[] prompt = new string[] {
                "-- brak wyboru --"};

            ComboBox combo = new ComboBox();

            // w tym miejscu dwie sekwencje zostają połączone 
            // i powiązane z kontrolką ComboBox
            combo.DataSource = prompt.Concat(status).ToList();

            // wyświetlanie otrzymanej sekwencji
            // w formularzu testowym
            Form form = new Form();
            form.Text = "TestForm";
            form.Controls.Add(combo);
            form.ShowDialog();
        }

        [Category("Operatory zbiorów")]
        [Title("Listing 6-3 : Operator Distinct")]
        [Description("Przykład demonstruje najbardziej podstawowe użycie operatora LINQ Distinct.")]
        public void Listing_6_3_DistinctOperator()
        {
            int[] source = new int[] { 1, 2, 3, 1, 2, 3, 4, 5 };

            // operator Distinct usuwa duplikaty z kolekcji
            var q = source.Distinct();

            Console.WriteLine(
                "Przykład użycia Distinct: 1, 2, 3, 1, 2, 3, 4, 5 - ");

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

            // operator Distinct dla ciągów znaków z komparatorem 
            string[] names = new string[] 
                { "jeden", "JEDEN", "Jeden", "Dwa", "Dwa" };

            /* pomocne są także wbudowane komparatory:
             * 
             * StringComparer.CurrentCulture: Uwzględniające wielkość liter porównanie za pomocą reguł
             * porównywania słów bieżących ustawień regionalnych. 
             * 
             * StringComparer.CurrentCultureIgnoreCase: Nieuwzględniające wielkości liter porównanie za pomocą reguł
             * porównywania słów bieżących ustawień regionalnych. 
             * 
             * StringComparer.InvariantCulture: Uwzględniające wielkość liter porównanie za pomocą reguł
             * porównywania słów bez użycia ustawień regionalnych.
             * 
             * StringComparer.InvariantCultureIgnoreCase: Nieuwzględniające wielkości liter porównanie za pomocą
             * reguł porównywania słów bez użycia ustawień regionalnych..
             * 
             * StringComparer.Ordinal: Uwzględniające wielkość liter porządkowe porównanie ciągów znaków.
             * 
             * StringComparer.OrdinalIgnoreCase: Nieuwzględniające wielkości liter porządkowe porównanie
             * ciągów znaków.
             */

            var q1 = names.Distinct(
                StringComparer.CurrentCultureIgnoreCase);

            Console.WriteLine();
            Console.WriteLine(
                "Przykład użycia Distinct: jeden, JEDEN, Jeden, Dwa, Dwa - ");

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

        [Category("Operatory zbiorów")]
        [Title("Listing 6-4 : Operator Except ")]
        [Description("Przykład demonstruje najbardziej podstawowe użycie operatora LINQ Except.")]
        public void Listing_6_4_ExceptOperator()
        {
            int[] first = new int[] { 1, 2, 3 };
            int[] second = new int[] { 3, 4, 5 };

            // Operator Except zwraca wszystkie elementy w sekwencji pierwszej,
            // które nie znajdują się w sekwencji drugiej.
            var q = first.Except(second);

            Console.WriteLine(
                "Przykład Except: 1,2,3 z 3,4,5 - ");

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

        [Category("Operatory zbiorów")]
        [Title("Listing 6-5 : Operator Intersect Operator")]
        [Description("Przykład demonstruje najbardziej podstawowe użycie operatora LINQ Intersect.")]
        public void Listing_6_5_IntersectOperator()
        {
            int[] first = new int[] { 1, 2, 3 };
            int[] second = new int[] { 3, 4, 5 };

            // Operator Intersect zwraca tylko te elementy z pierwszej kolekcji, 
            // które znajdują się także w drugiej kolekcji..
            var q = first.Intersect(second);
            Console.WriteLine(
                "Przykład Intersect: 1,2,3 z 3,4,5 - ");

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

        [Category("Operatory zbiorów")]
        [Title("Listing 6-6 : Operator Union i typy anonimowe")]
        [Description("Przykład demonstruje, jak złączyć dane z wielu źródeł za pomocą operatora Union i typu anonimowego.")]
        public void Listing_6_6_AnonymousTypesUnion()
        {
            // Wyszukiwanie użytego ostatnio numeru telefonu LUB
            // imienia i nazwiska kontaktu w celu inkrementowanego tworzenia
            // pomocniczej listy wyboru na podstawie
            // danych częściowych wpisanych przez użytkownika
            // (lista jest skracana w miarę wpisywania danych).
            string userEntry = "Ka";

            var q = (

                     // userEntry jest imieniem lub nazwiskiem kontaktu              
                     from contact in Contact.SampleData()
                     where contact.FirstName.StartsWith(userEntry)
                        || contact.LastName.StartsWith(userEntry)
                     select new { Display = contact.FirstName + " " + 
                                            contact.LastName }).Distinct()

                    .Union(

                    // userEntry jest częścią numeru telefonu
                    (from call in CallLog.SampleData()
                     where call.Number.Contains(userEntry)
                        && call.Incoming == false
                     select new { Display = call.Number }).Distinct()

                    );

            Console.WriteLine(
                "Wpis użytkownika - " + userEntry);

            foreach (var item in q)
                Console.WriteLine(item.Display);


            // wpis numeryczny
            userEntry = "7";

            var q1 = (
                // userEntry jest imieniem lub nazwiskiem kontaktu           
                     from contact in Contact.SampleData()
                     where contact.FirstName.StartsWith(userEntry)
                        || contact.LastName.StartsWith(userEntry)
                     select new { Display = contact.FirstName + " " + contact.LastName })

                    .Union(

                    // userEntry jest częścią numeru telefonu
                    from call in CallLog.SampleData()
                    where call.Number.Contains(userEntry)
                    select new { Display = call.Number }

                    );

            Console.WriteLine();
            Console.WriteLine(
                "Wpis użytkownika - " + userEntry);

            foreach (var item in q1)
                Console.WriteLine(item.Display);
        }

        public class SoundexEqualityComparer
            : IEqualityComparer<string>
        {
            public bool Equals(string x, string y)
            {
                return GetHashCode(x) == GetHashCode(y);
            }

            public int GetHashCode(string obj)
            {
                // Np. konwersja kodu Soundex A123,
                // na liczbę całkowitą: 65123
                int result = 0;

                string s = soundex(obj);
                if (string.IsNullOrEmpty(s) == false)
                    result = Convert.ToInt32(s[0]) * 1000 +
                             Convert.ToInt32(s.Substring(1, 3));

                return result;
            }

            private string soundex(string s)
            {
                // Algorytm jak na
                //     http://en.wikipedia.org/wiki/Soundex.
                // tworzy kod ciągu w formacie:
                //     [A-Z][0-6][0-6][0-6]
                // na podstawie fonetyki wejścia.

                if (String.IsNullOrEmpty(s))
                    return null;

                StringBuilder result =
                    new StringBuilder();

                string source = s.ToUpper().Replace(" ", "");

                // dodaj pierwszy znak, a następnie 
                // wykonaj w pętli mapowanie znaków.
                char previous = '0';

                for (int i = 1; i < source.Length; i++)
                {
                    // map to the soundex numeral
                    char mappedTo = '0';
                    char thisChar = source[i];
                    if ("BFPV".Contains(thisChar))
                        mappedTo = '1';
                    else if ("CGJKQSXZ".Contains(thisChar))
                        mappedTo = '2';
                    else if ("DT".Contains(thisChar))
                        mappedTo = '3';
                    else if ('L' == thisChar)
                        mappedTo = '4';
                    else if ("MN".Contains(thisChar))
                        mappedTo = '5';
                    else if ('R' == thisChar)
                        mappedTo = '6';

                    // igonoruj sąsiadujące ze sobą duplikaty
                    // i niedopasowane znaki
                    if (mappedTo != previous && mappedTo != '0')
                    {
                        result.Append(mappedTo);
                        previous = mappedTo;
                    }
                }

                while (result.Length < 4)
                    result.Append("0");

                return result.ToString(0, 4);
            }
        }

        [Category("Operatory zbiorów")]
        [Title("Listing 6-7 : Własny komparator równości - Soundex")]
        [Description("Przykład demonstruje, jak używać własnego komparatora równości w operatorach zbiorów.")]
        [LinkedClass("SoundexEqualityComparer")]
        public void Listing_6_7_SetCustomEqualityComparer()
        {
            // znajdywanie imion podobnych fonetycznie na liście
            string[] names = new string[] { "Janet", "Janette", "Joanne", 
                "Jo-anne", "Johanne", "Katy", "Katie", "Ralph", "Ralphe" };

            var q = names.Distinct(
                new SoundexEqualityComparer());

            Console.WriteLine("Liczba imion odmiennych fonetycznie = {0}",
                q.Count());
        }

        [Category("Operatory zbiorów")]
        [Title("Listing 6-8 : HashSet")]
        [Description("Przykład demonstruje, jak używać HashSet w zapytaniu LINQ.")]
        public void Listing_6_8_HashSetAndLINQ()
        {
            int[] first = new int[] { 1, 2, 3 };
            int[] second = new int[] { 3, 4, 5 };

            // Modyfikowanie bieżącego zestawu poprzez utworzenie unii z drugim zestawem.
            HashSet<int> set = new HashSet<int>(first);
            set.UnionWith(second);

            // Zwracanie tylko parzystych liczb z zestawu.
            // Wartości z HashSet są IEnumerable<T>.
            var q = from i in set
                    where i % 2 == 0
                    select i;

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

        [Category("Operatory zbiorów")]
        [Title("Listing 6 : SymetricExceptWith (XOR)")]
        [Description("Przykład demonstruje, jak stworzyć zapytanie XOR w LINQ.")]
        public void Listing_6_ExclusiveOr()
        {
            int[] first = new int[] { 1, 2, 3 };
            int[] second = new int[] { 3, 4, 5 };
            
            // Modify the current set by unioning with second.
            HashSet<int> set = new HashSet<int>(first);
            set.SymmetricExceptWith(second);

            Console.WriteLine("SymetricExceptWith - 1,2,3 i 3,4,5");
            foreach (var item in set)
                Console.Write(item + " ");

            // concat two reciprocal except operations
            var q = first.Except(second)
                    .Concat(
                    second.Except(first));

            Console.WriteLine();
            Console.WriteLine("LINQ XOR - 1,2,3 i 3,4,5");
            foreach (var item in q)
                Console.Write(item + " ");
        }

        [Category("Operatory zbiorów")]
        [Title("Listing 6 : Podzbiory i nadzbiory")]
        [Description("Przykład demonstruje, jak ustalić podzbiory i nadzbiory.")]
        [LinkedClass("SampleQueries.LINQSetOperatorExtensions")]
        public void Listing_6_SuperSetAndSubset()
        {
            int[] first = new int[] { 1, 2, 3, 4, 5, 5 };
            int[] second = new int[] { 5, 4, 3, 2, 1, 1, 6 };

            // HashSet
            HashSet<int> set = new HashSet<int>(first);
            Console.WriteLine("IsSupersetOf = {0}", set.IsSupersetOf(second));
            Console.WriteLine("IsProperSupersetOf = {0}", set.IsProperSupersetOf(second));
            
            HashSet<int> set2 = new HashSet<int>(second);
            Console.WriteLine("IsSubsetOf = {0}", set2.IsSupersetOf(first));
            Console.WriteLine("IsProperSubsetOf = {0}", set2.IsProperSupersetOf(first));
            Console.WriteLine("Overlaps = {0}", set2.Overlaps(first));
            Console.WriteLine("SetEquals = {0}", set.SetEquals(second));

            // rozszerzenia własne LINQ
            Console.WriteLine("LINQ IsSupersetOf = {0}", first.IsSupersetOf(second));
            Console.WriteLine("LINQ IsProperSupersetOf = {0}", first.IsProperSupersetOf(second));
            Console.WriteLine("LINQ IsSubsetOf = {0}", second.IsSupersetOf(first));
            Console.WriteLine("LINQ IsProperSubsetOf = {0}", second.IsProperSupersetOf(first));
            Console.WriteLine("LINQ Overlaps = {0}", second.Overlaps(first));
            Console.WriteLine("LINQ SetEquals = {0}", first.SetEquals(second));
        }
    }

    // Listing 6-9
    public static class LINQSetOperatorExtensions
    {
        public static IEnumerable<T> SymmetricExcept<T>(
            this IEnumerable<T> first, 
            IEnumerable<T> second)
        {
            if (first == null) 
                throw new ArgumentNullException("first");
            
            if (second == null) 
                throw new ArgumentNullException("second");

            return  first.Except(second)
                    .Concat(
                    second.Except(first));
        }

        public static bool Overlaps<T>(
            this IEnumerable<T> first,
            IEnumerable<T> second)
        {
            if (first == null)
                throw new ArgumentNullException("first");

            if (second == null)
                throw new ArgumentNullException("second");

            return second.Intersect(first).Distinct().Any();
        }

        public static bool IsSupersetOf<T>(
            this IEnumerable<T> first, 
            IEnumerable<T> second)
        {
            if (first == null) 
                throw new ArgumentNullException("first");

            if (second == null) 
                throw new ArgumentNullException("second");

            var secondCount = second.Distinct().Count();
            
            var intersectCount = second
                .Intersect(first)
                .Distinct()
                .Count();

            return intersectCount == secondCount;
        }

        public static bool IsProperSupersetOf<T>(
            this IEnumerable<T> first, 
            IEnumerable<T> second)
        {
            if (first == null) 
                throw new ArgumentNullException("first");

            if (second == null) 
                throw new ArgumentNullException("second");

            var firstCount = first.Distinct().Count();
            var secondCount = second.Distinct().Count();
            
            var intersectCount = 
                second
                .Intersect(first)
                .Distinct()
                .Count();

            return (intersectCount < firstCount) && 
                   (intersectCount == secondCount);
        }

        public static bool IsSubsetOf<T>(
            this IEnumerable<T> first, 
            IEnumerable<T> second)
        {
            // wywołaj operator Superset i odwróć kolejność argumentów
            return IsSupersetOf(second, first);
        }

        public static bool IsProperSubsetOf<T>(
            this IEnumerable<T> first, 
            IEnumerable<T> second)
        {
            // wywołaj operator ProperSuperset i odwróć kolejność argumentów
            return IsProperSupersetOf(second, first);
        }

        public static bool SetEquals<T>(
            this IEnumerable<T> first,
            IEnumerable<T> second)
        {
            return first.Distinct().OrderBy(x => x)
                .SequenceEqual(
                second.Distinct().OrderBy(y => y));
        }
    }
}

