/**
 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, Tarr...)(auto ref Tarr froms) @trusted
{
    import core.internal.array.capacity : _d_arraysetlengthTImpl;
    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]);
    enum elemSize = T.sizeof;
    enum hasPostblit = __traits(hasPostblit, T);

    static foreach (from; froms)
        static if (is (typeof(from) : T))
            totalLen++;
        else
            totalLen += from.length;

    if (totalLen == 0)
        return res;

    // We cannot use this, because it refuses to work if the array type has disabled default construction.
    // res.length = totalLen;
    // Call the runtime function directly instead.
    // TODO: once `__arrayAlloc` is templated, call that instead.
    version (D_ProfileGC)
    {
        // TODO: forward file, line, name from _d_arraycatnTXTrace
        _d_arraysetlengthTImpl!(typeof(res))._d_arraysetlengthTTrace(
            res, totalLen, __FILE__, __LINE__, __FUNCTION__);
    }
    else
    {
        _d_arraysetlengthTImpl!(typeof(res))._d_arraysetlengthT(res, totalLen);
    }

    /* Currently, if both a postblit and a cpctor are defined, the postblit is
     * used. If this changes, the condition below will have to be adapted.
     */
    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(Unqual!T *) res;
        foreach (ref from; froms)
            static if (is (typeof(from) : T))
                memcpy(resptr++, cast(Unqual!T *) &from, elemSize);
            else
            {
                const len = from.length;
                if (len)
                {
                    memcpy(resptr, cast(Unqual!T *) 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);
}

version (D_ProfileGC)
{
    /**
    * TraceGC wrapper around $(REF _d_arraycatnTX, core,internal,array,concatenation).
    */
    Tret _d_arraycatnTXTrace(Tret, Tarr...)(scope auto ref Tarr froms, string file = __FILE__, int line = __LINE__, string funcname = __FUNCTION__) @trusted
    {
        version (D_TypeInfo)
        {
            import core.internal.array.utils: TraceHook, gcStatsPure, accumulatePure;
            mixin(TraceHook!(Tarr.stringof, "_d_arraycatnTX"));

            import core.lifetime: forward;
            return _d_arraycatnTX!Tret(forward!froms);
        }
        else
            assert(0, "Cannot concatenate arrays if compiling without support for runtime type information!");
    }
}
