/**
 * Implementation of array assignment support routines.
 *
 *
 * Copyright: Copyright Digital Mars 2010 - 2016.
 * License:   Distributed under the
 *            $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
 * Authors:   Walter Bright, Kenji Hara
 * Source:    $(DRUNTIMESRC rt/_arrayassign.d)
 */

module rt.arrayassign;

private
{
    import rt.util.array;
    import core.stdc.string;
    import core.stdc.stdlib;
    debug(PRINTF) import core.stdc.stdio;
}

/**
 * Keep for backward binary compatibility. This function can be removed in the future.
 */
extern (C) void[] _d_arrayassign(TypeInfo ti, void[] from, void[] to)
{
    debug(PRINTF) printf("_d_arrayassign(from = %p,%d, to = %p,%d) size = %d\n", from.ptr, from.length, to.ptr, to.length, ti.tsize);

    immutable elementSize = ti.tsize;

    // Need a temporary buffer tmp[] big enough to hold one element
    void[16] buf = void;
    void* ptmp = (elementSize > buf.sizeof) ? malloc(elementSize) : buf.ptr;
    scope (exit)
    {
        if (ptmp != buf.ptr)
            free(ptmp);
    }
    return _d_arrayassign_l(ti, from, to, ptmp);
}

/**
 * Does array assignment (not construction) from another
 * lvalue array of the same element type.
 * Handles overlapping copies.
 * Input:
 *      ti      TypeInfo of the element type.
 *      dst     Points target memory. Its .length is equal to the element count, not byte length.
 *      src     Points source memory. Its .length is equal to the element count, not byte length.
 *      ptmp    Temporary memory for element swapping.
 */
