using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using DomainModel;
using PageableTableApp.Controllers;
using System.Web.Mvc;
using Moq;
using System.Web;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;


namespace MvcTableDemo.Tests
{
    [TestFixture]
    public class ItemsListController_MaintainList_Tests
    {
        IPersonsRepository _personsRepository;
        ItemsListController _itemsListController;


        [SetUp]
        public void SetUp ()
        {
            _personsRepository = PersonsRepositoryCreator.CreatePersonsRepository (10);

            _itemsListController = new ItemsListController (_personsRepository);
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Even_PageSizes_From_2_To_10 ()
        {
            SetUpMode (false);

            var pageSize = 4;

            var result = _itemsListController.MaintainList (pageSize, 2, "", "", "", "", new FormCollection());

            Assert.IsNotNull (result, "View is not rendered.");

            var pageSizes = _itemsListController.ViewData ["pageSizes"] as List<SelectListItem>;

            Assert.AreEqual (5, pageSizes.Count, "Not all 5 page sizes were returned.");

            var areAllPageSizesIncluded = true;
            for (var i = 1; i <= 5; ++i)
                if (pageSizes [i - 1].Text != i * 2 + "")
                {
                    areAllPageSizesIncluded = false;

                    break;
                }
                else
                    if (i * 2 != pageSize)
                        Assert.IsFalse (pageSizes [i - 1].Selected, pageSizes [i - 1].Text + " was set as selected.");

            Assert.IsTrue (areAllPageSizesIncluded, "Not all page sizes were returned.");
            Assert.IsTrue (pageSizes [pageSize / 2 - 1].Selected, "Pagesize of " + pageSize + " was not selected.");
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Correct_Number_Of_Persons ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 2, "","","", "", new FormCollection());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.AreEqual (_personsRepository.Persons.Count (), _itemsListController.ViewData ["numberOfRecords"]);
        }

