// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
using NUnit.Framework;
using Assert = NUnit.Framework.Legacy.ClassicAssert;

namespace AvaloniaEdit.Tests.Document
{
    /// <summary>
    /// A randomized test for the line manager.
    /// </summary>
    [TestFixture]
    public class RandomizedLineManagerTest
    {
        TextDocument document;
        Random rnd;

        [OneTimeSetUp]
        public void FixtureSetup()
        {
            int seed = Environment.TickCount;
            Console.WriteLine("RandomizedLineManagerTest Seed: " + seed);
            rnd = new Random(seed);
        }

        [SetUp]
        public void Setup()
        {
            document = new TextDocument();
        }

        [Test]
        public void ShortReplacements()
        {
            char[] chars = { 'a', 'b', '\r', '\n' };
            char[] buffer = new char[20];
            for (int i = 0; i < 2500; i++)
            {
                int offset = rnd.Next(0, document.TextLength);
                int length = rnd.Next(0, document.TextLength - offset);
                int newTextLength = rnd.Next(0, 20);
                for (int j = 0; j < newTextLength; j++)
                {
                    buffer[j] = chars[rnd.Next(0, chars.Length)];
                }

                document.Replace(offset, length, new string(buffer, 0, newTextLength));
                CheckLines();
            }
        }

        [Test]
        public void LargeReplacements()
        {
            char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '\r', '\n' };
            char[] buffer = new char[1000];
            for (int i = 0; i < 20; i++)
            {
                int offset = rnd.Next(0, document.TextLength);
                int length = rnd.Next(0, (document.TextLength - offset) / 4);
                int newTextLength = rnd.Next(0, 1000);
                for (int j = 0; j < newTextLength; j++)
                {
                    buffer[j] = chars[rnd.Next(0, chars.Length)];
                }

                string newText = new string(buffer, 0, newTextLength);
                string expectedText = document.Text.Remove(offset, length).Insert(offset, newText);
                document.Replace(offset, length, newText);
                Assert.AreEqual(expectedText, document.Text);
                CheckLines();
            }
        }

        void CheckLines()
        {
            string text = document.Text;
            int lineNumber = 1;
            int lineStart = 0;
            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (c == '\r' && i + 1 < text.Length && text[i + 1] == '\n')
                {
                    DocumentLine line = document.GetLineByNumber(lineNumber);
                    Assert.AreEqual(lineNumber, line.LineNumber);
                    Assert.AreEqual(2, line.DelimiterLength);
                    Assert.AreEqual(lineStart, line.Offset);
                    Assert.AreEqual(i - lineStart, line.Length);
                    i++; // consume \n
                    lineNumber++;
                    lineStart = i + 1;
                }
                else if (c == '\r' || c == '\n')
                {
                    DocumentLine line = document.GetLineByNumber(lineNumber);
                    Assert.AreEqual(lineNumber, line.LineNumber);
                    Assert.AreEqual(1, line.DelimiterLength);
                    Assert.AreEqual(lineStart, line.Offset);
                    Assert.AreEqual(i - lineStart, line.Length);
                    lineNumber++;
                    lineStart = i + 1;
                }
            }
            Assert.AreEqual(lineNumber, document.LineCount);
        }

        [Test]
        public void CollapsingTest()
        {
            char[] chars = { 'a', 'b', '\r', '\n' };
            char[] buffer = new char[20];
            using HeightTree heightTree = new HeightTree(document, 10);
            List<CollapsedLineSection> collapsedSections = new List<CollapsedLineSection>();
            for (int i = 0; i < 2500; i++)
            {
                //				Console.WriteLine("Iteration " + i);
                //				Console.WriteLine(heightTree.GetTreeAsString());
                //				foreach (CollapsedLineSection cs in collapsedSections) {
                //					Console.WriteLine(cs);
                //				}

                switch (rnd.Next(0, 10))
                {
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                        int offset = rnd.Next(0, document.TextLength);
                        int length = rnd.Next(0, document.TextLength - offset);
                        int newTextLength = rnd.Next(0, 20);
                        for (int j = 0; j < newTextLength; j++)
                        {
                            buffer[j] = chars[rnd.Next(0, chars.Length)];
                        }

                        document.Replace(offset, length, new string(buffer, 0, newTextLength));
                        break;
                    case 6:
                    case 7:
                        int startLine = rnd.Next(1, document.LineCount + 1);
                        int endLine = rnd.Next(startLine, document.LineCount + 1);
                        collapsedSections.Add(heightTree.CollapseText(document.GetLineByNumber(startLine), document.GetLineByNumber(endLine)));
                        break;
                    case 8:
                        if (collapsedSections.Count > 0)
                        {
                            CollapsedLineSection cs = collapsedSections[rnd.Next(0, collapsedSections.Count)];
                            // unless the text section containing the CollapsedSection was deleted:
                            if (cs.Start != null)
                            {
                                cs.Uncollapse();
                            }
                            collapsedSections.Remove(cs);
                        }
                        break;
                    case 9:
                        foreach (DocumentLine ls in document.Lines)
                        {
                            heightTree.SetHeight(ls, ls.LineNumber);
                        }
                        break;
                }
                var treeSections = new HashSet<CollapsedLineSection>(heightTree.GetAllCollapsedSections());
                int expectedCount = 0;
                foreach (CollapsedLineSection cs in collapsedSections)
                {
                    if (cs.Start != null)
                    {
                        expectedCount++;
                        Assert.IsTrue(treeSections.Contains(cs));
                    }
                }
                Assert.AreEqual(expectedCount, treeSections.Count);
                CheckLines();
                HeightTests.CheckHeights(document, heightTree);
            }
        }
    }
}
