//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;

namespace SampleQueries {
    [Title("Rozdział 1 - Wprowadzenie do LINQ")]
    [Prefix("Listing_1_")]
    public class Chapter01Samples : SampleHarness
    {
        // Ciąg opisujący połączenie LINQ to SQL - w razie potrzeby zmodyfikuj odpowiednio do lokalnej bazy Northwind
        private readonly static string dbPath = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\Data\NORTHWND.MDF"));
        private readonly static string sqlServerInstance = @".\SQLEXPRESS";
        private readonly static string connString = "AttachDBFileName='" + dbPath + "';Server='" + sqlServerInstance + "';user instance=true;Integrated Security=SSPI;Connection Timeout=60";

        // przykładowe dane dla grupowania i sortowania LINQ to Objects        
        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) }
                };
            }
        }

        [Category("Grupowanie i sortowanie rekordów kontaktów")]
        [Title("Listing 1-1 : Kod C# 2.0 grupujący i sortujący rekordy kontaktów")]
        [Description("Ten przykład pokazuje wykorzystanie kodu C#2.0 do pogrupowania i posortowania rekordów listy List<Contact>.")]
        [LinkedClass("Contact")]
        public void Listing_1_1()
        {
            List<Contact> contacts = Contact.SampleData();

            // sortowanie według nazwisk
            contacts.Sort(
                delegate(Contact c1, Contact c2)
                {
                    if (c1 != null && c2 != null)
                        return string.Compare(
                            c1.LastName, c2.LastName);
                    
                    return 0;
                }
            );

            // grupowanie według województw (za pomocą posortowanego słownika)
            SortedDictionary<string, List<Contact>> groups =
                new SortedDictionary<string, List<Contact>>();

            foreach (Contact c in contacts)
            {
                if (groups.ContainsKey(c.State))
                {
                    groups[c.State].Add(c);
                }
                else
                {
                    List<Contact> list = new List<Contact>();
                    list.Add(c);
                    groups.Add(c.State, list);
                }
            }

            // wyświetlanie wyników
            foreach (KeyValuePair<string, List<Contact>> 
                group in groups)
            {
                Console.WriteLine("Województwo: " + group.Key);
                foreach (Contact c in group.Value)
                    Console.WriteLine(" {0} {1}", c.FirstName, c.LastName);
            }
        }

        [Category("Grupowanie i sortowanie rekordów kontaktów")]
        [Title("Listing 1-2 : Kod LINQ to Objects w C# 3.0 służący do grupowania i sortowania rekordów kontaktów")]
        [Description("Ten przykład pokazuje wykorzystanie kodu LINQ to Objects C#3.0 do pogrupowania i posortowania rekordów listy List<Contact>.")]
        [LinkedClass("Contact")]
        public void Listing_1_2()
        {
            List<Contact> contacts = Contact.SampleData();

            // wykonywanie zapytania LINQ
            var query = from c in contacts
                        orderby c.State, c.LastName
                        group c by c.State;

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

        [Category("Sumowanie danych w postaci XML")]
        [Title("Listing 1-3 : Kod C# 2.0 sumujący dane i złączający je z drugą kolekcją, a następnie zapisujący wyniki do XML")]
        [Description("Kod C# 2.0 sumujący dane i złączający je z drugą kolekcją, a następnie zapisujący wyniki do XML.")]
        public void Listing_1_3_SummarizingDataFromMultipleCollections()
        {
            List<Contact> contacts = Contact.SampleData();
            List<CallLog> callLog = CallLog.SampleData();

            // grupowanie połączeń przychodzących wegług numerów telefonów
            Dictionary<string, List<CallLog>> callGroups 
                = new Dictionary<string, List<CallLog>>();

            foreach (CallLog call in callLog)
            {
                if (callGroups.ContainsKey(call.Number))
                {
                    if (call.Incoming == true)
                        callGroups[call.Number].Add(call);
                }
                else
                {
                    if (call.Incoming == true)
                    {
                        List<CallLog> list = new List<CallLog>();
                        list.Add(call);
                        callGroups.Add(call.Number, list);
                    }
                }
            }

            // sortowanie kontaktów według nazwiska, a następnie imienia
            contacts.Sort(
                delegate(Contact c1, Contact c2)
                {
                    // porównywanie nazwisk
                    int result = c1.LastName.CompareTo(c2.LastName);

                    // jeśli nazwiska się zgadzają, porównywanie imion
                    if (result == 0)
                        result = c1.FirstName.CompareTo(c2.FirstName);

                    return result;
                });

            // przygotowanie i zapisywanie dokumentu XML
            using (StringWriter writer = new StringWriter())
            {
                using (XmlTextWriter doc = new XmlTextWriter(writer))
                {
                    // przygotowanie nagłówków XML
                    doc.Formatting = Formatting.Indented;
                    doc.WriteComment("Zsumowane dane połączeń przychodzących");
                    doc.WriteStartElement("contacts");

                    // łączenie połączeń z danymi kontaktow
                    foreach (Contact con in contacts)
                    {
                        if (callGroups.ContainsKey(con.Phone))
                        {
                            List<CallLog> calls = callGroups[con.Phone];
                        
                            // obliczanie całkowitego i średniego czasu trwania
                            long sum = 0;
                            foreach (CallLog call in calls)
                                sum += call.Duration;

                            double avg = (double)sum / (double)calls.Count;

                            // zapisywanie rekordu XML dla tego kontaktu
                            doc.WriteStartElement("contact");
                            
                            doc.WriteElementString("lastName", 
                                con.LastName);
                            doc.WriteElementString("firstName", 
                                con.FirstName);
                            doc.WriteElementString("count", 
                                calls.Count.ToString());
                            doc.WriteElementString("totalDuration", 
                                sum.ToString());
                            doc.WriteElementString("averageDuration", 
                                avg.ToString());
                            
                            doc.WriteEndElement();
                        }
                    }

                    doc.WriteEndElement();
                    doc.Flush();
                    doc.Close();
                }

                Console.WriteLine(writer.ToString());
            }
        }

        [Category("Sumowanie danych w postaci XML")]
        [Title("Listing 1-3 : Kod C# 3.0 LINQ to Objects sumujący dane i złączający je z drugą kolekcją, a następnie zapisujący wyniki do XML")]
        [Description("Kod C# 3.0 LINQ to Objects sumujący dane i złączający je z drugą kolekcją, a następnie zapisujący wyniki do XML.")]
        public void Listing_1_4_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.LastName, contact.FirstName
                    select new XElement("contact",
                             new XElement("lastName",
                                 contact.LastName),
                             new XElement("firstName",
                                 contact.FirstName),
                             new XElement("count",
                                  g.Count()),
                             new XElement("totalDuration",
                                 g.Sum(c => c.Duration)),
                             new XElement("averageDuration",
                                g.Average(c => c.Duration))
                                );

            //tworzenie dokumentu XML i dodawanie elementów w zapytaniu q
            //(każde źródło IEnumerable spowoduje, że element zostanie 
            //zapisany dla każdej pozycji w źródle (każdego kontaktu).
            XDocument doc = new XDocument(
                new XComment("Zsumowane dane połączeń przychodzących"),
                new XElement("contacts", q)
            );

            Console.WriteLine(doc.ToString());
        }

        [Category("Tworzenie kanału RSS danych zamówień z serwera SQL")]
        [Title("Listing 1 : Tradycyjny kod C# 2.0 ADO.Net i XML.")]
        [Description("Ten przykład pokazuje użycie kodu ADO.Net i XML do stworzenia ciągu RSS z danych zamówień. Przykład nie został opisany w książce.")]
        public void Listing_1_ADONet_RSS_Example()
        {
            // w tym przykładzie używamy procedur przechowywanych zamiast SQL
            // jednak użycie PP jest właściwym sposobem - wymaga zmiany tylko kilku linii!
            const string sql =
                @"SELECT TOP (5) o.OrderID, o.OrderDate, c.ContactName 
                              FROM Orders AS o 
                                INNER JOIN Customers AS c ON 
                                    o.CustomerID = c.CustomerID 
                              ORDER BY o.OrderDate DESC";

            const string sqlDetails =
                @"SELECT od.Quantity, p.ProductName 
                              FROM [Order Details] AS od 
                                INNER JOIN Products AS p ON 
                                    od.ProductID = p.ProductID 
                              WHERE od.OrderID = @orderID";

            using (StringWriter writer = new StringWriter())
            {
                using (XmlTextWriter doc = new XmlTextWriter(writer))
                {
                    // dodawanie elementów nagłówkowych
                    doc.Formatting = Formatting.Indented;
                    doc.WriteStartDocument();
                    doc.WriteComment("Real-Time Orders RSS Feed");
                    doc.WriteStartElement("rss");
                    doc.WriteAttributeString("version", "2.0");
                    doc.WriteStartElement("channel");
                    doc.WriteElementString("title", "Orders RSS Title");
                    doc.WriteElementString("description", "Recent Orders.");
                    doc.WriteElementString("link",
                        "http://HookedOnLinq.com");

                    // tworzenie elementów
                    using (SqlConnection connection =
                        new SqlConnection(connString))
                    {
                        connection.Open();

                        SqlCommand command = new SqlCommand(sql, connection);
                        SqlDataReader reader = command.ExecuteReader();
                        while (reader != null && reader.Read())
                        {
                            doc.WriteStartElement("item");

                            doc.WriteElementString("title",
                                  string.Format("Order {0} from {1}",
                                            reader["OrderID"],
                                            reader["ContactName"]
                                            ));

                            // tworzenie ciągu znaków zawierającego szczegóły zamówienia
                            string detail = "";
                            using (SqlConnection conn2 = new SqlConnection(connString))
                            {
                                conn2.Open();
                                SqlCommand detailcommand = new SqlCommand(sqlDetails, conn2);
                                SqlParameter param = new SqlParameter(
                                    "orderID", reader["OrderID"]);
                                detailcommand.Parameters.Add(param);
                                SqlDataReader details = detailcommand.ExecuteReader();
                                while (details != null && details.Read())
                                {
                                    detail +=
                                        details["Quantity"].ToString() +
                                        " x " + details["ProductName"] + ". ";
                                }
                                details.Close();
                            }

                            doc.WriteElementString("description",
                                detail);

                            doc.WriteElementString("pubdate",
                                ((DateTime.Parse(
                                    reader["OrderDate"].ToString()))
                                .ToUniversalTime())
                                .ToString("u")
                                );

                            doc.WriteElementString("guid",
                                reader["OrderID"].ToString());

                            doc.WriteEndElement();
                        }
                        reader.Close();
                        connection.Close();
                    }

                    doc.WriteEndElement();
                    doc.WriteEndElement();
                    doc.WriteEndDocument();
                    doc.Flush();
                    doc.Close();

                    Console.WriteLine(writer.ToString());
                }
            }
        }

        [Category("Tworzenie kanału RSS danych zamówień z serwera SQL")]
        [Title("Listing 1 : Kod LINQ to SQL and LINQ to XML C# 3.0.")]
        [Description("Ten przykład pokazuje użycie kodu LINQ to SQL i LINQ to XML C# 3.0 do stworzenia ciągu RSS z danych zamówień. Przykład nie został opisany w książce.")]
        public void Listing_1_LINQ_RSS_Example()
        {
            XDocument doc;

            using (NorthwindDataContext db =
                new NorthwindDataContext(connString))
            {
                //db.Log = Console.Out;

                // używanie zapytania LINQ to SQL, tworzenie elementów RSS
                var query = db.Orders
                            .OrderByDescending(order => order.OrderDate)
                            .Take(5)
                            .Select(order =>
                                // ten kod LINQ to XML code tworzy
                                // element dla każdego rekordu.
                                 new XElement("item",
                                     new XElement("title",
                                         string.Format("Zamówienie {0} od {1}",
                                            order.OrderID,
                                            order.Customer.ContactName)
                                     ),
                                     new XElement("description",
                                     // tworzenie ciągu znaków zawierającego szczegóły zamówienia
                                         (from detail in order.Order_Details
                                          select detail.Quantity + " x " +
                                          detail.Product.ProductName + ". ")
                                     ),
                                     new XElement("pubdate",
                                         order.OrderDate ??
                                                DateTime.Now
                                                    .ToUniversalTime()
                                     ),
                                     new XElement("guid",
                                         order.OrderID
                                     )
                                )
                            );

                // przygotowywanie nagłówka dokumentu RSS i dodawanie elementów
                doc = new XDocument(

                    // dodawanie elementów nagłówka
                    new XDeclaration("1.0", "utf-8", "yes"),
                    new XComment("Kanał RSS zamówień"),
                    new XElement("rss",
                        new XAttribute("version", "2.0"),
                        new XElement("channel",
                            new XElement("title", "Zamówienia RSS"),
                            new XElement("description", "Najnowsze zamówienia"),
                            new XElement("link", "http://HookedOnLinq.com"),
                            query)
                        )
                     );
            }

            Console.WriteLine(doc.ToString());
        }
    }
}
