// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license. 
// See the license.txt file in the project root for more information.

using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax.Inlines;

namespace Markdig.Syntax;

/// <summary>
/// A base class for container blocks.
/// </summary>
/// <seealso cref="Block" />
[DebuggerDisplay("{GetType().Name} Count = {Count}")]
public abstract class ContainerBlock : Block, IList<Block>, IReadOnlyList<Block>
{
    private BlockWrapper[] _children;

    /// <summary>
    /// Initializes a new instance of the <see cref="ContainerBlock"/> class.
    /// </summary>
    /// <param name="parser">The parser used to create this block.</param>
    protected ContainerBlock(BlockParser? parser) : base(parser)
    {
        _children = [];
        SetTypeKind(isInline: false, isContainer: true);
    }

    /// <summary>
    /// Gets the last child.
    /// </summary>
    public Block? LastChild
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            BlockWrapper[] children = _children;
            int index = Count - 1;
            if ((uint)index < (uint)children.Length)
            {
                return children[index].Block;
            }
            else
            {
                return null;
            }
        }
    }

    /// <summary>
    /// Specialize enumerator.
    /// </summary>
    /// <returns></returns>
    public Enumerator GetEnumerator()
    {
        return new Enumerator(this);
    }

    IEnumerator<Block> IEnumerable<Block>.GetEnumerator()
    {
        return GetEnumerator();
    }

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

    /// <summary>
    /// Performs the add operation.
    /// </summary>
    public void Add(Block item)
    {
        if (item is null)
            ThrowHelper.ArgumentNullException_item();

        if (item.Parent != null)
        {
            ThrowHelper.ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
        }

        if (Count == _children.Length)
        {
            Grow();
        }
        _children[Count] = new BlockWrapper(item);
        Count++;
        item.Parent = this;

        UpdateSpanEnd(item.Span.End);
    }

    private void Grow()
    {
        if (_children.Length == 0)
        {
            _children = new BlockWrapper[4];
        }
        else
        {
            Debug.Assert(_children[_children.Length - 1].Block is not null);

            var newArray = new BlockWrapper[_children.Length * 2];
            Array.Copy(_children, 0, newArray, 0, Count);
            _children = newArray;
        }
    }

    /// <summary>
    /// Performs the clear operation.
    /// </summary>
    public void Clear()
    {
        BlockWrapper[] children = _children;
        for (int i = 0; i < Count && i < children.Length; i++)
        {
            children[i].Block.Parent = null;
            children[i] = default;
        }
        Count = 0;
    }

    /// <summary>
    /// Transfers all children from this container to <paramref name="destination"/>.
    /// </summary>
    /// <param name="destination">The destination container.</param>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
    /// <remarks>
    /// Child order is preserved. This method does not recompute source or destination spans/trivia.
    /// </remarks>
    public void TransferChildrenTo(ContainerBlock destination)
    {
        if (destination is null)
        {
            ThrowHelper.ArgumentNullException(nameof(destination));
        }

        if (ReferenceEquals(this, destination))
        {
            return;
        }

        var children = _children;
        int count = Count;
        for (int i = 0; i < count && i < children.Length; i++)
        {
            var child = children[i].Block;
            child.Parent = null;
            children[i] = default;
            destination.Add(child);
        }

        Count = 0;
    }

    /// <summary>
    /// Performs the contains operation.
    /// </summary>
    public bool Contains(Block item)
    {
        return IndexOf(item) >= 0;
    }

    /// <summary>
    /// Performs the copy to operation.
    /// </summary>
    public void CopyTo(Block[] array, int arrayIndex)
    {
        BlockWrapper[] children = _children;
        for (int i = 0; i < Count && i < children.Length; i++)
        {
            array[arrayIndex + i] = children[i].Block;
        }
    }

    /// <summary>
    /// Performs the remove operation.
    /// </summary>
    public bool Remove(Block item)
    {
        int index = IndexOf(item);
        if (index >= 0)
        {
            RemoveAt(index);
            return true;
        }
        return false;
    }

    /// <summary>
    /// Gets or sets the count.
    /// </summary>
    public int Count { get; private set; }

    /// <summary>
    /// Gets or sets the is read only.
    /// </summary>
    public bool IsReadOnly => false;

    /// <summary>
    /// Performs the index of operation.
    /// </summary>
    public int IndexOf(Block item)
    {
        if (item is null)
            ThrowHelper.ArgumentNullException_item();

        BlockWrapper[] children = _children;
        for (int i = 0; i < Count && i < children.Length; i++)
        {
            if (ReferenceEquals(children[i].Block, item))
            {
                return i;
            }
        }
        return -1;
    }

    /// <summary>
    /// Performs the insert operation.
    /// </summary>
    public void Insert(int index, Block item)
    {
        if (item is null)
            ThrowHelper.ArgumentNullException_item();

        if (item.Parent != null)
        {
            ThrowHelper.ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
        }
        if ((uint)index > (uint)Count)
        {
            ThrowHelper.ArgumentOutOfRangeException_index();
        }
        if (Count == _children.Length)
        {
            Grow();
        }
        if (index < Count)
        {
            Array.Copy(_children, index, _children, index + 1, Count - index);
        }
        _children[index] = new BlockWrapper(item);
        Count++;
        item.Parent = this;
    }

    /// <summary>
    /// Removes at.
    /// </summary>
    public void RemoveAt(int index)
    {
        if ((uint)index >= (uint)Count)
            ThrowHelper.ArgumentOutOfRangeException_index();

        Count--;
        // previous children
        var item = _children[index].Block;
        item.Parent = null;
        if (index < Count)
        {
            Array.Copy(_children, index + 1, _children, index, Count - index);
        }
        _children[Count] = default;
    }

    /// <summary>
    /// Gets the value at the specified index.
    /// </summary>
    public Block this[int index]
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            var array = _children;
            if ((uint)index >= (uint)array.Length || index >= Count)
            {
                ThrowHelper.ThrowIndexOutOfRangeException();
                return null;
            }
            return array[index].Block;
        }
        set
        {
            if ((uint)index >= (uint)Count) ThrowHelper.ThrowIndexOutOfRangeException();

            if (value is null)
                ThrowHelper.ArgumentNullException_item();

            if (value.Parent != null)
                ThrowHelper.ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");

            var existingChild = _children[index].Block;
            if (existingChild != null)
                existingChild.Parent = null;

            value.Parent = this;
            _children[index] = new BlockWrapper(value);
        }
    }

    /// <summary>
    /// Checks whether this container span is valid with respect to child block spans.
    /// </summary>
    /// <param name="recursive">
    /// When <c>true</c>, validates descendant container blocks and inline containers recursively.
    /// </param>
    /// <returns>
    /// <c>true</c> when this container span contains all direct child spans and recursive checks (if enabled) succeed;
    /// otherwise, <c>false</c>.
    /// </returns>
    public bool HasValidSpan(bool recursive = false)
    {
        var children = _children;
        for (int i = 0; i < Count && i < children.Length; i++)
        {
            var child = children[i].Block;
            if (!ContainsSpan(Span, child.Span))
            {
                return false;
            }

            if (!recursive)
            {
                continue;
            }

            if (child is ContainerBlock containerBlock)
            {
                if (!containerBlock.HasValidSpan(recursive: true))
                {
                    return false;
                }
            }
            else if (child is LeafBlock leafBlock && leafBlock.Inline is ContainerInline inline)
            {
                if (!ContainsSpan(leafBlock.Span, inline.Span))
                {
                    return false;
                }

                if (!inline.HasValidSpan(recursive: true))
                {
                    return false;
                }
            }
        }

        return true;
    }

    /// <summary>
    /// Updates this container span from its child block spans.
    /// </summary>
    /// <param name="recursive">
    /// When <c>true</c>, updates descendant container blocks and inline containers recursively before updating this container.
    /// </param>
    /// <param name="preserveSelfSpan">
    /// When <c>true</c>, preserves this container current span and only expands it to include children.
    /// When <c>false</c>, recomputes from children only.
    /// </param>
    /// <returns><c>true</c> when this container span changed; otherwise, <c>false</c>.</returns>
    public bool UpdateSpanFromChildren(bool recursive = false, bool preserveSelfSpan = true)
    {
        var updatedSpan = SourceSpan.Empty;
        bool hasUpdatedSpan = false;

        if (preserveSelfSpan && !Span.IsEmpty)
        {
            updatedSpan = Span;
            hasUpdatedSpan = true;
        }

        var children = _children;
        for (int i = 0; i < Count && i < children.Length; i++)
        {
            var child = children[i].Block;

            if (recursive)
            {
                if (child is ContainerBlock containerBlock)
                {
                    containerBlock.UpdateSpanFromChildren(recursive: true, preserveSelfSpan: preserveSelfSpan);
                }
                else if (child is LeafBlock leafBlock && leafBlock.Inline is ContainerInline inline)
                {
                    inline.UpdateSpanFromChildren(recursive: true, preserveSelfSpan: preserveSelfSpan);

                    if (!ContainsSpan(leafBlock.Span, inline.Span))
                    {
                        if (preserveSelfSpan && !leafBlock.Span.IsEmpty)
                        {
                            leafBlock.UpdateSpanToInclude(inline.Span);
                        }
                        else
                        {
                            leafBlock.Span = inline.Span;
                        }
                    }
                }
            }

            AppendSpan(ref updatedSpan, ref hasUpdatedSpan, child.Span);
        }

        if (!hasUpdatedSpan)
        {
            updatedSpan = SourceSpan.Empty;
        }

        if (updatedSpan == Span)
        {
            return false;
        }

        Span = updatedSpan;
        return true;
    }

    /// <summary>
    /// Performs the sort operation.
    /// </summary>
    public void Sort(IComparer<Block> comparer)
    {
        if (comparer is null) ThrowHelper.ArgumentNullException(nameof(comparer));
        Array.Sort(_children, 0, Count, new BlockComparerWrapper(comparer));
    }

    /// <summary>
    /// Performs the sort operation.
    /// </summary>
    public void Sort(Comparison<Block> comparison)
    {
        if (comparison is null) ThrowHelper.ArgumentNullException(nameof(comparison));
        Array.Sort(_children, 0, Count, new BlockComparisonWrapper(comparison));
    }

    #region Nested type: Enumerator

    /// <summary>
    /// Represents the Enumerator type.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Enumerator : IEnumerator<Block>
    {
        private readonly ContainerBlock block;
        private int index;
        private Block? current;

        internal Enumerator(ContainerBlock block)
        {
            this.block = block;
            index = 0;
            current = null;
        }

        /// <summary>
        /// Gets or sets the current.
        /// </summary>
        public Block Current => current!;

        object IEnumerator.Current => Current;

        /// <summary>
        /// Performs the dispose operation.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// Performs the move next operation.
        /// </summary>
        public bool MoveNext()
        {
            if (index < block.Count)
            {
                current = block[index];
                index++;
                return true;
            }
            return MoveNextRare();
        }

        private bool MoveNextRare()
        {
            index = block.Count + 1;
            current = null;
            return false;
        }

        void IEnumerator.Reset()
        {
            index = 0;
            current = null;
        }
    }

    #endregion

    private sealed class BlockComparisonWrapper(Comparison<Block> comparison) : IComparer<BlockWrapper>
    {
        private readonly Comparison<Block> _comparison = comparison;

        public int Compare(BlockWrapper x, BlockWrapper y)
        {
            return _comparison(x.Block, y.Block);
        }
    }

    private sealed class BlockComparerWrapper(IComparer<Block> comparer) : IComparer<BlockWrapper>
    {
        private readonly IComparer<Block> _comparer = comparer;

        public int Compare(BlockWrapper x, BlockWrapper y)
        {
            return _comparer.Compare(x.Block, y.Block);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool ContainsSpan(in SourceSpan containerSpan, in SourceSpan childSpan)
    {
        return childSpan.IsEmpty || (!containerSpan.IsEmpty && childSpan.Start >= containerSpan.Start && childSpan.End <= containerSpan.End);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void AppendSpan(ref SourceSpan destinationSpan, ref bool hasDestinationSpan, in SourceSpan spanToAppend)
    {
        if (spanToAppend.IsEmpty)
        {
            return;
        }

        if (!hasDestinationSpan)
        {
            destinationSpan = spanToAppend;
            hasDestinationSpan = true;
            return;
        }

        if (spanToAppend.Start < destinationSpan.Start)
        {
            destinationSpan.Start = spanToAppend.Start;
        }

        if (spanToAppend.End > destinationSpan.End)
        {
            destinationSpan.End = spanToAppend.End;
        }
    }
}