extern (C) void[] _d_arrayassign_l(TypeInfo ti, void[] src, void[] dst, void* ptmp)
{
    debug(PRINTF) printf("_d_arrayassign_l(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize);

    immutable elementSize = ti.tsize;

    enforceRawArraysConformable("copy", elementSize, src, dst, true);

    if (src.ptr < dst.ptr && dst.ptr < src.ptr + elementSize * src.length)
    {
        // If dst is in the middle of src memory, use reverse order.
        for (auto i = dst.length; i--; )
        {
            void* pdst = dst.ptr + i * elementSize;
            void* psrc = src.ptr + i * elementSize;
            memcpy(ptmp, pdst, elementSize);
            memcpy(pdst, psrc, elementSize);
            ti.postblit(pdst);
            ti.destroy(ptmp);
        }
    }
    else
    {
        // Otherwise, use normal order.
        foreach (i; 0 .. dst.length)
        {
            void* pdst = dst.ptr + i * elementSize;
            void* psrc = src.ptr + i * elementSize;
            memcpy(ptmp, pdst, elementSize);
            memcpy(pdst, psrc, elementSize);
            ti.postblit(pdst);
            ti.destroy(ptmp);
        }
    }
    return dst;
}

unittest    // Bugzilla 14024
{
    string op;

    struct S
    {
        char x = 'x';
        this(this) { op ~= x-0x20; }    // upper case
        ~this()    { op ~= x; }         // lower case
    }

    S[4] mem;
    ref S[2] slice(int a, int b) { return mem[a .. b][0 .. 2]; }

    op = null;
    mem[0].x = 'a';
    mem[1].x = 'b';
    mem[2].x = 'x';
    mem[3].x = 'y';
    slice(0, 2) = slice(2, 4);  // [ab] = [xy]
    assert(op == "XaYb", op);

    op = null;
    mem[0].x = 'x';
    mem[1].x = 'y';
    mem[2].x = 'a';
    mem[3].x = 'b';
    slice(2, 4) = slice(0, 2);  // [ab] = [xy]
    assert(op == "XaYb", op);

    op = null;
    mem[0].x = 'a';
    mem[1].x = 'b';
    mem[2].x = 'c';
    slice(0, 2) = slice(1, 3);  // [ab] = [bc]
    assert(op == "BaCb", op);

    op = null;
    mem[0].x = 'x';
    mem[1].x = 'y';
    mem[2].x = 'z';
    slice(1, 3) = slice(0, 2);  // [yz] = [xy]
    assert(op == "YzXy", op);
}

/**
 * Does array assignment (not construction) from another
 * rvalue array of the same element type.
 * Input:
 *      ti      TypeInfo of the element type.
 *      dst     Points target memory. Its .length is equal to the element count, not byte length.
 *      src     Points source memory. Its .length is equal to the element count, not byte length.
 *              It is always allocated on stack and never overlapping with dst.
 *      ptmp    Temporary memory for element swapping.
 */
extern (C) void[] _d_arrayassign_r(TypeInfo ti, void[] src, void[] dst, void* ptmp)
{
    debug(PRINTF) printf("_d_arrayassign_r(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize);

    immutable elementSize = ti.tsize;

    enforceRawArraysConformable("copy", elementSize, src, dst, false);

    // Always use normal order, because we can assume that
    // the rvalue src has no overlapping with dst.
    foreach (i; 0 .. dst.length)
    {
        void* pdst = dst.ptr + i * elementSize;
        void* psrc = src.ptr + i * elementSize;
        memcpy(ptmp, pdst, elementSize);
        memcpy(pdst, psrc, elementSize);
        ti.destroy(ptmp);
    }
    return dst;
}

/**
 * Does array initialization (not assignment) from another
 * array of the same element type.
 * ti is the element type.
 */
extern (C) void[] _d_arrayctor(TypeInfo ti, void[] from, void[] to)
{
    debug(PRINTF) printf("_d_arrayctor(from = %p,%d, to = %p,%d) size = %d\n", from.ptr, from.length, to.ptr, to.length, ti.tsize);


    auto element_size = ti.tsize;

    enforceRawArraysConformable("initialization", element_size, from, to);

    size_t i;
    try
    {
        for (i = 0; i < to.length; i++)
        {
            // Copy construction is defined as bit copy followed by postblit.
            memcpy(to.ptr + i * element_size, from.ptr + i * element_size, element_size);
            ti.postblit(to.ptr + i * element_size);
        }
    }
    catch (Throwable o)
    {
        /* Destroy, in reverse order, what we've constructed so far
         */
        while (i--)
        {
            ti.destroy(to.ptr + i * element_size);
        }

        throw o;
    }
    return to;
}


/**
 * Do assignment to an array.
 *      p[0 .. count] = value;
 */
extern (C) void* _d_arraysetassign(void* p, void* value, int count, TypeInfo ti)
{
    void* pstart = p;

    auto element_size = ti.tsize;

    // Need a temporary buffer tmp[] big enough to hold one element
    immutable maxAllocaSize = 512;
    void *ptmp = (element_size > maxAllocaSize) ? malloc(element_size) : alloca(element_size);

    foreach (i; 0 .. count)
    {
        memcpy(ptmp, p, element_size);
        memcpy(p, value, element_size);
        ti.postblit(p);
        ti.destroy(ptmp);
        p += element_size;
    }
    if (element_size > maxAllocaSize)
        free(ptmp);
    return pstart;
}

/**
 * Do construction of an array.
 *      ti[count] p = value;
 */
extern (C) void* _d_arraysetctor(void* p, void* value, int count, TypeInfo ti)
{
    void* pstart = p;
    auto element_size = ti.tsize;

    try
    {
        foreach (i; 0 .. count)
        {
            // Copy construction is defined as bit copy followed by postblit.
            memcpy(p, value, element_size);
            ti.postblit(p);
            p += element_size;
        }
    }
    catch (Throwable o)
    {
        // Destroy, in reverse order, what we've constructed so far
        while (p > pstart)
        {
            p -= element_size;
            ti.destroy(p);
        }

        throw o;
    }
    return pstart;
}
