/**
 This module contains compiler support for casting dynamic arrays

  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/_casting.d)
*/
module core.internal.array.casting;

/**
Used by `__ArrayCast` to emit a descriptive error message.

It is a template so it can be used by `__ArrayCast` in -betterC
builds.  It is separate from `__ArrayCast` to minimize code
bloat.

Params:
    fromType   = name of the type being cast from
    fromSize   = total size in bytes of the array being cast from
    fromLength = length of array being cast from
    toType     = name of the type being cast to
    toElemSize = element size of array being cast to
 */
private void onArrayCastError()(string fromType, size_t fromSize, size_t fromLength, string toType, size_t toElemSize) @trusted
{
    import core.internal.string : unsignedToTempString;
    import core.memory : pureMalloc;

    // convert discontiguous `msgComponents` to contiguous string on the C heap
    enum msgLength = 2048;
    // note: never freed!
    char* msg = cast(char *)pureMalloc(msgLength);

    size_t index = 0;
    void add(const(char)[] m)
    {
        import core.stdc.string : memcpy;

        auto N = msgLength - 1 - index;
        if (N > m.length)
            N = m.length;
        // prevent superfluous and betterC-unfriendly checks via direct memcpy
        memcpy(msg + index, m.ptr, N);
        index += N;
    }

    add("`");
    add(fromType);
    add("[]` of length ");
    auto s = unsignedToTempString(fromLength);
    add(s[]);
    add(" cannot be cast to `");
    add(toType);
    add("[]` as its length in bytes (");
    s = unsignedToTempString(fromSize);
    add(s[]);
    add(") is not a multiple of `");
    add(toType);
    add(".sizeof` (");
    s = unsignedToTempString(toElemSize);
    add(s[]);
    add(").");
    msg[index] = '\0'; // null-termination

    // first argument must evaluate to `false` at compile-time to maintain memory safety in release builds
    assert(false, msg[0 .. index]);
}

/**
The compiler lowers expressions of `cast(TTo[])TFrom[]` to
this implementation. Note that this does not detect alignment problems.

Params:
    from = the array to reinterpret-cast

Returns:
    `from` reinterpreted as `TTo[]`
 */
TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) @nogc pure @trusted
{
    const fromSize = from.length * TFrom.sizeof;
    const toLength = fromSize / TTo.sizeof;

    if ((fromSize % TTo.sizeof) != 0)
    {
        onArrayCastError(TFrom.stringof, fromSize, from.length, TTo.stringof, TTo.sizeof);
    }

    struct Array
    {
        size_t length;
        void* ptr;
    }
    auto a = cast(Array*)&from;
    a.length = toLength; // jam new length
    return *cast(TTo[]*)a;
}

@safe @nogc pure nothrow unittest
{
    byte[int.sizeof * 3] b = cast(byte) 0xab;
    int[] i;
    short[] s;

    i = __ArrayCast!(byte, int)(b);
    assert(i.length == 3);
    foreach (v; i)
        assert(v == cast(int) 0xabab_abab);

    s = __ArrayCast!(byte, short)(b);
    assert(s.length == 6);
    foreach (v; s)
        assert(v == cast(short) 0xabab);

    s = __ArrayCast!(int, short)(i);
    assert(s.length == 6);
    foreach (v; s)
        assert(v == cast(short) 0xabab);
}

@system nothrow unittest
{
    string msg;
    try
    {
        auto str = "hello";
        auto wstr = cast(wstring) str;
    }
    catch (Throwable t)
        msg = t.msg;

    static immutable expected = "`immutable(char)[]` of length 5 cannot be cast to `immutable(wchar)[]` as " ~
                            "its length in bytes (5) is not a multiple of `immutable(wchar).sizeof` (2).";

    if (msg != expected)
    {
        import core.stdc.stdio : printf;
        printf("Expected: |%.*s|\n", cast(int) expected.length, expected.ptr);
        printf("Actual  : |%.*s|\n", cast(int) msg.length, msg.ptr);
        assert(false);
    }
}
