// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars
{
    /// <summary>
    /// Represents the individual characters that raw string token represents (i.e. with escapes collapsed).  
    /// The difference between this and the result from token.ValueText is that for each collapsed character
    /// returned the original span of text in the original token can be found.  i.e. if you had the
    /// following in C#:
    /// <para/>
    /// <c>"G\u006fo"</c>
    /// <para/>
    /// Then you'd get back:
    /// <para/>
    /// <c>'G' -> [0, 1) 'o' -> [1, 7) 'o' -> [7, 1)</c>
    /// <para/>
    /// This allows for embedded language processing that can refer back to the user's original code
    /// instead of the escaped value we're processing.
    /// </summary>
    internal partial struct VirtualCharSequence
    {
        public static readonly VirtualCharSequence Empty = Create(ImmutableSegmentedList<VirtualChar>.Empty);

        public static VirtualCharSequence Create(ImmutableSegmentedList<VirtualChar> virtualChars)
            => new(new ImmutableSegmentedListChunk(virtualChars));

        public static VirtualCharSequence Create(int firstVirtualCharPosition, string underlyingData)
            => new(new StringChunk(firstVirtualCharPosition, underlyingData));

        /// <summary>
        /// The actual characters that this <see cref="VirtualCharSequence"/> is a portion of.
        /// </summary>
        private readonly Chunk _leafCharacters;

        /// <summary>
        /// The portion of <see cref="_leafCharacters"/> that is being exposed.  This span 
        /// is `[inclusive, exclusive)`.
        /// </summary>
        private readonly TextSpan _span;

        private VirtualCharSequence(Chunk sequence)
            : this(sequence, new TextSpan(0, sequence.Length))
        {
        }

        private VirtualCharSequence(Chunk sequence, TextSpan span)
        {
            if (span.Start > sequence.Length)
                throw new ArgumentException();

            if (span.End > sequence.Length)
                throw new ArgumentException();

            _leafCharacters = sequence;
            _span = span;
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="VirtualCharSequence"/>.
        /// </summary>
        public int Length => _span.Length;

        /// <summary>
        /// Gets the <see cref="VirtualChar"/> at the specified index.
        /// </summary>
        public VirtualChar this[int index] => _leafCharacters[_span.Start + index];

        /// <summary>
        /// Gets a value indicating whether the <see cref="VirtualCharSequence"/> was declared but not initialized.
        /// </summary>
        public bool IsDefault => _leafCharacters == null;
        public bool IsEmpty => Length == 0;
        public bool IsDefaultOrEmpty => IsDefault || IsEmpty;

        /// <summary>
        /// Retreives a sub-sequence from this <see cref="VirtualCharSequence"/>.
        /// </summary>
        public VirtualCharSequence GetSubSequence(TextSpan span)
           => new(_leafCharacters, new TextSpan(_span.Start + span.Start, span.Length));

        public Enumerator GetEnumerator()
            => new(this);

        public VirtualChar First() => this[0];
        public VirtualChar Last() => this[^1];

        /// <summary>
        /// Finds the virtual char in this sequence that contains the position.  Will return null if this position is not
        /// in the span of this sequence.
        /// </summary>
        public VirtualChar? Find(int position)
            => _leafCharacters?.Find(position);

        public bool Contains(VirtualChar @char)
            => IndexOf(@char) >= 0;

        public int IndexOf(VirtualChar @char)
        {
            var index = 0;
            foreach (var ch in this)
            {
                if (ch == @char)
                    return index;

                index++;
            }

            return -1;
        }

        public VirtualChar? FirstOrNull(Func<VirtualChar, bool> predicate)
        {
            foreach (var ch in this)
            {
                if (predicate(ch))
                    return ch;
            }

            return null;
        }

        public VirtualChar? LastOrNull(Func<VirtualChar, bool> predicate)
        {
            for (var i = this.Length - 1; i >= 0; i--)
            {
                var ch = this[i];
                if (predicate(ch))
                    return ch;
            }

            return null;
        }

        public bool Any(Func<VirtualChar, bool> predicate)
        {
            foreach (var ch in this)
            {
                if (predicate(ch))
                    return true;
            }

            return false;
        }

        public bool All(Func<VirtualChar, bool> predicate)
        {
            foreach (var ch in this)
            {
                if (!predicate(ch))
                    return false;
            }

            return true;
        }

        public VirtualCharSequence Skip(int count)
            => this.GetSubSequence(TextSpan.FromBounds(count, this.Length));

        public VirtualCharSequence SkipWhile(Func<VirtualChar, bool> predicate)
        {
            var start = 0;
            foreach (var ch in this)
            {
                if (!predicate(ch))
                    break;

                start++;
            }

            return this.GetSubSequence(TextSpan.FromBounds(start, this.Length));
        }

        /// <summary>
        /// Create a <see cref="string"/> from the <see cref="VirtualCharSequence"/>.
        /// </summary>
        public string CreateString()
        {
            using var _ = PooledStringBuilder.GetInstance(out var builder);
            foreach (var ch in this)
                ch.AppendTo(builder);

            return builder.ToString();
        }

        [Conditional("DEBUG")]
        public void AssertAdjacentTo(VirtualCharSequence virtualChars)
        {
            Debug.Assert(_leafCharacters == virtualChars._leafCharacters);
            Debug.Assert(_span.End == virtualChars._span.Start);
        }

        /// <summary>
        /// Combines two <see cref="VirtualCharSequence"/>s, producing a final
        /// sequence that points at the same underlying data, but spans from the 
        /// start of <paramref name="chars1"/> to the end of <paramref name="chars2"/>.
        /// </summary>  
        public static VirtualCharSequence FromBounds(
            VirtualCharSequence chars1, VirtualCharSequence chars2)
        {
            Debug.Assert(chars1._leafCharacters == chars2._leafCharacters);
            return new VirtualCharSequence(
                chars1._leafCharacters,
                TextSpan.FromBounds(chars1._span.Start, chars2._span.End));
        }
    }
}
