using ClosedXML.Excel;
using ClosedXML.Excel.Patterns;
using ClosedXML.Excel.Ranges.Index;
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;

namespace ClosedXML_Tests.Excel.Ranges
{
    [TestFixture]
    public class RangeIndexTest
    {
        private const int TEST_COUNT = 10000;

        [Test]
        public void FindExistingMatches()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                for (int i = 1; i <= TEST_COUNT; i++)
                {
                    for (int j = 2; j <= 4; j++)
                    {
                        var address = new XLAddress(ws, i * 2, j, false, false);
                        Assert.True(index.Contains(in address));
                    }
                }
            }
        }

        [Test]
        public void FindNonExistingMatches()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                for (int i = 1; i <= TEST_COUNT; i++)
                {
                    var address = new XLAddress(ws, i * 2 + 1, 3, false, false);
                    Assert.False(index.Contains(in address));
                }
            }
        }

        [Test]
        public void FindExistingIntersections()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                for (int i = 1; i <= TEST_COUNT; i++)
                {
                    var rangeAddress = new XLRangeAddress(
                        new XLAddress(ws, i * 2, 1 + i % 4, false, false),
                        new XLAddress(ws, i * 2 + 1, 8 - i % 3, false, false));

                    Assert.True(index.Intersects(in rangeAddress));
                }

                for (int i = 2; i < 4; i++)
                {
                    var columnAddress = XLRangeAddress.EntireColumn(ws, i);
                    Assert.True(index.Intersects(in columnAddress));
                }
            }
        }

        [Test]
        public void FindNonExistingIntersections()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                for (int i = 1; i <= TEST_COUNT; i++)
                {
                    var rangeAddress = new XLRangeAddress(
                        new XLAddress(ws, i * 2 + 1, 1 + i % 4, false, false),
                        new XLAddress(ws, i * 2 + 1, 8 - i % 3, false, false));

                    Assert.False(index.Intersects(in rangeAddress));
                }

                var columnAddress = XLRangeAddress.EntireColumn(ws, 1);
                Assert.False(index.Intersects(in columnAddress));
                columnAddress = XLRangeAddress.EntireColumn(ws, 5);
                Assert.False(index.Intersects(in columnAddress));
            }
        }

        [Test]
        public void FindMatchAfterColumnShifting()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                ws.Column(3).InsertColumnsBefore(2);

                var address = new XLAddress(ws, 102, 6, false, false);

                Assert.True(index.Contains(in address));
            }
        }

        [Test]
        public void FindIntersectionsAfterColumnShifting()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                ws.Column(3).InsertColumnsBefore(2);

                var rangeAddress = new XLRangeAddress(ws, "F102:E103");

                Assert.True(index.Intersects(in rangeAddress));
            }
        }

        [Test]
        public void FindMatchAfterRowShifting()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                ws.Row(10).InsertRowsBelow(3);

                var address = new XLAddress(ws, 103, 4, false, false);

                Assert.True(index.Contains(in address));
            }
        }

        [Test]
        public void FindIntersectionsAfterRowShifting()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var index = FillIndexWithTestData(ws);

                ws.Row(10).InsertRowsBelow(3);

                var rangeAddress = new XLRangeAddress(ws, "C103:E103");

                Assert.True(index.Intersects(in rangeAddress));
            }
        }

        [Test]
        public void CreateQuadTree()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var quadTree = new Quadrant();
                var range = ws.Range("BT76:CA87");

                quadTree.Add(range);

                var level0 = quadTree;
                Assert.AreEqual(1, level0.MinimumColumn);
                Assert.AreEqual(XLHelper.MaxColumnNumber, level0.MaximumColumn);
                Assert.AreEqual(1, level0.MinimumRow);
                Assert.AreEqual(XLHelper.MaxRowNumber, level0.MaximumRow);
                Assert.IsNull(level0.Ranges);
                Assert.AreEqual(128, level0.Children.Count());
                Assert.True(level0.Children.All(child => child.Level == 1));
                Assert.AreEqual(64, level0.Children.Count(child =>
                    child.MinimumColumn == 1 &&
                    child.MaximumColumn == 8192 &&
                    child.X == 0));
                Assert.AreEqual(64, level0.Children.Count(child =>
                    child.MinimumColumn == 8193 &&
                    child.MaximumColumn == 16384 &&
                    child.X == 1));
                Assert.AreEqual(2, level0.Children.Count(child =>
                    child.MinimumRow == 1 &&
                    child.MaximumRow == 8192 &&
                    child.Y == 0));
                Assert.AreEqual(2, level0.Children.Count(child =>
                    child.MinimumRow == 16385 &&
                    child.MaximumRow == 24576 &&
                    child.Y == 2));

                Assert.True(level0.Children.ElementAt(0).Children.Any());
                Assert.True(level0.Children.Skip(1).All(child => child.Children == null));

                var level8 = level0
                    .Children.First() // 1
                    .Children.First() // 2
                    .Children.First() // 3
                    .Children.First() // 4
                    .Children.First() // 5
                    .Children.First() // 6
                    .Children.First() // 7
                    .Children.Last(); // 8

                Assert.AreEqual(65, level8.MinimumColumn);
                Assert.AreEqual(65, level8.MinimumRow);
                Assert.AreEqual(128, level8.MaximumColumn);
                Assert.AreEqual(128, level8.MaximumRow);

                var level9 = level8.Children.First();
                Assert.NotNull(level9.Ranges);
                Assert.AreEqual(range, level9.Ranges.Single());
            }
        }

        [Test]
        public void XLRangesCountChangesCorrectly()
        {
            using (var wb = new XLWorkbook())
            {
                var ws = wb.Worksheets.Add("Sheet1") as XLWorksheet;
                var range1 = ws.Range("A1:B2");
                var range2 = ws.Range("A2:B3");
                var range3 = ws.Range("A1:B2"); // same as range1

                var ranges = new XLRanges();
                ranges.Add(range1);
                Assert.AreEqual(1, ranges.Count);
                ranges.Add(range2);
                Assert.AreEqual(2, ranges.Count);
                ranges.Add(range3);
                Assert.AreEqual(2, ranges.Count);

                Assert.AreEqual(ranges.Count, ranges.Count());

                ranges.Remove(range3);
                Assert.AreEqual(1, ranges.Count);
                ranges.Remove(range2);
                Assert.AreEqual(0, ranges.Count);
                ranges.Remove(range1);
                Assert.AreEqual(0, ranges.Count);
            }
        }

        private IXLRangeIndex CreateRangeIndex(IXLWorksheet worksheet)
        {
            return new XLRangeIndex((XLWorksheet)worksheet);
        }

        private IXLRangeIndex FillIndexWithTestData(IXLWorksheet worksheet)
        {
            var ranges = new List<IXLRange>();
            for (int i = 1; i <= TEST_COUNT; i++)
            {
                ranges.Add(worksheet.Range(i * 2, 2, i * 2, 4));
            }

            var index = CreateRangeIndex(worksheet);
            ranges.ForEach(r => index.Add(r));
            return index;
        }
    }
}
