using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Xunit;

using LinqStudy;

namespace LinqStudy.Test.LinqToObject
{
    /// <summary>
    /// 投影操作符
    /// 投影:遍历序列中的元素,将元素由一种类型转换为另一种类型的操作,并返回由转换后元素组成的新序列。
    /// 即是:IEnumerable<TSource> => IEnumerable<TResult>
    /// </summary>
    public class ProjectiveTest
    {
        #region Select

        /// <summary>
        /// Select投影:简单投影,一对一
        /// </summary>
        [Fact]
        public void Select_Test()
        {
            // Arrange
            var persons = PersonManager.GetPersons();

            // Act
            var maps = persons.Select(p => p.Age).ToList();

            // Assert
            Assert.IsType<List<int>>(maps);
        }

        /// <summary>
        /// 投影为匿名类
        /// </summary>
        [Fact]
        public void Select_Anonymous_Test()
        {
            // Arrange
            var persons = PersonManager.GetPersons();

            // Act
            var maps = persons.Select(p => new { Id = p.Id, Node = $"姓名{p.Name},年龄{p.Age}." }).ToList();

            // Assert
            Assert.IsNotType<List<Person>>(maps);
        }

        /// <summary>
        /// 投影传入索引(序号)参数
        /// </summary>
        [Fact]
        public void Select_Index_Test()
        {
            var persons = PersonManager.GetPersons();

            var maps = persons.Select((query, index) => new KeyValuePair<int, Person>(index, query)).ToList();
            var indexs = persons.Select((query, index) => index).ToList();

            Assert.IsType<List<KeyValuePair<int, Person>>>(maps);
            Assert.Equal(0, indexs[0]);
        }
        #endregion

        #region SelectMany

        /// <summary>
        ///  将序列的每个元素投影到 IEnumerable<TResult> 并将结果序列合并为一个序列。
        /// </summary>
        /// <remarks>
        ///  SelectMany:复合投影,一对多,合并多到一个集合。
        ///  枚举源序列,将源序列每一项投影为新的集合,合并所有新集合为一个可枚举序列,做为返回值;
        ///  提供了将多个 from子句组合起来的功能,它将每个对象的结果合并成单个可枚举序列。
        /// </remarks>
        [Fact]
        public void SelectMany_Test()
        {
            var employees = new List<Employee>()
            {
                new Employee(){Id=1,Name="小明",Emails=new List<string>(){ "abc@163.com", "acd@163.com", "ade@163.com" } },
                new Employee(){Id=2,Name="大壮",Emails=new List<string>(){ "bbc@163.com", "bcd@163.com", "bde@163.com" } },
                new Employee(){Id=3,Name="周羊",Emails=new List<string>(){ "cbc@163.com", "ccd@163.com", "cde@163.com" } },
                new Employee(){Id=4,Name="承承",Emails=new List<string>(){ "dbc@163.com", "dcd@163.com", "dde@163.com" } },
                new Employee(){Id=5,Name="东升",Emails=new List<string>(){ "ebc@163.com", "ecd@163.com", "ede@163.com" }},
            };

            var maps = employees.SelectMany(q => q.Emails).ToList();

            Assert.IsType<List<string>>(maps);
            Assert.Equal(15, maps.Count);
        }

        /// <summary>
        ///  将序列的每个元素投影到 IEnumerable<TResult>,并将结果序列合并为一个序列。每个源元素的索引用于该元素的投影表。
        /// </summary>
        /// <remarks>
        ///  添加一个对源枚举的索引而已。索引从0开始,最大为TSource.Count-1
        /// </remarks>
        [Fact]
        public void SelectMany_Index_Test()
        {
            var employees = new List<Employee>()
            {
                new Employee(){Id=1,Name="小明",Emails=new List<string>(){ "abc@163.com", "acd@163.com", "ade@163.com" } },
                new Employee(){Id=2,Name="大壮",Emails=new List<string>(){ "bbc@163.com", "bcd@163.com", "bde@163.com" } },
                new Employee(){Id=3,Name="周羊",Emails=new List<string>(){ "cbc@163.com", "ccd@163.com", "cde@163.com" } },
                new Employee(){Id=4,Name="承承",Emails=new List<string>(){ "dbc@163.com", "dcd@163.com", "dde@163.com" } },
                new Employee(){Id=5,Name="东升",Emails=new List<string>(){ "ebc@163.com", "ecd@163.com", "ede@163.com" }},
            };

            var maps = employees.SelectMany((q, idx) =>
            {
                q.Emails.Add(idx.ToString());
                return q.Emails;
            }).ToList();

            Assert.IsType<List<string>>(maps);
            Assert.Equal(20, maps.Count);
        }

        /// <summary>
        /// 将序列的每个元素投影到 IEnumerable<TCollection>,并将结果序列合并为一个序列,并对其中每个元素调用结果选择器函数。
        /// </summary>
        /// <remarks>
        /// 自定义结果项,类似Cross JOIN
        /// </remarks>
        [Fact]
        public void SelectMany_TCollection_Test()
        {
            var employees = new List<Employee>()
            {
                new Employee(){ Id=1,Name="小明",Emails=new List<string>(){ "a1@163.com", "a2@163.com", "a3@163.com" } },
                new Employee(){ Id=2,Name="大壮",Emails=new List<string>(){ "b1@163.com", "b2@163.com", "b3@163.com" } },
                new Employee(){ Id=3,Name="周羊",Emails=new List<string>(){ "c1@163.com", "c2@163.com", "c3@163.com" } },
                new Employee(){ Id=4,Name="承承",Emails=new List<string>(){ "d1@163.com", "d2@163.com", "d3@163.com" } },
                new Employee(){ Id=5,Name="东升",Emails=new List<string>(){ "e1@163.com", "e2@163.com", "e3@163.com" } },
            };

            var maps = employees.SelectMany((employee) => employee.Emails, (person, email) => new { Name = person.Name, Email = email }).ToList();

            Assert.Equal(15, maps.Count);
        }

        /// <summary>
        /// 将序列的每个元素投影到 IEnumerable<TCollection>,并将结果序列合并为一个序列,并对其中每个元素调用结果选择器函数。
        /// 每个源元素的索引用于该元素的中间投影表。
        /// </summary>
        /// <remark>
        ///  自定义结果项,类似Cross JOIN
        /// </remark>
        [Fact]
        public void SelectMany_TCollection_Index_Test()
        {
            var employees = new List<Employee>()
            {
                new Employee(){ Id=1,Name="小明",Emails=new List<string>(){ "a1@163.com", "a2@163.com", "a3@163.com" } },
                new Employee(){ Id=2,Name="大壮",Emails=new List<string>(){ "b1@163.com", "b2@163.com", "b3@163.com" } },
                new Employee(){ Id=3,Name="周羊",Emails=new List<string>(){ "c1@163.com", "c2@163.com", "c3@163.com" } },
                new Employee(){ Id=4,Name="承承",Emails=new List<string>(){ "d1@163.com", "d2@163.com", "d3@163.com" } },
                new Employee(){ Id=5,Name="东升",Emails=new List<string>(){ "e1@163.com", "e2@163.com", "e3@163.com" } },
            };

            var maps = employees.SelectMany
            (
                (employee, idx) =>
                {
                    return new List<Employee>() { new Employee() { Id = employee.Id, Name = employee.Name + "_" + idx, Emails = employee.Emails } };
                },

                (person, email) => new
                {
                    Name = email.Name,
                    Email = email.Emails
                }
            )
            .ToList();

            Assert.Equal(5, maps.Count);
        }
        #endregion
    }
}