module core.internal.array.arrayassign;

// Force `enforceRawArraysConformable` to remain `pure` `@nogc`
private void enforceRawArraysConformable(const char[] action, const size_t elementSize,
    const void[] a1, const void[] a2, const bool allowOverlap) @trusted @nogc pure nothrow
{
    import core.internal.util.array : enforceRawArraysConformable;

    alias Type = void function(const char[] action, const size_t elementSize,
        const void[] a1, const void[] a2, in bool allowOverlap = false) @nogc pure nothrow;
    (cast(Type)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap);
}

private template CopyElem(string CopyAction)
{
    const char[] CopyElem = "{\n" ~ q{
            memcpy(&tmp, cast(void*) &dst, elemSize);
            } ~ CopyAction ~ q{
            auto elem = cast(Unqual!T*) &tmp;
            destroy(*elem);
        } ~ "}\n";
}

private template CopyArray(bool CanOverlap, string CopyAction)
{
    const char[] CopyArray = CanOverlap ? q{
        if (vFrom.ptr < vTo.ptr && vTo.ptr < vFrom.ptr + elemSize * vFrom.length)
            foreach_reverse (i, ref dst; to)
            } ~ CopyElem!(CopyAction) ~ q{
        else
            foreach (i, ref dst; to)
            } ~ CopyElem!(CopyAction)
        : q{
            foreach (i, ref dst; to)
            } ~ CopyElem!(CopyAction);
}

private template ArrayAssign(string CopyLogic, string AllowOverLap)
{
    const char[] ArrayAssign = q{
        import core.internal.traits : hasElaborateCopyConstructor, Unqual;
        import core.lifetime : copyEmplace;
        import core.stdc.string : memcpy;

        void[] vFrom = (cast(void*) from.ptr)[0 .. from.length];
        void[] vTo = (cast(void*) to.ptr)[0 .. to.length];
        enum elemSize = T.sizeof;

        enforceRawArraysConformable("copy", elemSize, vFrom, vTo, } ~ AllowOverLap ~ q{);

        void[elemSize] tmp = void;

        } ~ CopyLogic ~ q{

        return to;
    };
}

/**
 * Does array assignment (not construction) from another array of the same
 * element type. Handles overlapping copies. Assumes the right hand side is an
 * lvalue,
 *
 * Used for static array assignment with non-POD element types:
 * ---
 * struct S
 * {
 *     ~this() {} // destructor, so not Plain Old Data
 * }
 *
 * void main()
 * {
 *   S[3] arr;
 *   S[3] lvalue;
 *
 *   arr = lvalue;
 *   // Generates:
 *   // _d_arrayassign_l(arr[], lvalue[]), arr;
 * }
 * ---
 *
 * Params:
 *     to = destination array
 *     from = source array
 * Returns:
 *     `to`
 */
Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted
{
    mixin(ArrayAssign!(q{
        static if (hasElaborateCopyConstructor!T)
            } ~ CopyArray!(true, "copyEmplace(from[i], dst);") ~ q{
        else
            } ~ CopyArray!(true, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"),
        "true"));
}

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

    S[4] arr1;
    S[4] arr2 = [S(0), S(1), S(2), S(3)];
    _d_arrayassign_l(arr1[], arr2[]);

    assert(counter == 4);
    assert(arr1 == arr2);
}

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

    S[4] arr1;
    S[4] arr2 = [S(0), S(1), S(2), S(3)];
    _d_arrayassign_l(arr1[], arr2[]);

    assert(counter == 4);
    assert(arr1 == arr2);
}

@safe nothrow unittest
{
    // Test that throwing works
    int counter;
    bool didThrow;

    struct Throw
    {
        int val;
        this(this)
        {
            counter++;
            if (counter == 2)
                throw new Exception("");
        }
    }
    try
    {
        Throw[4] a;
        Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)];
        _d_arrayassign_l(a[], b[]);
    }
    catch (Exception)
    {
        didThrow = true;
    }
    assert(didThrow);
    assert(counter == 2);


    // Test that `nothrow` works
    didThrow = false;
    counter = 0;
    struct NoThrow
    {
        int val;
        this(this)
        {
            counter++;
        }
    }
    try
    {
        NoThrow[4] a;
        NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)];
        _d_arrayassign_l(a[], b[]);
    }
    catch (Exception)
    {
        didThrow = false;
    }
    assert(!didThrow);
    assert(counter == 4);
}

