// Lucene version compatibility level 4.8.1
using J2N.Text;
using Lucene.Net.Support;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using WritableArrayAttribute = Lucene.Net.Support.WritableArrayAttribute;

namespace Lucene.Net.Analysis.Util
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /// <summary>
    /// A StringBuilder that allows one to access the array.
    /// </summary>
    public class OpenStringBuilder : IAppendable, ISpanAppendable, ICharSequence // LUCENENET specific - implemented ISpanAppendable to support ReadOnlySpan<char>
    {
        protected char[] m_buf;
        protected int m_len;

        public OpenStringBuilder()
            : this(32)
        {
        }

        bool ICharSequence.HasValue => m_buf != null;

        public OpenStringBuilder(int size)
        {
            m_buf = new char[size];
        }

        public OpenStringBuilder(char[] arr, int len)
        {
            // LUCENENET specific - calling private method instead of public virtual
            SetInternal(arr, len);
        }

        public virtual int Length
        {
            get => m_len;
            set => m_len = value;
        }

        public virtual void Set(char[] arr, int end) => SetInternal(arr, end);

        // LUCENENET specific - S1699 - introduced this to allow the constructor to
        // still call "Set" functionality without having to call the virtual method
        // that could be overridden by a subclass and don't have the state it expects
        private void SetInternal(char[] arr, int end)
        {
            this.m_buf = arr;
            this.m_len = end;
        }

        [WritableArray]
        [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
        public virtual char[] Array => m_buf;

        // LUCENENE NOTE: This is essentially a duplicate of Length (except that property can be set).
        // .NET uses Length for StringBuilder anyway, so that property is preferable to this one.
        //public virtual int Count // LUCENENET NOTE: This was size() in Lucene.
        //{
        //    get{ return m_len; }
        //}

        public virtual int Capacity => m_buf.Length;

        public virtual OpenStringBuilder Append(ICharSequence csq)
        {
            return Append(csq, 0, csq.Length);
        }

        public virtual OpenStringBuilder Append(ICharSequence csq, int startIndex, int count) // LUCENENET specific: changed to startIndex/length to match .NET
        {
            EnsureCapacity(count);
            UnsafeWrite(csq, startIndex, count);
            return this;
        }

        public virtual OpenStringBuilder Append(ReadOnlySpan<char> value) // LUCENENET specific - added to support ReadOnlySpan<char>
        {
            EnsureCapacity(value.Length);
            UnsafeWrite(value);
            return this;
        }

        // LUCENENET specific - overload for string (more common in .NET than ICharSequence)
        public virtual OpenStringBuilder Append(string csq)
        {
            return Append(csq, 0, csq.Length);
        }

        // LUCENENET specific - overload for string (more common in .NET than ICharSequence)
        public virtual OpenStringBuilder Append(string csq, int startIndex, int count) // LUCENENET specific: changed to startIndex/length to match .NET
        {
            EnsureCapacity(count);
            UnsafeWrite(csq, startIndex, count);
            return this;
        }

        // LUCENENET specific - char sequence overload for StringBuilder
        public virtual OpenStringBuilder Append(StringBuilder csq)
        {
            return Append(csq, 0, csq.Length);
        }

        // LUCENENET specific - char sequence overload for StringBuilder
        public virtual OpenStringBuilder Append(StringBuilder csq, int startIndex, int count) // LUCENENET specific: changed to startIndex/length to match .NET
        {
            EnsureCapacity(count);
            UnsafeWrite(csq, startIndex, count);
            return this;
        }

        // LUCENENET specific - char sequence overload for char[]
        public virtual OpenStringBuilder Append(char[] value)
        {
            Write(value);
            return this;
        }

        // LUCENENET specific - char sequence overload for char[]
        public virtual OpenStringBuilder Append(char[] value, int startIndex, int count)
        {
            EnsureCapacity(count);
            UnsafeWrite(value, startIndex, count);
            return this;
        }

        public virtual OpenStringBuilder Append(char c)
        {
            Write(c);
            return this;
        }

        // LUCENENET specific - removed (replaced with this[])
        //public virtual char CharAt(int index)
        //{
        //    return m_buf[index];
        //}

        // LUCENENET specific - removed (replaced with this[])
        //public virtual void SetCharAt(int index, char ch)
        //{
        //    m_buf[index] = ch;
        //}

        // LUCENENET specific - added to .NETify
        public virtual char this[int index]
        {
            get => m_buf[index];
            set => m_buf[index] = value;
        }

        public virtual ICharSequence Subsequence(int startIndex, int length)
        {
            // From Apache Harmony String class
            if (m_buf is null || (startIndex == 0 && length == m_buf.Length))
            {
                return new CharArrayCharSequence(m_buf);
            }
            if (startIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(startIndex));
            if (length < 0)
                throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(length)} may not be negative.");
            if (startIndex > m_buf.Length - length) // LUCENENET: Checks for int overflow
                throw new ArgumentOutOfRangeException(nameof(length), $"Index and length must refer to a location within the string. For example {nameof(startIndex)} + {nameof(length)} <= {nameof(Length)}.");

            char[] result = new char[length];
            for (int i = 0, j = startIndex; i < length; i++, j++)
                result[i] = m_buf[j];

            return new CharArrayCharSequence(result);
        }

        public virtual void UnsafeWrite(char b)
        {
            m_buf[m_len++] = b;
        }

        public virtual void UnsafeWrite(int b)
        {
            UnsafeWrite((char)b);
        }

        public virtual void UnsafeWrite(ReadOnlySpan<char> b) // LUCENENET specific - added to support ReadOnlySpan<char>
        {
            int len = b.Length;
            b.CopyTo(m_buf.AsSpan(this.m_len, len));
            this.m_len += len;
        }

        public virtual void UnsafeWrite(char[] b, int off, int len)
        {
            Arrays.Copy(b, off, m_buf, this.m_len, len);
            this.m_len += len;
        }

        // LUCENENET specific overload for StringBuilder
        public virtual void UnsafeWrite(StringBuilder b, int off, int len)
        {
            b.CopyTo(off, m_buf, this.m_len, len);
            this.m_len += len;
        }

        // LUCENENET specific overload for string
        public virtual void UnsafeWrite(string b, int off, int len)
        {
            b.CopyTo(off, m_buf, this.m_len, len);
            this.m_len += len;
        }

        // LUCENENET specific overload for ICharSequence
        public virtual void UnsafeWrite(ICharSequence b, int off, int len)
        {
            if (!b.HasValue) return;

            if (b is StringBuilderCharSequence sb)
            {
                UnsafeWrite(sb.Value, off, len);
                return;
            }
            if (b is StringCharSequence str)
            {
                UnsafeWrite(str.Value, off, len);
                return;
            }
            if (b is CharArrayCharSequence chars)
            {
                UnsafeWrite(chars.Value, off, len);
                return;
            }

            for (int i = off; i < off + len; i++)
            {
                UnsafeWrite(b[i]);
            }
        }

        protected virtual void Resize(int len)
        {
            char[] newbuf = new char[Math.Max(m_buf.Length << 1, len)];
            Arrays.Copy(m_buf, 0, newbuf, 0, Length);
            m_buf = newbuf;
        }

        public virtual void EnsureCapacity(int capacity) // LUCENENET NOTE: renamed from reserve() in Lucene to match .NET StringBuilder
        {
            if (m_len + capacity > m_buf.Length)
            {
                Resize(m_len + capacity);
            }
        }

        public virtual void Write(char b)
        {
            if (m_len >= m_buf.Length)
            {
                Resize(m_len + 1);
            }
            UnsafeWrite(b);
        }

        public virtual void Write(int b)
        {
            Write((char)b);
        }

        public void Write(char[] b)
        {
            Write(b, 0, b.Length);
        }

        public virtual void Write(char[] b, int off, int len)
        {
            EnsureCapacity(len);
            UnsafeWrite(b, off, len);
        }

        public void Write(OpenStringBuilder arr)
        {
            Write(arr.m_buf, 0, arr.Length); // LUCENENET specific - changed to arr.m_len (original was just len - appears to be a bug)
        }

        // LUCENENET specific overload for StringBuilder
        public void Write(StringBuilder arr)
        {
            EnsureCapacity(arr.Length);
            UnsafeWrite(arr, 0, arr.Length);
        }

        public virtual void Write(string s)
        {
            EnsureCapacity(s.Length);
            s.CopyTo(0, m_buf, m_len, s.Length - 0);
            m_len += s.Length;
        }

        //public virtual void Flush() // LUCENENET specific - removed because this doesn't make much sense on a StringBuilder in .NET, and it is not used
        //{
        //}

        public void Reset()
        {
            m_len = 0;
        }

        public virtual char[] ToCharArray()
        {
            char[] newbuf = new char[Length];
            Arrays.Copy(m_buf, 0, newbuf, 0, Length);
            return newbuf;
        }

        public override string ToString()
        {
            return new string(m_buf, 0, Length);
        }

        public virtual OpenStringBuilder Remove(int startIndex, int length) // LUCENENET specific - added missing remove method
        {
            if (m_len == length && startIndex == 0)
            {
                m_len = 0;
                return this;
            }

            if (length > 0)
            {
                int endIndex = startIndex + length;
                m_buf.AsSpan(endIndex).CopyTo(m_buf.AsSpan(startIndex));
                m_len -= length;
            }
            return this;
        }

        #region IAppendable Members

        IAppendable IAppendable.Append(char value)
        {
            return Append(value);
        }

        IAppendable IAppendable.Append(string value)
        {
            return Append(value);
        }

        IAppendable IAppendable.Append(string value, int startIndex, int count)
        {
            return Append(value, startIndex, count);
        }

        IAppendable IAppendable.Append(StringBuilder value)
        {
            return Append(value);
        }

        IAppendable IAppendable.Append(StringBuilder value, int startIndex, int count)
        {
            return Append(value, startIndex, count);
        }

        IAppendable IAppendable.Append(char[] value)
        {
            return Append(value);
        }

        IAppendable IAppendable.Append(char[] value, int startIndex, int count)
        {
            return Append(value, startIndex, count);
        }

        IAppendable IAppendable.Append(ICharSequence value)
        {
            return Append(value);
        }

        IAppendable IAppendable.Append(ICharSequence value, int startIndex, int count)
        {
            return Append(value, startIndex, count);
        }

        ISpanAppendable ISpanAppendable.Append(ReadOnlySpan<char> value) => Append(value);

        #endregion IAppendable Members
    }
}