        [Test]
        public void MaintainList_In_Ajax_Mode_Returns_Correct_Number_Of_Persons ()
        {
            SetUpMode (true);

            var result = _itemsListController.MaintainList (4, 2, "", "", "", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":5,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            //Assert.AreEqual (
            //    _personsRepository.Persons.Count (),
            //    (int)jsonResult.Data.GetType ().GetProperty ("NumberOfRecords").GetValue (jsonResult.Data, null));

            Assert.IsTrue (output.Contains ("\"NumberOfRecords\":10"));
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Page_1_Of_3_Of_Size_4 ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            var returnedPersons = _itemsListController.ViewData.Model as IEnumerable<Object>;

            Assert.AreEqual (4, returnedPersons.Count (), "Not all persons were returned.");

            Assert.AreEqual (4, (int)_itemsListController.ViewData ["pageSize"]);
            Assert.AreEqual (3, (int)_itemsListController.ViewData ["pages"]);
            Assert.AreEqual (1, (int)_itemsListController.ViewData ["page"]);
        }

        [Test]
        public void MaintainList_Ajax_Mode_Returns_Page_1_Of_3_Of_Size_4 ()
        {
            SetUpMode (true);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":5,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            var regExp = new Regex ("{[a-zA-Z0-9,:\"\\\\/()]+}");

            Assert.AreEqual (4, regExp.Matches (output).Count);

            Assert.IsTrue(output.Contains("\"Pages\":3"));
            Assert.IsTrue(output.Contains("\"Page\":1"));
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Page_2_Of_3_Of_Size_4_Sorted_By_Name_In_Ascending_Order ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 2, "", "name", "asc", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            var returnedPersons = _itemsListController.ViewData.Model as IEnumerable<Object>;

            Assert.AreEqual (4, returnedPersons.Count (), "Not all persons were returned.");

            Assert.AreEqual (4, (int)_itemsListController.ViewData ["pageSize"]);
            Assert.AreEqual (3, (int)_itemsListController.ViewData ["pages"]);
            Assert.AreEqual (2, (int)_itemsListController.ViewData ["page"]);

            var sortedPersons = _personsRepository.Persons.OrderBy (x => x.Name);

            var isSortOk = true;

            for (var i = 4; i < 8; ++i)
                if (sortedPersons.ElementAt (i).Name != (returnedPersons.ElementAt (i - 4) as Person).Name)
                {
                    isSortOk = false;

                    break;
                }

            Assert.IsTrue (isSortOk, "Sorting by name in ascending order has failed.");
        }

        [Test]
        public void MaintainList_In_Ajax_Mode_Returns_Page_2_Of_3_Of_Size_4_Sorted_By_Name_In_Ascending_Order ()
        {
            SetUpMode (true);

            var result = _itemsListController.MaintainList (4, 2, "", "name", "asc", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":5,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            var regExp = new Regex ("{[a-zA-Z0-9,:\"\\\\/()-]+}");

            var matches = regExp.Matches (output);

            Assert.AreEqual (4, matches.Count);

            Assert.IsTrue (output.Contains ("\"Pages\":3"));
            Assert.IsTrue (output.Contains ("\"Page\":2"));

            var sortedPersons = _personsRepository.Persons.OrderBy (x => x.Name);

            var isSortOk = true;

            for (var i = 4; i < 8; ++i)
            {
                var match = matches [i - 4].Value;

                regExp = new Regex("\"Name\":[a-zA-Z0-9_\"]+");

                match = regExp.Match (match).Value;

                match = match.Substring (8, match.Length - 9);

                if (sortedPersons.ElementAt (i).Name != match)
                {
                    isSortOk = false;

                    break;
                }
            }

            Assert.IsTrue (isSortOk, "Sorting by name in ascending order has failed.");
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Page_2_Of_3_Of_Size_4_Sorted_By_Name_In_Descending_Order ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 2, "", "name", "desc", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            var returnedPersons = _itemsListController.ViewData.Model as IEnumerable<Object>;

            Assert.AreEqual (4, returnedPersons.Count (), "Not all persons were returned.");

            Assert.AreEqual (4, (int)_itemsListController.ViewData ["pageSize"]);
            Assert.AreEqual (3, (int)_itemsListController.ViewData ["pages"]);
            Assert.AreEqual (2, (int)_itemsListController.ViewData ["page"]);

            var sortedPersons = _personsRepository.Persons.OrderByDescending (x => x.Name);

            var isSortOk = true;

            for (var i = 4; i < 8; ++i)
                if (sortedPersons.ElementAt (i).Name != (returnedPersons.ElementAt (i - 4) as Person).Name)
                {
                    isSortOk = false;

                    break;
                }

            Assert.IsTrue (isSortOk, "Sorting by name in descending order has failed.");
        }

        [Test]
        public void MaintainList_In_Ajax_Mode_Returns_Page_2_Of_3_Of_Size_4_Sorted_By_Name_In_Descending_Order ()
        {
            SetUpMode (true);

            var result = _itemsListController.MaintainList (4, 2, "", "name", "desc", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":5,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            var regExp = new Regex ("{[a-zA-Z0-9,:\"\\\\/()-]+}");

            var matches = regExp.Matches (output);

            Assert.AreEqual (4, matches.Count);

            Assert.IsTrue (output.Contains ("\"Pages\":3"));
            Assert.IsTrue (output.Contains ("\"Page\":2"));

            var sortedPersons = _personsRepository.Persons.OrderByDescending (x => x.Name);

            var isSortOk = true;

            for (var i = 4; i < 8; ++i)
            {
                var match = matches [i - 4].Value;

                regExp = new Regex ("\"Name\":[a-zA-Z0-9_\"]+");

                match = regExp.Match (match).Value;

                match = match.Substring (8, match.Length - 9);

                if (sortedPersons.ElementAt (i).Name != match)
                {
                    isSortOk = false;

                    break;
                }
            }

            Assert.IsTrue (isSortOk, "Sorting by name in descending order has failed.");
        }

        [Test]
        public void MaintainList_Preserves_Instant_Filtering_Setting ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 1, "ID", "", "", "", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.AreEqual ("ID", _itemsListController.ViewData ["showFilter"] as String);
        }

        [Test]
        public void MaintainList_Preserves_ScrollTop_Value ()
        {
            SetUpMode (false);

            var result = _itemsListController.MaintainList (4, 1, "ID", "", "", "123", new FormCollection ());

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.AreEqual ("123", _itemsListController.ViewData ["scrollTop"] as String);
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Persons_Filtered_By_Name_Name0_And_Name1 ()
        {
            SetUpMode (false);

            var filtersOn = new Dictionary<String, String[]> ();
            filtersOn ["Name"] = new [] { "Name0", "Name1" };

            var fc = BuildFilterFormCollection (filtersOn);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", fc);

            Assert.IsNotNull (result, "View is not rendered.");

            var returnedPersons = _itemsListController.ViewData.Model as IEnumerable<Object>;

            Assert.AreEqual (2, returnedPersons.Count (), "Not all persons were returned.");

            Assert.AreEqual (4, (int)_itemsListController.ViewData ["pageSize"]);
            Assert.AreEqual (1, (int)_itemsListController.ViewData ["pages"]);
            Assert.AreEqual (1, (int)_itemsListController.ViewData ["page"]);

            var p1 = returnedPersons.ElementAt (0) as Person;
            var p2 = returnedPersons.ElementAt (1) as Person;

            Assert.AreEqual ("Name0", p1.Name, "Person with name of Name0 was not returned.");
            Assert.AreEqual ("Name1", p2.Name, "Person with name of Name1 was not returned.");
        }

        [Test]
        public void MaintainList_In_Ajax_Mode_Returns_Persons_Filtered_By_Name_Name0_And_Name1 ()
        {
            SetUpMode (true);

            var filtersOn = new Dictionary<String, String []> ();
            filtersOn ["Name"] = new [] { "Name0", "Name1" };

            var fc = BuildFilterFormCollection (filtersOn);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", fc);

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":567,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            var regExp = new Regex ("{[a-zA-Z0-9,:\"\\\\/()-]+}");

            var matches = regExp.Matches (output);

            Assert.AreEqual (2, matches.Count);

            Assert.IsTrue (output.Contains ("\"Pages\":1"));
            Assert.IsTrue (output.Contains ("\"Page\":1"));

            Assert.IsTrue (matches [0].Value.Contains ("\"Name\":\"Name0\""), "First person's Name property not equals Name0.");

            Assert.IsTrue (matches [1].Value.Contains ("\"Name\":\"Name1\""), "Second person's Name property not equals Name1.");
        }

        [Test]
        public void MaintainList_In_Non_Ajax_Mode_Returns_Persons_Filtered_By_Id_1_And_2_IsMarried_By_True ()
        {
            SetUpMode (false);

            var filtersOn = new Dictionary<String, String []> ();
            filtersOn ["ID"] = new [] { "1", "2" };
            filtersOn ["IsMarried"] = new [] { "True" };

            var fc = BuildFilterFormCollection (filtersOn);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", fc);

            Assert.IsNotNull (result, "View is not rendered.");

            var returnedPersons = _itemsListController.ViewData.Model as IEnumerable<Object>;

            Assert.AreEqual (1, returnedPersons.Count (), "Not all persons were returned.");

            Assert.AreEqual (4, (int)_itemsListController.ViewData ["pageSize"]);
            Assert.AreEqual (1, (int)_itemsListController.ViewData ["pages"]);
            Assert.AreEqual (1, (int)_itemsListController.ViewData ["page"]);

            var p = returnedPersons.ElementAt (0) as Person;

            Assert.AreEqual (1, p.ID, "Person with ID of 1 was not returned.");
            Assert.AreEqual (true, p.IsMarried, "Person with IsMarried of true was not returned.");
        }

        [Test]
        public void MaintainList_In_Ajax_Mode_Returns_Persons_Filtered_By_Id_1_And_2_IsMarried_By_True ()
        {
            SetUpMode (true);

            var filtersOn = new Dictionary<String, String []> ();
            filtersOn ["ID"] = new [] { "1", "2" };
            filtersOn ["IsMarried"] = new [] { "True" };

            var fc = BuildFilterFormCollection (filtersOn);

            var result = _itemsListController.MaintainList (4, 1, "", "", "", "", fc);

            Assert.IsNotNull (result, "View is not rendered.");

            Assert.IsTrue (result is JsonResult, "Returned object is not of type JsonResult.");

            var jsonResult = result as JsonResult;

            var serializer = new JavaScriptSerializer ();
            var output = serializer.Serialize (jsonResult.Data);

            // {"Items":[{"ID":567,"Name":"Name4","BirthDate":"\/Date(26922296438)\/","IsMarried":true},
            //           {"ID":6,"Name":"Name5","BirthDate":"\/Date(-288610503561)\/","IsMarried":false},
            //           {"ID":7,"Name":"Name6","BirthDate":"\/Date(-604229703561)\/","IsMarried":true},
            //           {"ID":8,"Name":"Name7","BirthDate":"\/Date(-919762503561)\/","IsMarried":false}],
            //  "Pages":3,
            //  "Page":2,
            //  "SortBy":"Name",
            //  "SortMode":"asc",
            //  "NumberOfRecords":10}

            var regExp = new Regex ("{[a-zA-Z0-9,:\"\\\\/()-]+}");

            var matches = regExp.Matches (output);

            Assert.AreEqual (1, matches.Count, "Not all persons were returned.");

            Assert.IsTrue (output.Contains ("\"Pages\":1"));
            Assert.IsTrue (output.Contains ("\"Page\":1"));

            var match = matches [0].Value;

            Assert.IsTrue (match.Contains ("\"ID\":1"), "No ID property of value 1 was found.");

            Assert.IsTrue (match.Contains ("\"IsMarried\":true"), "No IsMarried property of value true was found.");
        }


        /// <summary>
        /// Set up the request mode for testing.
        /// </summary>
        /// <param name="isAjax">True if the request is an Ajax request, false otherwise.</param>
        void SetUpMode (bool isAjax)
        {
            var mockRequest = new Mock<HttpRequestBase> ();
           
            var mockContext = new Mock<HttpContextBase> ();
            mockContext.Setup (x => x.Request).Returns (mockRequest.Object);

            var nvc = new NameValueCollection ();

            if (isAjax)
                nvc.Add ("X-Requested-With", "XMLHttpRequest");

            mockRequest.Setup (x => x.Headers).Returns (nvc);

            var rc = new RequestContext (mockContext.Object, new RouteData ());

            _itemsListController.ControllerContext = new ControllerContext (rc, _itemsListController);
        }

        /// <summary>
        /// Creates a collection of HTML controls based on the property values of the persons.
        /// </summary>
        /// <param name="filtersOn">A dictionary that stores the enabled filter values.
        /// The key element is the name of the property and the value is an array of filter texts.</param>
        /// <returns>A FormCollection object with all the distinct property values for the 
        /// persons.</returns>
        FormCollection BuildFilterFormCollection (Dictionary<String, String[]> filtersOn)
        {
            var fc = new FormCollection ();

            foreach (var pInfo in _personsRepository.Persons.First ().GetType ().GetProperties ())
            {
                var filters = new List<String> ();

                foreach (var p in _personsRepository.Persons)
                {
                    var filterText = pInfo.GetValue (p, null).ToString ();

                    if (!filters.Contains (filterText))
                    {
                        filters.Add (filterText);
                 
                        fc.Add (
                            "filter_" + pInfo.Name + "_" + filterText, 
                            filtersOn.ContainsKey(pInfo.Name) ? filtersOn[pInfo.Name].Contains(filterText).ToString() : "False");
                    }
                }
            }

            return fc;
        }
    }
}
