 /*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package templates

import templates.DocExtensions.collection
import templates.Family.*
import templates.Ordering.appendStableSortNote
import templates.Ordering.stableSortNote

object ArrayOps : TemplateGroupBase() {

    init {
        defaultBuilder {
            specialFor(ArraysOfUnsigned) {
                sinceAtLeast("1.3")
                annotation("@ExperimentalUnsignedTypes")
            }
        }
    }

    val f_isEmpty = fn("isEmpty()") {
        include(ArraysOfObjects, ArraysOfPrimitives)
    } builder {
        inlineOnly()
        doc { "Returns `true` if the array is empty." }
        returns("Boolean")
        body {
            "return size == 0"
        }
    }

    val f_isNotEmpty = fn("isNotEmpty()") {
        include(ArraysOfObjects, ArraysOfPrimitives)
    } builder {
        inlineOnly()
        doc { "Returns `true` if the array is not empty." }
        returns("Boolean")
        body {
            "return !isEmpty()"
        }
    }


    val f_lastIndex = pval("lastIndex") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        doc { "Returns the last valid index for the array." }
        returns("Int")
        body {
            "get() = size - 1"
        }
        specialFor(ArraysOfUnsigned) {
            // TODO: Make inlineOnly after KT-30015 is fixed.
            // InlineOnly properties currently are not inlined and may lead to IllegalAccessException
            // when accessed from an inline (or inlineOnly) method.
            // It is because the method body contains access call to inlineOnly property in nonpublic multifile part,
            // which may be inaccessible from method call site, where method body gets inlined.
            inline()
            body { "get() = storage.lastIndex" }
        }
    }

    val f_indices = pval("indices") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        doc { "Returns the range of valid indices for the array." }
        returns("IntRange")
        body {
            "get() = IntRange(0, lastIndex)"
        }
        specialFor(ArraysOfUnsigned) {
            // TODO: Make inlineOnly after KT-30015 is fixed.
            // InlineOnly properties currently are not inlined and may lead to IllegalAccessException
            // when accessed from an inline (or inlineOnly) method.
            // It is because the method body contains access call to inlineOnly property in nonpublic multifile part,
            // which may be inaccessible from method call site, where method body gets inlined.
            inline()
            body { "get() = storage.indices" }
        }
    }

    private fun MemberBuilder.contentEqualsDoc(nullabilityNote: String = "") {
        doc {
            """
            Checks if the two specified arrays are *structurally* equal to one another.

            Two arrays are considered structurally equal if they have the same size, and elements at corresponding indices are equal.
            """
        }

        val isArrayOfObjects = f == ArraysOfObjects
        val isArrayOfFloatingPoint = primitive?.isFloatingPoint() == true

        if (isArrayOfObjects || isArrayOfFloatingPoint) {
            val prefix = if (isArrayOfObjects) "For floating point numbers, this" else "This"
            doc {
                doc + """Elements are compared for equality using the [equals][Any.equals] function.
                $prefix means `NaN` is equal to itself and `-0.0` is not equal to `0.0`.
                """
            }
        }
        doc {
            doc + nullabilityNote
        }
        if (isArrayOfObjects) {
            doc {
                doc + """
                If the arrays contain nested arrays, use [contentDeepEquals] to recursively compare their elements.
                """
            }
        }
        doc {
            doc + """
            @param other the array to compare with this array.
            @return `true` if the two arrays are structurally equal, `false` otherwise.
            """
        }

        val sampleMethod = when {
            isArrayOfObjects -> "arrayContentEquals"
            isArrayOfFloatingPoint -> "doubleArrayContentEquals"
            primitive == PrimitiveType.Char -> "charArrayContentEquals"
            primitive == PrimitiveType.Boolean -> "booleanArrayContentEquals"
            else -> "intArrayContentEquals"
        }
        sample("samples.collections.Arrays.ContentOperations.$sampleMethod")
    }

    val f_contentEquals_nullable = fn("contentEquals(other: SELF?)") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        since("1.4")
        infix(true)
        receiver("SELF?")
        returns("Boolean")
        contentEqualsDoc(
            nullabilityNote = """
            The arrays are also considered structurally equal if both are `null`.
            """
        )
        if (family == ArraysOfUnsigned) {
            body { "return this?.storage.contentEquals(other?.storage)" }
            return@builder
        }
        on(Platform.JVM) {
            inlineOnly()
            body { "return java.util.Arrays.equals(this, other)" }
        }

        on(Platform.JS) {
            body { "return contentEqualsInternal(other)" }
        }
        on(Platform.Native) {
            fun notEq(operand1: String, operand2: String) = when {
                primitive?.isFloatingPoint() == true -> "!$operand1.equals($operand2)"
                else -> "$operand1 != $operand2"
            }
            body {
                """
                if (this === other) return true
                if (this === null || other === null) return false
                if (size != other.size) return false
                for (i in indices) {
                    if (${notEq("this[i]", "other[i]")}) return false
                }
                return true
                """
            }
        }
    }

    private fun MemberBuilder.contentDeepEqualsDoc(nullabilityNote: String = "") {
        doc {
            """
            Checks if the two specified arrays are *deeply* equal to one another.

            Two arrays are considered deeply equal if they have the same size, and elements at corresponding indices are deeply equal.
            That is, if two corresponding elements are nested arrays, they are also compared deeply.
            Elements of other types are compared for equality using the [equals][Any.equals] function.
            For floating point numbers, this means `NaN` is equal to itself and `-0.0` is not equal to `0.0`.
            """
        }
        doc {
            doc + nullabilityNote
        }
        doc {
            doc + """
            If any of the arrays contain themselves at any nesting level, the behavior is undefined.

            @param other the array to compare deeply with this array.
            @return `true` if the two arrays are deeply equal, `false` otherwise.
            """
        }
        sample("samples.collections.Arrays.ContentOperations.contentDeepEquals")
    }

    val f_contentDeepEquals = fn("contentDeepEquals(other: SELF)") {
        include(ArraysOfObjects)
    } builder {
        since("1.1")
        annotation("@kotlin.internal.LowPriorityInOverloadResolution")
        infix(true)
        returns("Boolean")
        contentDeepEqualsDoc()
        body { "return this.contentDeepEquals(other)" }
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepEqualsInline")""")
        }
    }

    val f_contentDeepEquals_nullable = fn("contentDeepEquals(other: SELF?)") {
        include(ArraysOfObjects)
    } builder {
        since("1.4")
        infix(true)
        receiver("SELF?")
        returns("Boolean")
        contentDeepEqualsDoc(
            nullabilityNote = """
            The arrays are also considered deeply equal if both are `null`.
            """
        )
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepEqualsNullable")""")
        }
        body { "return contentDeepEqualsImpl(other)" }
    }

    val f_contentToString_nullable = fn("contentToString()") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        since("1.4")
        doc {
            """
            Returns a string representation of the contents of the specified array as if it is [List].
            """
        }
        sample("samples.collections.Arrays.ContentOperations.contentToString")
        receiver("SELF?")
        returns("String")
        if (family == ArraysOfUnsigned) {
            body { """return this?.joinToString(", ", "[", "]") ?: "null"""" }
            return@builder
        }

        on(Platform.JVM) {
            inlineOnly()
            body { "return java.util.Arrays.toString(this)" }
        }
        on(Platform.JS) {
            body { """return this?.joinToString(", ", "[", "]") ?: "null"""" }
        }
        on(Platform.Native) {
            body { """return this?.joinToString(", ", "[", "]") ?: "null"""" }
        }
    }

    val f_contentDeepToString = fn("contentDeepToString()") {
        include(ArraysOfObjects)
    } builder {
        since("1.1")
        annotation("@kotlin.internal.LowPriorityInOverloadResolution")
        doc {
            """
            Returns a string representation of the contents of this array as if it is a [List].
            Nested arrays are treated as lists too.

            If any of arrays contains itself on any nesting level that reference
            is rendered as `"[...]"` to prevent recursion.
            """
        }
        sample("samples.collections.Arrays.ContentOperations.contentDeepToString")
        returns("String")
        body { "return this.contentDeepToString()" }
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepToStringInline")""")
        }
    }

    val f_contentDeepToString_nullable = fn("contentDeepToString()") {
        include(ArraysOfObjects)
    } builder {
        since("1.4")
        doc {
            """
            Returns a string representation of the contents of this array as if it is a [List].
            Nested arrays are treated as lists too.

            If any of arrays contains itself on any nesting level that reference
            is rendered as `"[...]"` to prevent recursion.
            """
        }
        sample("samples.collections.Arrays.ContentOperations.contentDeepToString")
        receiver("SELF?")
        returns("String")
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepToStringNullable")""")
        }
        body { "return contentDeepToStringImpl()" }
    }

    val f_contentHashCode_nullable = fn("contentHashCode()") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        since("1.4")
        doc {
            "Returns a hash code based on the contents of this array as if it is [List]."
        }
        receiver("SELF?")
        returns("Int")
        if (family == ArraysOfUnsigned) {
            body { "return this?.storage.contentHashCode()" }
            return@builder
        }

        on(Platform.JVM) {
            inlineOnly()
            body { "return java.util.Arrays.hashCode(this)" }
        }
        on(Platform.JS) {
            body { "return contentHashCodeInternal()" }
        }
        on(Platform.Native) {
            body {
                """
                if (this === null) return 0
                var result = 1
                for (element in this)
                    result = 31 * result + element.hashCode()
                return result
                """
            }
        }
    }

    val f_contentDeepHashCode = fn("contentDeepHashCode()") {
        include(ArraysOfObjects)
    } builder {
        since("1.1")
        annotation("@kotlin.internal.LowPriorityInOverloadResolution")
        doc {
            """
            Returns a hash code based on the contents of this array as if it is [List].
            Nested arrays are treated as lists too.

            If any of arrays contains itself on any nesting level the behavior is undefined.
            """
        }
        returns("Int")
        body { "return this.contentDeepHashCode()" }
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepHashCodeInline")""")
        }
    }

    val f_contentDeepHashCode_nullable = fn("contentDeepHashCode()") {
        include(ArraysOfObjects)
    } builder {
        since("1.4")
        doc {
            """
            Returns a hash code based on the contents of this array as if it is [List].
            Nested arrays are treated as lists too.

            If any of arrays contains itself on any nesting level the behavior is undefined.
            """
        }
        receiver("SELF?")
        returns("Int")
        on(Platform.JVM) {
            inlineOnly()
            annotation("""@JvmName("contentDeepHashCodeNullable")""")
        }

        body { "return contentDeepHashCodeImpl()" }
        on(Platform.JS) {
            body { "return contentDeepHashCodeInternal()" }
        }
    }

    val f_toPrimitiveArray = fn("toPrimitiveArray()") {
        include(ArraysOfObjects, PrimitiveType.entries.toSet())
        include(Collections, PrimitiveType.entries.toSet())
    } builder {
        val primitive = checkNotNull(primitive)
        val arrayType = primitive.name + "Array"
        signature("to$arrayType()")
        returns(arrayType)

        if (primitive in PrimitiveType.unsignedPrimitives) {
            since("1.3")
            annotation("@ExperimentalUnsignedTypes")
        }

        // TODO: Use different implementations for JS
        specialFor(ArraysOfObjects) {
            if (primitive in PrimitiveType.unsignedPrimitives) {
                sourceFile(SourceFile.UArrays)
            }
            doc { "Returns an array of ${primitive.name} containing all of the elements of this generic array." }
            body {
                """
                return $arrayType(size) { index -> this[index] }
                """
            }
        }
        specialFor(Collections) {
            if (primitive in PrimitiveType.unsignedPrimitives) {
                sourceFile(SourceFile.UCollections)
            }
            doc { "Returns an array of ${primitive.name} containing all of the elements of this collection." }
            body {
                """
                val result = $arrayType(size)
                var index = 0
                for (element in this)
                    result[index++] = element
                return result
                """
            }
        }
    }

    val f_asSignedArray = fn("asSignedArray()") {
        include(ArraysOfUnsigned)
    } builder {
        val arrayType = primitive!!.name.drop(1) + "Array"
        signature("as$arrayType()")
        returns(arrayType)

        doc {
            """
            Returns an array of type [$arrayType], which is a view of this array where each element is a signed reinterpretation
            of the corresponding element of this array.
            """
        }

        inlineOnly()
        body { """return storage""" }
    }

    val f_toSignedArray = fn("toSignedArray()") {
        include(ArraysOfUnsigned)
    } builder {
        val arrayType = primitive!!.name.drop(1) + "Array"
        signature("to$arrayType()")
        returns(arrayType)

        doc {
            """
            Returns an array of type [$arrayType], which is a copy of this array where each element is a signed reinterpretation
            of the corresponding element of this array.
            """
        }

        inlineOnly()
        body { """return storage.copyOf()""" }
    }

    val f_asUnsignedArray = fn("asUnsignedArray()") {
        include(ArraysOfUnsigned)
    } builder {
        val arrayType = primitive!!.name.drop(1) + "Array"
        receiver(arrayType)
        signature("asU$arrayType()")
        returns("SELF")

        doc {
            """
            Returns an array of type [U$arrayType], which is a view of this array where each element is an unsigned reinterpretation
            of the corresponding element of this array.
            """
        }

        inlineOnly()
        body { """return U$arrayType(this)""" }
    }

    val f_toUnsignedArray = fn("toUnsignedArray()") {
        include(ArraysOfUnsigned)
    } builder {
        val arrayType = primitive!!.name.drop(1) + "Array"
        receiver(arrayType)
        signature("toU$arrayType()")
        returns("SELF")

        doc {
            """
            Returns an array of type [U$arrayType], which is a copy of this array where each element is an unsigned reinterpretation
            of the corresponding element of this array.
            """
        }

        inlineOnly()
        body { """return U$arrayType(this.copyOf())""" }
    }

    val f_plusElement = listOf(true, false).map { covariant ->
        fn("plusElement(element: T)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects)
            }
        } builder {
            returns("SELF")
            doc { "Returns an array containing all elements of the original array and then the given [element]." }

            if (covariant) {
                returns("Array<T>")
                inline(suppressWarning = true)
                body { """return this.asDynamic().concat(arrayOf(element))""" }
                return@builder
            }

            on(Platform.JVM) {
                inlineOnly()
                body { "return plus(element)" }
            }
            on(Platform.Native) {
                inlineOnly()
                body { "return plus(element)" }
            }
            on(Platform.JS) {
                inlineOnly()
                deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                body { """return this.plusElement(element)""" }
            }
        }
    }

    val f_plus = listOf(true, false).map { covariant ->
        fn("plus(element: T)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
            }
        } builderWith { primitive ->
            doc { "Returns an array containing all elements of the original array and then the given [element]." }
            operator()
            returns("SELF")

            if (covariant) {
                returns("Array<T>")
                inline(suppressWarning = true)
                body { "return this.asDynamic().concat(arrayOf(element))" }
                return@builderWith
            }

            specialFor(ArraysOfUnsigned) {
                inlineOnly()
                val signedPrimitiveName = primitive!!.name.drop(1)
                body {
                    """
                    return SELF(storage + element.to$signedPrimitiveName())
                    """
                }
            }

            specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
                on(Platform.JVM) {
                    body {
                        """
                        val index = size
                        val result = java.util.Arrays.copyOf(this, index + 1)
                        result[index] = element
                        return result
                        """
                    }
                }

                on(Platform.JS) {
                    inline(suppressWarning = true)
                    body {
                        if (primitive == null)
                            "return this.asDynamic().concat(arrayOf(element))"
                        else
                            "return plus(${primitive.name.lowercase()}ArrayOf(element))"
                    }
                    specialFor(InvariantArraysOfObjects) {
                        inlineOnly()
                        deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                        body { "return this.plus(element)" }
                    }
                }
                on(Platform.Native) {
                    body {
                        """
                    val index = size
                    val result = copyOfUninitializedElements(index + 1)
                    result[index] = element
                    return result
                    """
                    }
                }
            }
        }
    }


    val f_plus_collection = listOf(true, false).map { covariant ->
        fn("plus(elements: Collection<T>)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
            }
        } builder {
            operator()
            returns("SELF")
            doc { "Returns an array containing all elements of the original array and then all elements of the given [elements] collection." }

            if (covariant) {
                returns("Array<T>")
                body { "return arrayPlusCollection(this, elements)" }
                return@builder
            }

            specialFor(ArraysOfUnsigned) {
                val signedPrimitiveName = primitive!!.name.drop(1)
                body {
                    """
                    var index = size
                    val result = storage.copyOf(size + elements.size)
                    for (element in elements) result[index++] = element.to$signedPrimitiveName()
                    return SELF(result)
                    """
                }
            }
            specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
                on(Platform.JVM) {
                    body {
                        """
                        var index = size
                        val result = java.util.Arrays.copyOf(this, index + elements.size)
                        for (element in elements) result[index++] = element
                        return result
                        """
                    }
                }
                on(Platform.JS) {
                    // TODO: inline arrayPlusCollection when @PublishedAPI is available
//                        inline(Platform.JS, Inline.Yes)
//                        annotations(Platform.JS, """@Suppress("NOTHING_TO_INLINE")""")
                    when (primitive) {
                        null, PrimitiveType.Boolean, PrimitiveType.Long ->
                            body { "return arrayPlusCollection(this, elements)" }
                        else -> {
                            // Don't use fillFromCollection because it treats arrays
                            // as `dynamic` but we need to concrete types to perform
                            // unboxing of collections elements
                            body {
                                """
                                var index = size
                                val result = this.copyOf(size + elements.size)
                                for (element in elements) result[index++] = element
                                return result
                                """
                            }
                        }
                    }
                    specialFor(InvariantArraysOfObjects) {
                        inlineOnly()
                        deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                        body { "return this.plus(elements)" }
                    }
                }
                on(Platform.Native) {
                    body {
                        """
                        var index = size
                        val result = copyOfUninitializedElements(index + elements.size)
                        for (element in elements) result[index++] = element
                        return result
                        """
                    }
                }
            }
        }
    }

    val f_plus_array = listOf(true, false).map { covariant ->
        fn("plus(elements: SELF)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
            }
        } builder {
            operator(true)
            doc { "Returns an array containing all elements of the original array and then all elements of the given [elements] array." }
            returns("SELF")

            if (covariant) {
                signature("plus(elements: Array<out T>)", notForSorting = true)
                returns("Array<T>")
                inline(suppressWarning = true)
                body { """return this.asDynamic().concat(elements)""" }
                return@builder
            }

            specialFor(ArraysOfUnsigned) {
                inlineOnly()
                body { "return SELF(storage + elements.storage)" }
            }
            specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
                specialFor(InvariantArraysOfObjects) {
                    signature("plus(elements: Array<out T>)", notForSorting = true)
                }

                on(Platform.JVM) {
                    body {
                        """
                        val thisSize = size
                        val arraySize = elements.size
                        val result = java.util.Arrays.copyOf(this, thisSize + arraySize)
                        System.arraycopy(elements, 0, result, thisSize, arraySize)
                        return result
                        """
                    }

                }
                on(Platform.JS) {
                    inline(suppressWarning = true)
                    specialFor(InvariantArraysOfObjects) {
                        inlineOnly()
                        deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                        body { "return this.plus(elements)" }
                    }
                    specialFor(ArraysOfPrimitives) {
                        body { """return primitiveArrayConcat(this, elements)""" }
                    }
                }
                on(Platform.Native) {
                    body {
                        """
                        val thisSize = size
                        val arraySize = elements.size
                        val result = copyOfUninitializedElements(thisSize + arraySize)
                        elements.copyInto(result, thisSize)
                        return result
                        """
                    }
                }
            }
        }
    }

    val f_copyInto = fn("copyInto(destination: SELF, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size)") {
        include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        since("1.3")
        returns("SELF")

        doc {
            """
            Copies this array or its subrange into the [destination] array and returns that array.

            It's allowed to pass the same array in the [destination] and even specify the subrange so that it overlaps with the destination range.

            @param destination the array to copy to.
            @param destinationOffset the position in the [destination] array to copy to, 0 by default.
            @param startIndex the beginning (inclusive) of the subrange to copy, 0 by default.
            @param endIndex the end (exclusive) of the subrange to copy, size of this array by default.

            @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this array indices or when `startIndex > endIndex`.
            @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset],
            or when that index is out of the [destination] array indices range.

            @return the [destination] array.
            """
        }

        specialFor(ArraysOfUnsigned) {
            inlineOnly()
            body {
                """
                storage.copyInto(destination.storage, destinationOffset, startIndex, endIndex)
                return destination
                """
            }
        }
        specialFor(ArraysOfPrimitives, InvariantArraysOfObjects) {
            specialFor(InvariantArraysOfObjects) {
                receiver("Array<out T>")
            }
            on(Platform.JVM) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                signature("copyInto(destination: SELF, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size)")
                body {
                    """
                    System.arraycopy(this, startIndex, destination, destinationOffset, endIndex - startIndex)
                    return destination
                    """
                }
            }
            on(Platform.JS) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                signature("copyInto(destination: SELF, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size)")
                inlineOnly()
                body {
                    val cast = ".unsafeCast<Array<$primitive>>()".takeIf { family == ArraysOfPrimitives } ?: ""
                    """
                    arrayCopy(this$cast, destination$cast, destinationOffset, startIndex, endIndex)
                    return destination
                    """
                }
            }
            on(Platform.Native) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                body {
                    """
                    arrayCopy(this, startIndex, destination, destinationOffset, endIndex - startIndex)
                    return destination
                    """
                }
                specialFor(InvariantArraysOfObjects) {
                    body {
                        """
                        @Suppress("UNCHECKED_CAST")
                        arrayCopy(this as Array<Any?>, startIndex, destination as Array<Any?>, destinationOffset, endIndex - startIndex)
                        return destination
                        """
                    }
                }
                on(Backend.Wasm) {
                    body {
                        """
                        AbstractList.checkRangeIndexes(startIndex, endIndex, this.size)
                        val rangeSize = endIndex - startIndex
                        AbstractList.checkRangeIndexes(destinationOffset, destinationOffset + rangeSize, destination.size)
                        kotlin.wasm.internal.copyWasmArray(this.storage, destination.storage, startIndex, destinationOffset, rangeSize)
                        return destination
                        """
                    }
                }
            }
        }
    }

    val f_copyOfRangeJvmImpl = fn("copyOfRangeImpl(fromIndex: Int, toIndex: Int)") {
        include(InvariantArraysOfObjects, ArraysOfPrimitives)
        platforms(Platform.JVM)
    } builderWith { _ ->
        since("1.3")
        visibility("internal")
        annotation("@PublishedApi")
        annotation("""@JvmName("copyOfRange")""")
        returns("SELF")
        body {
            """
            copyOfRangeToIndexCheck(toIndex, size)
            return java.util.Arrays.copyOfRange(this, fromIndex, toIndex)
            """
        }
    }

    val f_copyOfRange = listOf(true, false).map { covariant ->
        fn("copyOfRange(fromIndex: Int, toIndex: Int)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
            }
        } builderWith { primitive ->
            doc {
                """
                Returns a new array which is a copy of the specified range of the original array.
                
                ${rangeDoc(hasDefault = false, action = "copy")}
                """
            }
            returns("SELF")

            val rangeCheck = "AbstractList.checkRangeIndexes(fromIndex, toIndex, size)"
            if (covariant) {
                returns("Array<T>")
                body { "return this.asDynamic().slice(fromIndex, toIndex)" }
                body { rangeCheck + "\n" + body }
                return@builderWith
            }

            specialFor(ArraysOfUnsigned) {
                inlineOnly()
                body { "return SELF(storage.copyOfRange(fromIndex, toIndex))" }
            }
            specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
                on(Platform.JS) {
                    when (primitive) {
                        PrimitiveType.Char, PrimitiveType.Boolean, PrimitiveType.Long ->
                            body { "return withType(\"${primitive}Array\", this.asDynamic().slice(fromIndex, toIndex))" }
                        null -> {
                            inlineOnly()
                            deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                            body { "return this.copyOfRange(fromIndex, toIndex)" }
                            return@on
                        }
                        else -> {
                            body { "return this.asDynamic().slice(fromIndex, toIndex)" }
                        }
                    }
                    body { rangeCheck + "\n" + body }
                }
                on(Platform.JVM) {
                    annotation("""@JvmName("copyOfRangeInline")""")
                    inlineOnly()
                    body { "return copyOfRangeImpl(fromIndex, toIndex)" }
                }
                on(Platform.Native) {
                    body {
                        """
                        checkCopyOfRangeArguments(fromIndex, toIndex, size)
                        return copyOfUninitializedElements(fromIndex, toIndex)
                        """
                    }
                }
            }
        }
    }

    val f_copyOfUninitializedElements_size = fn("copyOfUninitializedElements(newSize: Int)") {
        include(InvariantArraysOfObjects)
        include(ArraysOfPrimitives)
        platforms(Platform.Native)
    } builder {
        visibility("internal")
        returns("SELF")
        doc {
            """
            Returns new array which is a copy of the original array with new elements filled with **lateinit** _uninitialized_ values.
            Attempts to read _uninitialized_ values from this array work in implementation-dependent manner,
            either throwing exception or returning some kind of implementation-specific default value.
            """
        }
        body { "return copyOfUninitializedElements(0, newSize)" }
        specialFor(InvariantArraysOfObjects) {
            explicitActual()
        }
    }

    val f_copyOfUninitializedElements_range = fn("copyOfUninitializedElements(fromIndex: Int, toIndex: Int)") {
        include(InvariantArraysOfObjects)
        include(ArraysOfPrimitives)
        platforms(Platform.Native)
    } builder {
        visibility("internal")
        returns("SELF")
        doc {
            """
            Returns new array which is a copy of the original array's range between [fromIndex] (inclusive)
            and [toIndex] (exclusive) with new elements filled with **lateinit** _uninitialized_ values.
            Attempts to read _uninitialized_ values from this array work in implementation-dependent manner,
            either throwing exception or returning some kind of implementation-specific default value.
            """
        }
        body {
            """
            val newSize = toIndex - fromIndex
            if (newSize < 0) {
                throw IllegalArgumentException("${'$'}fromIndex > ${'$'}toIndex")
            }
            val result = ${if (f == InvariantArraysOfObjects) "arrayOfUninitializedElements<T>" else "SELF"}(newSize)
            this.copyInto(result, 0, fromIndex, toIndex.coerceAtMost(size))
            return result
            """
        }
    }

    val f_copyOf = listOf(true, false).map { covariant ->
        fn("copyOf()") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(InvariantArraysOfObjects)
                include(ArraysOfPrimitives, PrimitiveType.defaultPrimitives)
                include(ArraysOfUnsigned)
            }
        } builder {
            doc { "Returns new array which is a copy of the original array." }
            sample("samples.collections.Arrays.CopyOfOperations.copyOf")
            returns("SELF")

            if (covariant) {
                returns("Array<T>")
                inline(suppressWarning = true)
                body { "return this.asDynamic().slice()" }
                return@builder
            }

            specialFor(ArraysOfUnsigned) {
                inlineOnly()
                body { "return SELF(storage.copyOf())" }
            }
            specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
                on(Platform.JVM) {
                    inlineOnly()
                    body { "return java.util.Arrays.copyOf(this, size)" }
                }
                on(Platform.JS) {
                    when (primitive) {
                        null -> {
                            inlineOnly()
                            deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                            body { "return this.copyOf()" }
                        }
                        PrimitiveType.Char, PrimitiveType.Boolean, PrimitiveType.Long ->
                            body { "return withType(\"${primitive}Array\", this.asDynamic().slice())" }
                        else -> {
                            inline(suppressWarning = true)
                            body { "return this.asDynamic().slice()" }
                        }
                    }
                }
                on(Platform.Native) {
                    body { "return this.copyOfUninitializedElements(size)" }
                }
            }
        }
    }

    val f_copyOf_newSize = listOf(true, false).map { covariant ->
        fn("copyOf(newSize: Int)") {
            if (covariant) {
                include(ArraysOfObjects)
                platforms(Platform.JS)
            } else {
                include(ArraysOfPrimitives, PrimitiveType.defaultPrimitives)
                include(InvariantArraysOfObjects)
                include(ArraysOfUnsigned)
            }
        } builder {
            doc {
                """
                Returns new array which is a copy of the original array, resized to the given [newSize].
                The copy is either truncated or padded at the end with ${primitive.zero} values if necessary.
    
                - If [newSize] is less than the size of the original array, the copy array is truncated to the [newSize].
                - If [newSize] is greater than the size of the original array, the extra elements in the copy array are filled with ${primitive.zero} values.
                """
            }
            val newSizeCheck = """require(newSize >= 0) { "Invalid new array size: ${'$'}newSize." }"""

            if (covariant) {
                returns("Array<T?>")
                body {
                    """
                    $newSizeCheck
                    return arrayCopyResize(this, newSize, null)
                    """
                }
                return@builder
            }

            specialFor(ArraysOfUnsigned) {
                inlineOnly()
                returns("SELF")
                body { "return SELF(storage.copyOf(newSize))" }
            }
            specialFor(ArraysOfPrimitives) {
                sample("samples.collections.Arrays.CopyOfOperations.resizedPrimitiveCopyOf")
                returns("SELF")
                on(Platform.JS) {
                    when (primitive!!) {
                        PrimitiveType.Boolean ->
                            body { "return withType(\"BooleanArray\", arrayCopyResize(this, newSize, false))" }
                        PrimitiveType.Char ->
                            body { "return withType(\"CharArray\", fillFrom(this, ${primitive}Array(newSize)))" }
                        PrimitiveType.Long ->
                            body { "return withType(\"LongArray\", arrayCopyResize(this, newSize, ${primitive!!.zero()}))" }
                        else ->
                            body { "return fillFrom(this, ${primitive}Array(newSize))" }
                    }
                    body { newSizeCheck + "\n" + body }
                }
                on(Platform.Native) {
                    body { "return this.copyOfUninitializedElements(newSize)" }
                }
            }
            specialFor(InvariantArraysOfObjects) {
                sample("samples.collections.Arrays.CopyOfOperations.resizingCopyOf")
                returns("Array<T?>")
                on(Platform.JS) {
                    inlineOnly()
                    deprecate(Deprecation("Provided for expect-actual matching", level = DeprecationLevel.HIDDEN))
                    body { "return this.copyOf(newSize)" }
                }
                on(Platform.Native) {
                    body { "return this.copyOfNulls(newSize)" }
                }
            }
            specialFor(ArraysOfPrimitives, InvariantArraysOfObjects) {
                on(Platform.JVM) {
                    inlineOnly()
                    body {
                        "return java.util.Arrays.copyOf(this, newSize)"
                    }
                }
            }
        }
    }


    val f_copyOfWithInitializer = fn("copyOf(newSize: Int, init: (Int) -> T)") {
        include(InvariantArraysOfObjects)
        include(ArraysOfPrimitives, PrimitiveType.defaultPrimitives)
        include(ArraysOfUnsigned)
    } builder {
        doc {
            """
                Returns new array which is a copy of the original array, resized to the given [newSize].
                The copy is either truncated or padded at the end with values calculated by calling the specified [init] function.

                - If [newSize] is less than the size of the original array, the copy array is truncated to the [newSize].
                - If [newSize] is greater than the size of the original array,
                the extra elements in the copy array are filled with values calculated by calling the specified [init] function.

                The function [init] is called sequentially for each array element in range starting from the index corresponding to the source
                array size until [newSize].
                It should return the value for an array element given its index.              
                """
        }
        specialFor(ArraysOfPrimitives) {
            sample("samples.collections.Arrays.CopyOfOperations.copyOf${primitive!!.name}ArrayWithInitializer")
        }
        specialFor(ArraysOfUnsigned) {
            sample("samples.collections.Arrays.CopyOfOperations.copyOf${primitive!!.name}ArrayWithInitializer")
        }
        specialFor(InvariantArraysOfObjects) {
            sample("samples.collections.Arrays.CopyOfOperations.copyOfArrayWithInitializer")
        }

        sinceAtLeast("2.2")
        annotation("@ExperimentalStdlibApi")
        specialFor(ArraysOfUnsigned) {
            annotation("@ExperimentalUnsignedTypes")
        }

        val newSizeCheck = """require(newSize >= 0) { "Invalid new array size: ${'$'}newSize." }"""

        inlineOnly()
        returns("SELF")
        body {
            val returnStmt = if (f == InvariantArraysOfObjects) {
                "@Suppress(\"UNCHECKED_CAST\") return copy as Array<T>"
            } else {
                "return copy"
            }
            """
                $newSizeCheck
                val oldSize = size
                val copy = copyOf(newSize)
                for (idx in oldSize until newSize) {
                    copy[idx] = init(idx)
                }
                $returnStmt
                """
        }
    }

    val f_sort = fn("sort()") {
        include(ArraysOfPrimitives, PrimitiveType.numericPrimitives + PrimitiveType.Char)
        include(ArraysOfUnsigned)
        include(ArraysOfObjects)
    } builder {
        typeParam("T : Comparable<T>")
        doc { "Sorts the array in-place according to the natural order of its elements." }
        appendStableSortNote()
        specialFor(ArraysOfObjects) {
            sample("samples.collections.Arrays.Sorting.sortArrayOfComparable")
        }
        specialFor(ArraysOfPrimitives, ArraysOfUnsigned) {
            doc { "Sorts the array in-place." }
            sample("samples.collections.Arrays.Sorting.sortArray")
        }

        returns("Unit")

        body(ArraysOfUnsigned) {
            """if (size > 1) sortArray(this, 0, size)"""
        }

        specialFor(ArraysOfPrimitives, ArraysOfObjects) {
            on(Platform.JS) {
                body {
                    """if (size > 1) sortArray(this)"""
                }
                specialFor(ArraysOfPrimitives) {
                    if (primitive != PrimitiveType.Long) {
                        if (primitive == PrimitiveType.Char) {
                            // Requires comparator because default comparator of 'Array.prototype.sort' compares
                            // string representation of values
                            body { "nativeSort(::primitiveCompareTo)" }
                        } else {
                            body { "nativeSort()" }
                        }
                    } else {
                        body {
                            """
                            @Suppress("DEPRECATION")
                            if (size > 1) sort { a: T, b: T -> a.compareTo(b) }
                            """
                        }
                    }
                }
            }
            on(Platform.JVM) {
                specialFor(ArraysOfObjects) {
                    inlineOnly()
                    body {
                        """
                        @Suppress("UNCHECKED_CAST")
                        (this as Array<Any?>).sort()
                        """
                    }
                }
                specialFor(ArraysOfPrimitives) {
                    body {
                        "if (size > 1) java.util.Arrays.sort(this)"
                    }
                }
            }
            on(Platform.Native) {
                body { """if (size > 1) sortArray(this, 0, size)""" }
            }
        }
    }

    val f_sortWith = fn("sortWith(comparator: Comparator<in T>)") {
        include(ArraysOfObjects)
    } builder {
        doc { "Sorts the array in-place according to the order specified by the given [comparator]." }
        appendStableSortNote()
        returns("Unit")
        on(Platform.JVM) {
            body {
                "if (size > 1) java.util.Arrays.sort(this, comparator)"
            }
        }
        on(Platform.JS) {
            body {
                """if (size > 1) sortArrayWith(this, comparator)"""
            }
        }
        on(Platform.Native) {
            body { """if (size > 1) sortArrayWith(this, 0, size, comparator)""" }
        }
    }

    val f_sort_comparison = fn("sort(comparison: (a: T, b: T) -> Int)") {
        platforms(Platform.JS)
        include(ArraysOfObjects, ArraysOfPrimitives)
        exclude(PrimitiveType.Boolean)
    } builder {
        returns("Unit")
        doc { "Sorts the array in-place according to the order specified by the given [comparison] function." }
        specialFor(ArraysOfPrimitives) {
            deprecate(Deprecation("Use other sorting functions from the Standard Library", warningSince = "1.6"))
            inlineOnly()
            signature("sort(noinline comparison: (a: T, b: T) -> Int)")
            body { "nativeSort(comparison)" }
        }
        specialFor(ArraysOfObjects) {
            deprecate(
                Deprecation("Use sortWith instead", replaceWith = "this.sortWith(Comparator(comparison))", warningSince = "1.6")
            )
            appendStableSortNote()
            body { """if (size > 1) sortArrayWith(this, comparison)""" }
        }
    }

    val f_sort_objects = fn("sort()") {
        // left with more generic signature for JVM only
        platforms(Platform.JVM)
        include(ArraysOfObjects)
    } builder {
        doc {
            """
            Sorts the array in-place according to the natural order of its elements.

            $stableSortNote

            @throws ClassCastException if any element of the array is not [Comparable].
            """
        }
        returns("Unit")
        body {
            "if (size > 1) java.util.Arrays.sort(this)"
        }
    }

    val f_sort_range_jvm = fn("sort(fromIndex: Int = 0, toIndex: Int = size)") {
        platforms(Platform.JVM)
        include(ArraysOfObjects)
    } builder {
        doc {
            """
            Sorts a range in the array in-place.
            
            $stableSortNote
            
            ${rangeDoc(hasDefault = true, action = "sort")}
            """
        }
        sample("samples.collections.Arrays.Sorting.sortRangeOfArrayOfComparable")
        returns("Unit")
        body { "java.util.Arrays.sort(this, fromIndex, toIndex)" }
    }

    val f_sort_range = fn("sort(fromIndex: Int = 0, toIndex: Int = size)") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
        exclude(PrimitiveType.Boolean)
    } builder {
        on(Platform.Common) { since("1.4") }

        typeParam("T : Comparable<T>")
        doc {
            """
            Sorts a range in the array in-place.
            ${if (f == ArraysOfObjects) "\n$stableSortNote\n" else ""}
            ${rangeDoc(hasDefault = true, action = "sort")}
            """
        }
        specialFor(ArraysOfObjects) {
            sample("samples.collections.Arrays.Sorting.sortRangeOfArrayOfComparable")
        }
        specialFor(ArraysOfPrimitives, ArraysOfUnsigned) {
            sample("samples.collections.Arrays.Sorting.sortRangeOfArray")
        }
        returns("Unit")

        if (f == ArraysOfUnsigned) {
            since("1.4")
            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                if (fromIndex < toIndex - 1) sortArray(this, fromIndex, toIndex)
                """
            }
            return@builder
        }

        on(Platform.JVM) {
            if (f == ArraysOfObjects) since("1.4")
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                "java.util.Arrays.sort(this, fromIndex, toIndex)"
            }
        }
        on(Platform.Native) {
            since("1.4")
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                if (fromIndex < toIndex - 1) sortArray(this, fromIndex, toIndex)
                """
            }
        }
        on(Platform.JS) {
            since("1.4")
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                sortArrayWith(this, fromIndex, toIndex, naturalOrder())
                """
            }
            specialFor(ArraysOfPrimitives) {
                body {
                    if (primitive == PrimitiveType.Char && target.backend == Backend.IR || primitive == PrimitiveType.Long) {
                        """
                        AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                        sortArrayWith(this.unsafeCast<Array<T>>(), fromIndex, toIndex, naturalOrder())
                        """
                    } else {
                        """
                        AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                        val subarray = this.asDynamic().subarray(fromIndex, toIndex).unsafeCast<SELF>()
                        subarray.sort()
                        """
                    }
                }
            }
        }
    }

    val f_sortWith_range = fn("sortWith(comparator: Comparator<in T>, fromIndex: Int = 0, toIndex: Int = size)") {
        include(ArraysOfObjects)
    } builder {
        doc {
            """
            Sorts a range in the array in-place with the given [comparator].
            
            $stableSortNote
            
            ${rangeDoc(hasDefault = true, action = "sort")}
            """
        }
        returns("Unit")
        on(Platform.JVM) {
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                "java.util.Arrays.sort(this, fromIndex, toIndex, comparator)"
            }
        }
        on(Platform.Native) {
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                sortArrayWith(this, fromIndex, toIndex, comparator)
                """
            }
        }
        on(Platform.JS) {
            since("1.4")
            suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                sortArrayWith(this, fromIndex, toIndex, comparator)
                """
            }
        }
    }

    val f_sortDescending_range = fn("sortDescending(fromIndex: Int, toIndex: Int)") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
        exclude(PrimitiveType.Boolean)
    } builder {
        since("1.4")
        doc {
            """
            Sorts elements of the ${f.collection} in the specified range in-place.
            The elements are sorted descending according to their natural sort order.
            ${if (f == ArraysOfObjects) "\n$stableSortNote\n" else ""}
            ${rangeDoc(hasDefault = false, action = "sort")}
            """
        }
        returns("Unit")
        typeParam("T : Comparable<T>")

        specialFor(ArraysOfObjects) {
            body { """sortWith(reverseOrder(), fromIndex, toIndex)""" }
        }
        body(ArraysOfPrimitives, ArraysOfUnsigned) {
            """
            sort(fromIndex, toIndex)
            reverse(fromIndex, toIndex)
            """
        }
    }


    val f_asList = fn("asList()") {
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        doc { "Returns a [List] that wraps the original array." }
        returns("List<T>")
        on(Platform.JVM) {
            body { """return ArraysUtilJVM.asList(this)""" }
        }
        on(Platform.JS) {
            body { """return ArrayList<T>(this.unsafeCast<Array<Any?>>())""" }
        }

        val objectLiteralImpl = if (primitive in PrimitiveType.floatingPointPrimitives) """
            return object : AbstractList<T>(), RandomAccess {
                override val size: Int get() = this@asList.size
                override fun isEmpty(): Boolean = this@asList.isEmpty()
                override fun contains(element: T): Boolean = this@asList.any { it.toBits() == element.toBits() }
                override fun get(index: Int): T = this@asList[index]
                override fun indexOf(element: T): Int = this@asList.indexOfFirst { it.toBits() == element.toBits() }
                override fun lastIndexOf(element: T): Int = this@asList.indexOfLast { it.toBits() == element.toBits() }
            }
            """
        else """
            return object : AbstractList<T>(), RandomAccess {
                override val size: Int get() = this@asList.size
                override fun isEmpty(): Boolean = this@asList.isEmpty()
                override fun contains(element: T): Boolean = this@asList.contains(element)
                override fun get(index: Int): T = this@asList[index]
                override fun indexOf(element: T): Int = this@asList.indexOf(element)
                override fun lastIndexOf(element: T): Int = this@asList.lastIndexOf(element)
            }
            """
        specialFor(ArraysOfPrimitives, ArraysOfUnsigned) {
            on(Platform.JVM) {
                body { objectLiteralImpl }
            }
            on(Platform.JS) {
                if (primitive == PrimitiveType.Char || primitive in PrimitiveType.unsignedPrimitives) {
                    body {
                        """
                        return object : AbstractList<T>(), RandomAccess {
                            override val size: Int get() = this@asList.size
                            override fun isEmpty(): Boolean = this@asList.isEmpty()
                            override fun contains(element: T): Boolean = this@asList.contains(element)
                            override fun get(index: Int): T {
                                AbstractList.checkElementIndex(index, size)
                                return this@asList[index]
                            }
                            override fun indexOf(element: T): Int {
                                @Suppress("USELESS_CAST")
                                if ((element as Any?) !is T) return -1
                                return this@asList.indexOf(element)
                            }
                            override fun lastIndexOf(element: T): Int {
                                @Suppress("USELESS_CAST")
                                if ((element as Any?) !is T) return -1
                                return this@asList.lastIndexOf(element)
                            }
                        }
                        """
                    }
                }
                else {
                    inlineOnly()
                    body { "return this.unsafeCast<Array<T>>().asList()" }
                }
            }
        }
        on(Platform.Native) {
            body { objectLiteralImpl }
        }
    }

    val f_toTypedArray = fn("toTypedArray()") {
        include(ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        returns("Array<T>")
        doc {
            """
            Returns a *typed* object array containing all of the elements of this primitive array.
            """
        }
        body { "return Array(size) { index -> this[index] }" }
        specialFor(ArraysOfPrimitives) {
            on(Platform.JVM) {
                body {
                    """
                val result = arrayOfNulls<T>(size)
                for (index in indices)
                    result[index] = this[index]
                @Suppress("UNCHECKED_CAST")
                return result as Array<T>
                """
                }
            }
            on(Platform.JS) {
                when (primitive) {
                    PrimitiveType.Char -> {}
                    else ->
                        body { "return js(\"[]\").slice.call(this)" }
                }

            }
        }
    }

    fun MemberBuilder.rangeParamDoc(hasDefault: Boolean, action: String): String = """
        @param fromIndex the start of the range (inclusive) to $action${if (hasDefault) ", 0 by default" else ""}.
        @param toIndex the end of the range (exclusive) to $action${if (hasDefault) ", ${f.code.size} of this ${f.collection} by default" else ""}.
        """.trimIndent()

    fun MemberBuilder.rangeThrowsDoc(): String = """
        @throws IndexOutOfBoundsException if [fromIndex] is less than zero or [toIndex] is greater than the ${f.code.size} of this ${f.collection}.
        @throws IllegalArgumentException if [fromIndex] is greater than [toIndex].
        """.trimIndent()

    fun MemberBuilder.rangeDoc(hasDefault: Boolean, action: String): String = """
        ${rangeParamDoc(hasDefault, action)}
        
        ${rangeThrowsDoc()}
        """.trimIndent()

    val f_fill = fn("fill(element: T, fromIndex: Int = 0, toIndex: Int = size)") {
        include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
    } builder {
        doc {
            """
            Fills this array or its subrange with the specified [element] value.
            
            ${rangeDoc(hasDefault = true, action = "fill")}
            """
        }
        returns("Unit")

        specialFor(ArraysOfUnsigned) {
            val signedPrimitiveName = primitive!!.name.drop(1)
            body {
                "storage.fill(element.to$signedPrimitiveName(), fromIndex, toIndex)"
            }
        }

        specialFor(InvariantArraysOfObjects, ArraysOfPrimitives) {
            on(Platform.JVM) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                body {
                    "java.util.Arrays.fill(this, fromIndex, toIndex, element)"
                }
            }
            on(Platform.JS) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                since("1.3")
                body {
                    """
                    AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                    nativeFill(element, fromIndex, toIndex)
                    """
                }
                specialFor(ArraysOfPrimitives) {
                    if (primitive == PrimitiveType.Char) {
                        body {
                            """
                            AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                            // We need to call [Char.code] here to eliminate Char-boxing with the new JS function inlining logic
                            nativeFill(element.code, fromIndex, toIndex)
                            """
                        }
                    }
                }
            }
            on(Platform.Native) {
                suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
                since("1.3")
                body {
                    "arrayFill(this, fromIndex, toIndex, element)"
                }
                on(Backend.Wasm) {
                    body {
                        """
                        AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
                        for (index in fromIndex until toIndex) {
                            this[index] = element    
                        }
                        """
                    }
                }
            }
            on(Platform.Common) {
                since("1.3")
            }
        }
    }

    val f_binarySearch = fn("binarySearch(element: T, fromIndex: Int = 0, toIndex: Int = size)") {
        platforms(Platform.JVM)
        include(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
        exclude(PrimitiveType.Boolean)
    } builder {
        doc {
            """
            Searches the array or the range of the array for the provided [element] using the binary search algorithm.
            The array is expected to be sorted, otherwise the result is undefined.

            If the array contains multiple elements equal to the specified [element], there is no guarantee which one will be found.
            
            @param element the to search for.
            ${rangeParamDoc(hasDefault = true, action = "search in")}

            @return the index of the element, if it is contained in the array within the specified range;
            otherwise, the inverted insertion point `(-insertion point - 1)`.
            The insertion point is defined as the index at which the element should be inserted,
            so that the array (or the specified subrange of array) still remains sorted.
            
            ${rangeThrowsDoc()}
            """
        }
        returns("Int")
        body {
            "return java.util.Arrays.binarySearch(this, fromIndex, toIndex, element)"
        }

        specialFor(ArraysOfUnsigned) {
            val elementConversion: String
            val midValConversion: String
            val compareFunction: String
            when (primitive!!) {
                PrimitiveType.UByte, PrimitiveType.UShort -> {
                    elementConversion = ".toInt()"
                    midValConversion = ".toInt()"
                    compareFunction = "uintCompare"
                }
                PrimitiveType.UInt -> {
                    elementConversion = ".toInt()"
                    midValConversion = ""
                    compareFunction = "uintCompare"
                }
                PrimitiveType.ULong -> {
                    elementConversion = ".toLong()"
                    midValConversion = ""
                    compareFunction = "ulongCompare"
                }
                else -> error(primitive!!)
            }


            body {
                """
                AbstractList.checkRangeIndexes(fromIndex, toIndex, size)

                val signedElement = element$elementConversion
                var low = fromIndex
                var high = toIndex - 1

                while (low <= high) {
                    val mid = (low + high).ushr(1) // safe from overflows
                    val midVal = storage[mid]
                    val cmp = $compareFunction(midVal$midValConversion, signedElement)

                    if (cmp < 0)
                        low = mid + 1
                    else if (cmp > 0)
                        high = mid - 1
                    else
                        return mid // key found
                }
                return -(low + 1)  // key not found
                """
            }
        }
    }

    val f_binarySearch_comparator = fn("binarySearch(element: T, comparator: Comparator<in T>, fromIndex: Int = 0, toIndex: Int = size)") {
        platforms(Platform.JVM)
        include(ArraysOfObjects)
    } builder {
        doc {
            """
            Searches the array or the range of the array for the provided [element] using the binary search algorithm.
            The array is expected to be sorted according to the specified [comparator], otherwise the result is undefined.

            If the array contains multiple elements equal to the specified [element], there is no guarantee which one will be found.

            @param element the element to search for.
            @param comparator the comparator according to which this array is sorted.
            ${rangeParamDoc(hasDefault = true, action = "search in")}

            @return the index of the element, if it is contained in the array within the specified range;
            otherwise, the inverted insertion point `(-insertion point - 1)`.
            The insertion point is defined as the index at which the element should be inserted,
            so that the array (or the specified subrange of array) still remains sorted according to the specified [comparator].

            ${rangeThrowsDoc()}
            """
        }
        returns("Int")
        body {
            "return java.util.Arrays.binarySearch(this, fromIndex, toIndex, element, comparator)"
        }
    }
}

