// String preparation, as specified in RFC4013

        /// <summary>
        /// This method will iterate all charactesr in given <see cref="String"/> and return index of first escapable character, as per <see href="https://tools.ietf.org/html/rfc4013">RFC-4013</see> spec.
        /// Will return <c>-1</c> if all characters are valid.
        /// </summary>
        /// <param name="str">The string to process.</param>
        /// <param name="normalizer">The custom normalizer callback</param>
        /// <returns>Index of first escapable or transformable character.</returns>
        /// <exception cref="ArgumentException">If <paramref name="str"/> contains any invalid character, as per <see href="https://tools.ietf.org/html/rfc3454">RFC-3454</see> spec.</exception>
        public static Int32 CheckString(
            String str,
            CustomStringNormalizer normalizer = null
            )
        {
            var retVal = -1;

            for (var i = 0; i < str.Length && retVal == -1; ++i)
            {
                var c = str[i];
                if (c == 0)
                {
                    throw new ArgumentException("Null characters are not allowed.", nameof(str));
                }
                else if (Char.IsControl(c))
                {
                    // Control characters not allowed
                    throw new ArgumentException("Control characters are not allowed.", nameof(str));
                }
                else if (c >= MIN_SPECIAL_CHAR)
                {
                    // It is possible that we need to replace character
                    if (IsNonAsciiSpace(c))
                    {
                        // Mapping special space into "normal" space (0x20)
                        retVal = i;
                    }
                    else if (IsCommonlyMappedToNothing(c))
                    {
                        // Create builder but don't append anything, as this character is 'nothing'
                        retVal = i;
                    }
                    else if (IsProhibited(str, i))
                    {
                        // Illegal character
                        throw new ArgumentException($"Prohibited character at index {i}", nameof(str));
                    }
                    else if (normalizer?.Invoke(str, i) != null)
                    {
                        retVal = i;
                    }
                }
                else if (normalizer?.Invoke(str, i) != null)
                {
                    retVal = i;
                }
            }

            return(retVal);
        }
        /// <summary>
        /// This method will write SASL string to given byte array using given <see cref="IEncodingInfo"/>, while escaping and transforming string as needed.
        /// See <see cref="CheckString"/> for more information about escaping and transforming.
        /// </summary>
        /// <param name="str">The string to write.</param>
        /// <param name="encodingInfo">The <see cref="IEncodingInfo"/> to use.</param>
        /// <param name="array">The byte array where to write the string.</param>
        /// <param name="offset">The offset in <paramref name="array"/> where to start writing.</param>
        /// <param name="normalizer">The optional custom normalizer callback to use.</param>
        /// <param name="checkResult">The result of <see cref="CheckString"/>, if it was used before calling this method.</param>
        /// <seealso cref="CheckString"/>
        /// <remarks>
        /// This method will not allocate any strings.
        /// </remarks>
        public static void WriteString(
            String str,
            IEncodingInfo encodingInfo,
            ResizableArray <Byte> array,
            ref Int32 offset,
            CustomStringNormalizer normalizer = null,
            Int32?checkResult = null
            )
        {
            var checkResultValue = checkResult ?? CheckString(str, normalizer);
            var encoding         = encodingInfo.Encoding;

            if (checkResultValue == -1)
            {
                // String may be written as-is
                array.CurrentMaxCapacity = offset + encoding.GetByteCount(str);
                encodingInfo.WriteString(array.Array, ref offset, str);
            }
            else
            {
                var maxCharByteCountDiff = encodingInfo.MaxCharByteCount - 1;

                String tmpStr;

                // Calculate byte size (actually, we are calculating the upper bound for byte size, but that's ok)
                var byteSize = encoding.GetByteCount(str);
                for (var i = checkResultValue; i < str.Length; ++i)
                {
                    var c = str[i];
                    if (c >= MIN_SPECIAL_CHAR)
                    {
                        // It is possible that we need to replace character
                        if (IsNonAsciiSpace(c))
                        {
                            // Mapping special space into "normal" space (0x20)
                            // Byte size doesn't grow at least.
                        }
                        else if (IsCommonlyMappedToNothing(c))
                        {
                            --byteSize;
                        }
                        else if ((tmpStr = normalizer?.Invoke(str, i)) != null)
                        {
                            byteSize += encoding.GetByteCount(tmpStr) - 1;
                        }
                    }
                }

                // Now we know that the string will take max 'byteSize' amount of bytes
                array.CurrentMaxCapacity = offset + byteSize;
                // We can get the actual array now, since we won't be resizing the ResizableArray anymore
                var buffer = array.Array;

                // Helper method to write chunks of "normal" characters
                var prev = 0;
                void WritePreviousChunk(Int32 charIdx, ref Int32 writeIdx)
                {
                    writeIdx += encoding.GetBytes(str, prev, charIdx - prev, buffer, writeIdx);
                    prev      = charIdx + 1;
                }

                for (var i = checkResultValue; i < str.Length; ++i)
                {
                    var c = str[i];
                    if (c >= MIN_SPECIAL_CHAR)
                    {
                        // It is possible that we need to replace character
                        if (IsNonAsciiSpace(c))
                        {
                            // Mapping special space into "normal" space (0x20)
                            WritePreviousChunk(i, ref offset);
                            encodingInfo.WriteASCIIByte(buffer, ref offset, 0x20);
                        }
                        else if (IsCommonlyMappedToNothing(c))
                        {
                            // Nothing additional to write
                            WritePreviousChunk(i, ref offset);
                        }
                        else if ((tmpStr = normalizer?.Invoke(str, i)) != null)
                        {
                            WritePreviousChunk(i, ref offset);
                            if (tmpStr.Length > 0)
                            {
                                encodingInfo.WriteString(buffer, ref offset, tmpStr);
                            }
                        }
                    }
                }

                if (prev < str.Length)
                {
                    // Write final chunk
                    WritePreviousChunk(str.Length - 1, ref offset);
                }
            }
        }