/**
 * Does array assignment (not construction) from another array of the same
 * element type. Does not support overlapping copies. Assumes the right hand
 * side is an rvalue,
 *
 * Used for static array assignment with non-POD element types:
 * ---
 * struct S
 * {
 *     ~this() {} // destructor, so not Plain Old Data
 * }
 *
 * void main()
 * {
 *   S[3] arr;
 *   S[3] getRvalue() {return lvalue;}
 *
 *   arr = getRvalue();
 *   // Generates:
 *   // (__appendtmp = getRvalue), _d_arrayassign_l(arr[], __appendtmp), arr;
 * }
 * ---
 *
 * Params:
 *     to = destination array
 *     from = source array
 * Returns:
 *     `to`
 */
Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted
{
    mixin(ArrayAssign!(
        CopyArray!(false, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"),
        "false"));
}

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

    S[4] arr1;
    S[4] arr2 = [S(0), S(1), S(2), S(3)];
    _d_arrayassign_r(arr1[], arr2[]);

    assert(counter == 0);
    assert(arr1 == arr2);
}

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

    S[4] arr1;
    S[4] arr2 = [S(0), S(1), S(2), S(3)];
    _d_arrayassign_r(arr1[], arr2[]);

    assert(counter == 0);
    assert(arr1 == arr2);
}

@safe nothrow unittest
{
    // Test that `nothrow` works
    bool didThrow = false;
    int counter = 0;
    struct NoThrow
    {
        int val;
        this(this)
        {
            counter++;
        }
    }
    try
    {
        NoThrow[4] a;
        NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)];
        _d_arrayassign_r(a[], b[]);
    }
    catch (Exception)
    {
        didThrow = false;
    }
    assert(!didThrow);
    assert(counter == 0);
}

/**
 * Sets all elements of an array to a single value. Takes into account postblits,
 * copy constructors and destructors. For Plain Old Data elements,`rt/memset.d`
 * is used.
 *
 * ---
 * struct S
 * {
 *     ~this() {} // destructor, so not Plain Old Data
 * }
 *
 * void main()
 * {
 *   S[3] arr;
 *   S value;
 *
 *   arr = value;
 *   // Generates:
 *   // _d_arraysetassign(arr[], value), arr;
 * }
 * ---
 *
 * Params:
 *     to = destination array
 *     value = the element to set
 * Returns:
 *     `to`
 */
Tarr _d_arraysetassign(Tarr : T[], T)(return scope Tarr to, scope ref T value) @trusted
{
    import core.internal.traits : Unqual;
    import core.lifetime : copyEmplace;
    import core.stdc.string : memcpy;

    enum elemSize = T.sizeof;
    void[elemSize] tmp = void;

    foreach (ref dst; to)
    {
        memcpy(&tmp, cast(void*) &dst, elemSize);
        // Use `memcpy` if `T` has a `@disable`d postblit.
        static if (__traits(isCopyable, T))
            copyEmplace(value, dst);
        else
            memcpy(cast(void*) &value, cast(void*) &dst, elemSize);
        auto elem = cast(Unqual!T*) &tmp;
        destroy(*elem);
    }

    return to;
}

// postblit and destructor
@safe unittest
{
    string ops;
    struct S
    {
        int val;
        this(this) { ops ~= "="; }
        ~this() { ops ~= "~"; }
    }

    S[4] arr;
    S s = S(1234);
    _d_arraysetassign(arr[], s);
    assert(ops == "=~=~=~=~");
    assert(arr == [S(1234), S(1234), S(1234), S(1234)]);
}

// copy constructor
@safe unittest
{
    string ops;
    struct S
    {
        int val;
        this(const scope ref S rhs)
        {
            val = rhs.val;
            ops ~= "=";
        }
        ~this() { ops ~= "~"; }
    }

    S[4] arr;
    S s = S(1234);
    _d_arraysetassign(arr[], s);
    assert(ops == "=~=~=~=~");
    assert(arr == [S(1234), S(1234), S(1234), S(1234)]);
}

// throwing and `nothrow`
@safe nothrow unittest
{
    // Test that throwing works
    bool didThrow;
    int counter;
    struct Throw
    {
        int val;
        this(this)
        {
            counter++;
            if (counter == 2)
                throw new Exception("Oh no.");
        }
    }

    try
    {
        Throw[4] a;
        Throw b = Throw(1);
        _d_arraysetassign(a[], b);
    }
    catch (Exception)
    {
        didThrow = true;
    }
    assert(didThrow);
    assert(counter == 2);

    // Test that `nothrow` works
    didThrow = false;
    counter = 0;
    struct NoThrow
    {
        int val;
        this(this) { counter++; }
    }

    try
    {
        NoThrow[4] a;
        NoThrow b = NoThrow(1);
        _d_arraysetassign(a[], b);
        foreach (ref e; a)
            assert(e == NoThrow(1));
    }
    catch (Exception)
    {
        didThrow = true;
    }
    assert(!didThrow);
    // The array `a` is destroyed when the `try` block ends.
    assert(counter == 4);
}
