/**
 This module contains support for controlling dynamic arrays' concatenation
  Copyright: Copyright Digital Mars 2000 - 2019.
  License: Distributed under the
       $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
     (See accompanying file LICENSE)
  Source: $(DRUNTIMESRC core/internal/_array/_concatenation.d)
*/
module core.internal.array.concatenation;

/**
 * Concatenate the arrays inside of `froms`.
 * `_d_arraycatnTX(a, b, c)` means `a ~ b ~ c`.
 *
 * Params:
 *      froms = Arrays to be concatenated.
 * Returns:
 *      A newly allocated array that contains all the elements from `froms`.
 */
Tret _d_arraycatnTX(Tret: Tret_El[], Tret_El, Tarr...)(auto ref Tarr froms) @trusted
{
    import core.exception : onOutOfMemoryError;
    import core.internal.array.utils : __arrayAlloc;
    import core.internal.traits : hasElaborateCopyConstructor, Unqual;
    import core.lifetime : copyEmplace;
    import core.stdc.string : memcpy;

    Tret res;
    size_t totalLen;

    alias T = typeof(res[0]); // Element type of the result array
    alias UnqT = Unqual!T;    // Unqualified version of T (strips const/immutable)
    enum elemSize = T.sizeof; // Size of each element
    enum hasPostblit = __traits(hasPostblit, T); // Whether T has a postblit constructor

    // Compute total length of the resulting array
    static foreach (from; froms)
        static if (is(typeof(from) : T))
            totalLen++; // Single element contributes one to length
        else
            totalLen += from.length; // Arrays contribute their full length

    if (totalLen == 0)
        return res; // Return an empty array if no elements are present

    // Allocate memory for mutable arrays using __arrayAlloc
    res = (cast(Tret_El*) &__arrayAlloc!(UnqT)(elemSize * totalLen)[0])[0 .. totalLen];

    if (res.ptr is null)
        onOutOfMemoryError(); // Abort if allocation fails

    static if (hasElaborateCopyConstructor!T && !hasPostblit)
    {
        size_t i = 0;
        foreach (ref from; froms)
            static if (is(typeof(from) : T))
                copyEmplace(cast(T) from, res[i++]);
            else
            {
                if (from.length)
                    foreach (ref elem; from)
                        copyEmplace(cast(T) elem, res[i++]);
            }
    }
    else
    {
        auto resptr = cast(UnqT *) res;
        foreach (ref from; froms)
            static if (is (typeof(from) : T))
                memcpy(resptr++, cast(UnqT *) &from, elemSize);
            else
            {
                const len = from.length;
                if (len)
                {
                    memcpy(resptr, cast(UnqT *) from, len * elemSize);
                    resptr += len;
                }
            }

        static if (hasPostblit)
            foreach (ref elem; res)
                (cast() elem).__xpostblit();
    }

    return res;
}

// postblit
@safe unittest
{
    int counter;
    struct S
    {
        int val;
        this(this)
        {
            counter++;
        }
    }

    S[] arr1 = [S(0), S(1), S(2)];
    S[] arr2 = [];
    S[] arr3 = [S(6), S(7), S(8)];
    S elem = S(9);
    S[] result = _d_arraycatnTX!(S[])(arr1, arr2, arr3, elem);

    assert(counter == 7);
    assert(result == [S(0), S(1), S(2), S(6), S(7), S(8), S(9)]);
}

// copy constructor
@safe unittest
{
    int counter;
    struct S
    {
        int val;
        this(ref return scope S rhs)
        {
            val = rhs.val;
            counter++;
        }
    }

    S[] arr1 = [S(0), S(1), S(2)];
    S[] arr2 = [S(3), S(4), S(5)];
    S[] arr3 = [S(6), S(7), S(8)];
    S elem = S(9);
    S[] result = _d_arraycatnTX!(S[])(arr1, elem, arr2, arr3);

    assert(counter == 10);
    assert(result == [S(0), S(1), S(2), S(9), S(3), S(4), S(5), S(6), S(7), S(8)]);
}

// throwing
@safe unittest
{
    int counter;
    bool didThrow;
    struct S
    {
        int val;
        this(this)
        {
            counter++;
            if (counter == 4)
                throw new Exception("");
        }
    }

    try
    {
        S[] arr1 = [S(0), S(1), S(2)];
        S[] arr2 = [S(3), S(4), S(5)];
        _d_arraycatnTX!(S[])(arr1, arr2);
    }
    catch (Exception)
    {
        didThrow = true;
    }

    assert(counter == 4);
    assert(didThrow);
}
