//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 Support = SampleSupport;
using QuerySamples;
using System.Xml;
using System.Data.SqlClient;
using NUnit.Framework;
using System.Diagnostics;
using System.Reflection;


namespace SampleQueries {
    [SampleSupport.Title("Rozdział 7 - Tworzenie rozszerzeń LINQ to Objects")]
    [SampleSupport.Prefix("Listing_7_")]
    public class Chapter07Samples : SampleSupport.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" }
                };
            }
        }

        #endregion

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-3 : Operator RandomElement")]
        [SampleSupport.Description("Przykład demonstruje, jak napisać nowy operator pojedynczego elementu. Operator RandomElement zwraca element losowy z sekwencji. Poniższa metoda pokazuje jego zastosowanie.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_3_Extensions")]
        public void Listing_7_3_RandomElement_Operator()
        {
            var contacts = Contact.SampleData();
            Contact con = contacts.RandomElement();
            Console.WriteLine("{0} {1} - {2}",
                con.FirstName,
                con.LastName,
                con.Phone);
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-4 : Operator RandomElement + testy jednostkowe")]
        [SampleSupport.Description("Przykład demonstruje, jak napisać nowy operator pojedynczego elementu. Operator RandomElement zwraca element losowy z sekwencji. Poniższa metoda wykonuje testy jednostkowe operatora.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_3_Extensions")]
        [SampleSupport.LinkedClass("SampleQueries.RandomElementUnitTests")]
        public void Listing_7_4_RandomElement_Operator_UnitTests()
        {
            RunUnitTestFixture("SampleQueries.RandomElementUnitTests");
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-4 do 7-7 : Operator TakeRange operator + testy jednostkowe")]
        [SampleSupport.Description("Przykład demonstruje,  jak napisać operator sekwencji. Operator TakeRange zwraca wszystkie wartości od pierwszej spełniającej warunki predykatu początkowego. Poniższa metoda wykonuje testy jednostkowe operatora.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_4_to_7_6_Extensions")]
        [SampleSupport.LinkedClass("SampleQueries.TakeRangeUnitTests")]
        public void Listing_7_7_TakeRange_Operator_UnitTests()
        {
            RunUnitTestFixture("SampleQueries.TakeRangeUnitTests");
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-9 : Operator LongSum + testy jednostkowe")]
        [SampleSupport.Description("Przykład demonstruje,  how to write a Aggregate operator. Operator LongSum zwraca sumę liczb całkowitych jako typ long. Poniższa metoda wykonuje testy jednostkowe operatora.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_8_Extensions")]
        [SampleSupport.LinkedClass("SampleQueries.LongSumUnitTests")]
        public void Listing_7_9_LongSum_Operator_UnitTests()
        {
            RunUnitTestFixture("SampleQueries.LongSumUnitTests");
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Operator Segment - przykład 1")]
        [SampleSupport.Description(".")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_6_Extensions")]
        public void Listing_7_x_Segment_Operator_Sample_1()
        {
            string[] elements = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };

            var groups = elements.Segment(5);

            foreach (var g in groups)
            {
                Console.WriteLine("Klucz grupy: {0}", g.Key);
                foreach (var elm in g)
                    Console.WriteLine("  {0}", elm);
            }
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Operator Segment - przykład 1")]
        [SampleSupport.Description(".")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_6_Extensions")]
        public void Listing_7_x_Segment_Operator_Sample_2()
        {
            string[] elements = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };

            var groups = elements.Segment(4);

            foreach (var g in groups)
            {
                Console.WriteLine("Klucz grupy: {0}", g.Key);
                foreach (var elm in g)
                    Console.WriteLine("  {0}", elm);
            }
        }


        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-16 - Operator Segment dla konkatenowanych rekordów")]
        [SampleSupport.Description("Wywołanie operatora Segment w celu podzielenia listy kontaktów na dwie grupy z konkatenowanych rekordów.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_6_Extensions")]
        public void Listing_7_16_Segment_Over_Contacts()
        {
            List<Contact> contacts = Contact.SampleData();

            // podziel kontakty na dwie grupy w celu przeprowadzenia testu A/B
            var groups = contacts.Segment(2);

            foreach (var g in groups)
            {
                Console.WriteLine("Klucz grupy: {0}", g.Key);
                foreach (var elm in g)
                    Console.WriteLine("  {0}, {1}",
                        elm.LastName.ToUpper(), elm.FirstName);
            }
        }

        [SampleSupport.Category("Pisanie nowych operatorów")]
        [SampleSupport.Title("Listing 7-12 : Operator Segment + testy jednostkowe")]
        [SampleSupport.Description("Przykład demonstruje,  jak napisać operator grupowania. Operator Segment zwraca liczbę grup (argument segments), wypełnionych równą liczbą elementów (tam, gdzie to możliwe). Poniższa metoda wykonuje testy jednostkowe operatora.")]
        [SampleSupport.LinkedClass("SampleQueries.Listing_7_11_Extensions")]
        [SampleSupport.LinkedClass("SampleQueries.SegmentUnitTests")]
        public void Listing_7_12_Segment_Operator_UnitTests()
        {
            RunUnitTestFixture("SampleQueries.SegmentUnitTests");
        }

    }

    #region Ostatni przykład - Listing 7-1 i Listing 7-2

    public static class LastExample
    {
         public static T Last_A<T>(
            this IEnumerable<T> source)
        {
            using (IEnumerator<T> enumerator = source.GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    T current;
                    do
                    {
                        current = enumerator.Current;
                    }
                    while (enumerator.MoveNext());

                    return current;
                }
            }

            throw new InvalidOperationException("Brak elementów");
        }

        public static T Last_B<T>(
            this IEnumerable<T> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            using (IEnumerator<T> enumerator = source.GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    T current;
                    do
                    {
                        current = enumerator.Current;
                    }
                    while (enumerator.MoveNext());

                    return current;
                }
            }

            throw new InvalidOperationException("Brak elementów");
        }

        public static T Last_C<T>(
            this IEnumerable<T> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            IList<T> list = source as IList<T>;
            if (list != null)
            {
                int count = list.Count;
                if (count > 0)
                    return list[count - 1];
            }
            else
            {
                using (IEnumerator<T> enumerator = 
                    source.GetEnumerator())
                {
                    if (enumerator.MoveNext())
                    {
                        T current;
                        do
                        {
                            current = enumerator.Current;
                        }
                        while (enumerator.MoveNext());
                        
                        return current;
                    }
                }
            }

            throw new InvalidOperationException("Brak elementów");
        }

        public static T LastOrDefault<T>(
            this IEnumerable<T> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            IList<T> list = source as IList<T>;
            if (list != null)
            {
                int count = list.Count;
                if (count > 0)
                    return list[count - 1];
            }
            else
            {
                using (IEnumerator<T> enumerator =
                    source.GetEnumerator())
                {
                    if (enumerator.MoveNext())
                    {
                        T current;
                        do
                        {
                            current = enumerator.Current;
                        }
                        while (enumerator.MoveNext());

                        return current;
                    }
                }
            }

            return default(T);
        }
    }

    #endregion // Ostatni przykład

    #region Random Element Sample - Listing 7-3

    // Listing 7-3
    public static class Listing_7_3_Extensions
    {
        public static T RandomElement<T>(
            this IEnumerable<T> source,
            int seed = 0 /* parametr opcjonalny, nowość w C# 4.0 */ )
        {
            // sprawdzenie prawidłowości kolekcji źródłowej
            if (source == null) 
                throw new ArgumentNullException("source");

            // metoda rozszerzenia Count() posiada już wbudowany
            // ten mechanizm optymalizacji. Pokazuję go tutaj,
            // aby zwrócić na niego uwagę!
    
            int count = 0;
            if (source is ICollection)
                count = ((ICollection)source).Count;
            else
                count = source.Count();

            if (count == 0)
                throw new InvalidOperationException("Brak elementów");

            // użyj przekazanej wartości ziarna lub ziarna opartego na pomiarze czasu
            Random random;
            if (seed == 0)
                random = new Random();
            else
                random = new Random(seed);


            // IList implementuje dostęp za pomocą indeksera, który jest szybszy.
            IList<T> list = source as IList<T>;
            if (list != null)
            {
                int index = random.Next(0, count);
                return list[index];
            }
            else
            {
                // Inne typy kolekcji (np. Dictionary, IEnumerable)
                // muszą być enumerowane.
                int index = random.Next(0, count);
                using (IEnumerator<T> e = source.GetEnumerator())
                {
                    // przejdź do pierwszego elementu
                    // (początkowa pozycja znajduje się
                    // PRZED elementem).
                    e.MoveNext();

                    // iteruj i przesuwaj się, aż dojedziesz
                    // do naszego elementu
                    while (index > 0)
                    {
                        e.MoveNext();
                        index--;
                    }

                    return e.Current;
                }
            }
        }

        public static T RandomElementOrDefault<T>(
            this IEnumerable<T> source)
        {
            // sprawdź prawidłowość warunków źródła
            if (source == null)
                throw new ArgumentNullException("source");

            // Metoda rozszerzenia Count() ma już wbudowaną tę
            // optymalizację. Pokazuję ją tutaj, aby podkreślić, 
            // że należy brać ją pod uwagę.
            int count = 0;
            if (source is ICollection)
                count = ((ICollection)source).Count;
            else
                count = source.Count();

            if (count == 0)
                return default(T);

            //... tak samo jak w kodzie operatora RandomElement. 

            return default(T);
        }
    }

    // Listing 7-2
    [TestFixture]
    public class RandomElementUnitTests
    {
        //3456789|123456789|123456789|123456789|123456789|12345678  
        [Test]
        [ExpectedException("System.ArgumentNullException")]
        public void NullSourceTest()
        {
            int[] values = null;
            int result = values.RandomElement();
        }

        [Test]
        [ExpectedException("System.InvalidOperationException")]
        public void EmptySourceTest()
        {
            var values = Enumerable.Empty<int>();
            int result = values.RandomElement();
        }

        [Test]
        public void IEnumerableTest()
        {
            // utwórz testową strukturę IEnumerable
            // zawierającą wartości typu int z przedziału 0-999
            var values = Enumerable.Range(0, 1000);
            int result1 = values.RandomElement(1);

            // inne losowe ziarno klasy 
            // Random class seed is different
            var values2 = Enumerable.Range(0, 1000);
            int result2 = values2.RandomElement(2);

            // upewnij się, że zwracany jest inny wynik
            // operator może zwrócić ten sam element, ale 
            // szansa wynosi tylko 1 do 1000.
            Assert.AreNotSame(result1, result2);
        }

        [Test]
        public void IListTest()
        {
            // utwórz testową tablicę
            // zawierającą wartości typu int z przedziału 0-999
            var values = Enumerable.Range(0, 1000).ToArray();
            int result1 = values.RandomElement(1);

            // użyj innego losowego ziarna
            int result2 = values.RandomElement(2);

            // operator może zwrócić ten sam element, ale 
            // szansa wynosi tylko 1 do 1000.
            Assert.AreNotSame(result1, result2);
        }

        [Test]
        public void SingleElementTest()
        {
            // dla źródła złożonego tylko z jednego 
            // elementu, ten element powinien być
            // zwracany za każdym razem

            // IEnumerable<T>
            var values = Enumerable.Range(10, 1);
            int result1 = values.RandomElement(1);
            Assert.AreEqual(10, result1);

            // IList<T>
            var values2 = values.ToArray();
            int result2 = values.RandomElement(2);
            Assert.AreEqual(10, result2);
        }

        [Test]
        public void BoundaryEnumerableTest()
        {
            // dla źródła złożonego z dwóch elementów
            // sprawdzamy, czy elementy krańcowe
            // mogą zostać zwrócone
            var values = Enumerable.Range(10, 2);

            // sprawdź, czy oba wyniki są zwracane
            // w przynajmniej 25 próbach za pomocą
            // różnych ziaren generatora liczb
            // losowych.
            int result1 = values.RandomElement();
            bool foundDifferent = false;
            for (int i = 0; i < 25; i++)
            {
                int result2 = values.RandomElement(i+100);

                if (result1 != result2)
                {
                    foundDifferent = true;
                    break;
                }
            }
            Assert.IsTrue(foundDifferent);
        }

        [Test]
        public void BoundaryIListTest()
        {
            // dla źródła złożonego z dwóch elementów
            // sprawdzamy, czy elementy krańcowe
            // mogą zostać zwrócone
            var values = Enumerable.Range(10, 2).ToArray();

            // sprawdź, czy oba wyniki są zwracane
            // przynajmniej jeden raz
            // w 25 próbach za pomocą
            // różnych ziaren generatora liczb
            // losowych.
            int result1 = values.RandomElement();
            bool foundDifferent = false;
            for (int i = 0; i < 25; i++)
            {
                int result2 = values.RandomElement(i+100);

                if (result1 != result2)
                {
                    foundDifferent = true;
                    break;
                }
            }
            Assert.IsTrue(foundDifferent);
        }
    }

    #endregion // Próbka RandomElement

    #region TakeRange Operator

    public static class Listing_7_4_to_7_6_Extensions
    {
        private static IEnumerable<T> TakeRangeIterator<T>(
            this IEnumerable<T> source,
            Func<T, bool> startPredicate,
            Func<T, bool> endPredicate)
        {
            // sprawdź prawidłowość argumentów wejściowych
            if (source == null)
                throw new ArgumentNullException("source");

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

            if (endPredicate == null)
                throw new ArgumentNullException("endPredicate");
            
            // rozpocznij iterację
            bool foundStart = false;
            
            foreach (T element in source)
            {
                if (startPredicate(element))
                    foundStart = true;

                if (foundStart)
                    if (endPredicate(element))
                        yield break;
                    else
                        yield return element;
            }
        }

        public static IEnumerable<T> TakeRange<T>(
            this IEnumerable<T> source,
            Func<T, bool> endPredicate)
        {

            return TakeRangeIterator<T>(
                source, b => true, endPredicate);
        }

        public static IEnumerable<T> TakeRange<T>(
            this IEnumerable<T> source,
            Func<T, bool> startPredicate,
            Func<T, bool> endPredicate)
        {

            return TakeRangeIterator<T>(
                source, startPredicate, endPredicate);
        }
    }

    [TestFixture]
    public class TakeRangeUnitTests
    {
        //3456789|123456789|123456789|123456789|123456789|12345678  
        [Test]
        [ExpectedException("System.ArgumentNullException")]
        public void NullSourceTest()
        {
            int[] values = null;
            var result = values.TakeRange(
                start => true, end => true).ToArray<int>();
        }

        [Test]
        [ExpectedException("System.ArgumentNullException")]
        public void NullStartPredicateTest()
        {
            int[] values = null;
            int[] result = values.TakeRange(
                null, b => true).ToArray<int>();
        }

        [Test]
        [ExpectedException("System.ArgumentNullException")]
        public void NullEndPredicateTest()
        {
            int[] values = null;
            int[] result = values.TakeRange(
                a => true, null).ToArray<int>();
        }

        [Test]
        public void IntArrayTest()
        {
            var values = Enumerable.Range(0, 20);

            int[] result = values.TakeRange(
                     start => start > 4, 
                     end => end > 9).ToArray<int>();

            Assert.AreEqual(
                5, result.Count(), "zwrócono niepoprawną liczbę elementów");

            // spodziewamy się, że zostaną zwrócone 5,6,7,8,9 
            for (int i = 0; i < result.Count(); i++)
                Assert.AreEqual(i + 5, result[i]);
        }

        [Test]
        public void IntArrayEndPredicateOnlyTest()
        {
            var values = Enumerable.Range(0, 20);

            int[] result = values.TakeRange(
                     end => end > 9).ToArray<int>();

            Assert.AreEqual(
                10, result.Count(), "zwrócono niepoprawną liczbę elementów");

            // spodziewamy się, że zostaną zwrócone 10-19
            for (int i = 10; i < result.Count(); i++)
                Assert.AreEqual(i, result[i]);
        }
    }

    #endregion // Operator TakeRange 

    #region Min Example
    public static class MinExample
    {
        //3456789|123456789|123456789|123456789|123456789|12345678  
        public static int Min(
            this IEnumerable<int> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            int num = 0;
            bool flag = false;
            foreach (int num2 in source)
            {
                if (flag)
                {
                    if (num2 < num)
                        num = num2;
                }
                else
                {
                    num = num2;
                    flag = true;
                }
            }

            if (!flag)
                throw new 
                    InvalidOperationException("Brak elementów");
            
            return num;
        }

        /* Pełny zestaw przeciążeń operatora Min

        decimal Min(this IEnumerable<decimal> source);
        double Min(this IEnumerable<double> source);
        decimal? Min(this IEnumerable<decimal?> source);
        double? Min(this IEnumerable<double?> source);
        int? Min(this IEnumerable<int?> source);
        long? Min(this IEnumerable<long?> source);
        float? Min(this IEnumerable<float?> source);
        int Min(this IEnumerable<int> source);
        long Min(this IEnumerable<long> source);
        float Min(this IEnumerable<float> source);
        TSource Min<TSource>(this IEnumerable<TSource> source);

        decimal Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, decimal> selector);
        double Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, double> selector);
        long? Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, long?> selector);
        int Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, int> selector);
        double? Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, double?> selector);
        float? Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, float?> selector);
        long Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, long> selector);
        decimal? Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, decimal?> selector);
        int? Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, int?> selector);
        float Min<TSource>(this IEnumerable<TSource> source, 
            Func<TSource, float> selector);
        TResult Min<TSource, TResult>(this IEnumerable<TSource> source, 
            Func<TSource, TResult> selector);
         */
 
    }

    #endregion // Przykład operatora Min

    #region LongSum Operator

    public static class Listing_7_8_Extensions
    {
        public static long LongSum(
            this IEnumerable<int> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            long sum = 0;
            checked
            {
                foreach (int v in source) 
                    sum += v;
            }

            return sum;
        }

        public static long LongSum<T>(
            this IEnumerable<T> source,
            Func<T, int> selector)
        {
            return source
                   .Select(selector)
                   .LongSum();
        }
    }


    [TestFixture]
    public class LongSumUnitTests
    {
        [Test]
        public void LongSumIntTest()
        {
            int[] values = new int[] { 5, 4, 3, 2, 1, 6, 7, 8, 9, 0 };
            long result = values.LongSum();
            Assert.AreEqual(45, result, "Niepoprawny wynik działania LongSum.");
        }

        [Test]
        public void LongSumNegativeIntTest()
        {
            int[] values = new int[] { int.MinValue, int.MaxValue, 1 };
            long result = values.LongSum();
            Assert.AreEqual(0, result, "Wartość ujemna, Niepoprawny wynik działania LongSum.");
        }
        
        [Test]
        public void LongSumIntBigTest()
        {
            int[] values = new int[] { int.MaxValue, 1, int.MaxValue };
            long result = values.LongSum();
            long correct = (long)int.MaxValue + (long)int.MaxValue + 1;
            Assert.AreEqual(correct, result);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void LongSumIntNullSourceTest()
        {
            int[] values = null;
            long result = values.LongSum();
        }

        [Test]
        public void LongSumIntEmptySourceTest()
        {
            var values = Enumerable.Empty<int>();
            long result = values.LongSum();
            Assert.AreEqual(0, result);
        }

        [Test]
        public void LongSumIntWithSelectorTest()
        {
            int[] values = new int[] { 5, 4, 3, 2, 1, 6, 7, 8, 9, 0 };
            long result = values.LongSum(i => i + 1);
            Assert.AreEqual(55, result);
        }

    }

    #endregion // operator LongSum 

    #region Segment Operator

    public static class Listing_7_11_Extensions
    {
        public static IEnumerable<IGrouping<int, T>> Segment<T>(
            this IEnumerable<T> source,
            int segments)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (segments <= 0)
                throw new ArgumentOutOfRangeException("segments");

            return SegmentIterator<T>(source, segments);
        }

        private static IEnumerable<IGrouping<int, T>> 
            SegmentIterator<T>(
                IEnumerable<T> source,
                int segments)
        {
            // oblicz liczbę elementów w segmencie
            int count = source.Count();
            int perSegment = (int)Math.Ceiling(
                (decimal)count / segments);

            // utwórz puste grupy
            Grouping<int, T>[] groups =
                new Grouping<int, T>[segments];

            for (int i = 0; i < segments; i++)
            {
                Grouping<int, T> g =
                    new Grouping<int, T>(perSegment);

                g.key = i + 1;
                groups[i] = g;
            }

            // wypełnij grupy wynikami otrzymanymi instruckją yield
            // kiedy uzyskasz wystarczającą do wypełnienia liczbę elementów.
            int index = 0;
            int segment = 1;
            Grouping<int, T> group = groups[0];
            using (IEnumerator<T> e = source.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    group.Add(e.Current);
                    index++;

                    // zwróć instrukcją yield, kiedy wypełnisz każdą grupę
                    if ((segment < segments) && 
                        (index == perSegment))
                    {
                        yield return group;
                        index = 0;
                        segment++;
                        group = groups[segment - 1];
                    }
                }
            }

            // zwróć ostatnią i wszelkie pozostałe grupy
            // (będą puste lub wypełnione częściowo)
            while (segment <= segments)
            {
                yield return groups[segment - 1];
                segment++;
            }
        }
    }

    public class Grouping<K, T> : IGrouping<K, T>, IList<T>
    {
        internal K key;
        internal T[] elements;
        internal int count;

        public Grouping(int count)
        {
            elements = new T[count];
        }

        internal void Add(T element)
        {
            if (elements.Length == count)
                Array.Resize(ref elements, count * 2);

            elements[count++] = element;
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < count; i++)
                yield return elements[i];
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        K IGrouping<K, T>.Key
        {
            get { return key; }
        }

        int ICollection<T>.Count
        {
            get { return count; }
        }

        bool ICollection<T>.IsReadOnly
        {
            get { return true; }
        }

        void ICollection<T>.Add(T item)
        {
            throw new NotSupportedException();
        }

        void ICollection<T>.Clear()
        {
            throw new NotSupportedException();
        }

        bool ICollection<T>.Contains(T item)
        {
            return
                Array.IndexOf(elements, item, 0, count) >= 0;
        }

        void ICollection<T>.CopyTo(T[] array, int arrayIndex)
        {
            Array.Copy(elements, 0, array, arrayIndex, count);
        }

        bool ICollection<T>.Remove(T item)
        {
            throw new NotSupportedException();
        }

        int IList<T>.IndexOf(T item)
        {
            return
                Array.IndexOf(elements, item, 0, count);
        }

        void IList<T>.Insert(int index, T item)
        {
            throw new NotSupportedException();
        }

        void IList<T>.RemoveAt(int index)
        {
            throw new NotSupportedException();
        }

        T IList<T>.this[int index]
        {
            get
            {
                if (index < 0 || index >= count)
                    throw new
                        ArgumentOutOfRangeException("index");

                return elements[index];
            }
            set
            {
                throw new NotSupportedException();
            }
        }
    }

    [TestFixture]
    public class SegmentUnitTests
    {
        [Test]
        [ExpectedException("System.ArgumentNullException")]
        public void NullSourceTest()
        {
            int[] values = null;
            var result = values.Segment(1);
        }

        [Test]
        public void EmptySourceTest()
        {
            // spodziewamy się 4 pustych grup
            int[] values = Enumerable.Empty<int>().ToArray();
            var result = values.Segment(4);
            Assert.AreEqual(4, result.Count());
        }

        [Test]
        public void EvenSegmentTest()
        {
            int[] values = 
                Enumerable.Range(1, 100).ToArray<int>();

            var result = values.Segment(4);
            Assert.AreEqual(4, result.Count());

            foreach (var g in result)
                Assert.AreEqual(25, g.Count());
        }

        [Test]
        public void MoreSegmentsThanElementsTest()
        {
            int[] values = Enumerable.Range(1, 3).ToArray<int>();

            var result = values.Segment(10);

            Assert.AreEqual(10, result.Count());
            int i = 1;
            foreach (var g in result)
            {
                if (i < 4)
                    Assert.AreEqual(1, g.Count());
                else
                    Assert.AreEqual(0, g.Count());

                i++;
            }
        }

        [Test]
        public void OddSegmentTest()
        {
            int[] values = 
                Enumerable.Range(1, 101).ToArray<int>();

            var result = values.Segment(4);
            Assert.AreEqual(4, result.Count());

            int i = 1;
            foreach (var g in result)
            {
                if (i < 4)
                    Assert.AreEqual(26, g.Count());
                else
                    Assert.AreEqual(23, g.Count());

                i++;
            }
        }

    }

    #endregion // Operator Segment 

}
