# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test
isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl")
using .Main.OffsetArrays
isdefined(Main, :TSlow) || @eval Main include("testhelpers/arrayindexingtypes.jl")
using .Main: TSlow, WrapperArray
isdefined(Main, :StridedArrays) || @eval Main include("testhelpers/StridedArrays.jl")
using .Main.StridedArrays

tslow(a::AbstractArray) = TSlow(a)
wrapper(a::AbstractArray) = WrapperArray(a)
fcviews(a::AbstractArray) = view(a, ntuple(Returns(:),ndims(a)-1)..., axes(a)[end])
fcviews(a::AbstractArray{<:Any, 0}) = view(a)
offset_nominal(a::AbstractArray) = OffsetArray(a)
offset_maybe(a::AbstractArray) = (eltype(a) <: Real) ? a : OffsetArray(a, (1-ndims(A)):2:(ndims(A)-1)...)
tslow(t::Tuple) = map(tslow, t)
wrapper(t::Tuple) = map(wrapper, t)
fcviews(t::Tuple) = map(fcviews, t)
offset_nominal(t::Tuple) = map(offset_nominal, t)
offset_maybe(t::Tuple) = map(offset_maybe, t)

test_many_wrappers(testf, A, wrappers) = foreach(w -> testf(w(A)), wrappers)
test_many_wrappers(testf, A) = test_many_wrappers(
    testf, A, (identity, tslow, wrapper, fcviews, offset_nominal, offset_maybe)
)

A = Int64[1, 2, 3, 4]
Ars = Int64[1 3; 2 4]
B = Complex{Int64}[5+6im, 7+8im, 9+10im]
Av = [Int32[1,2], Int32[3,4]]
C = view([1,1], [1,2])

test_many_wrappers(Ars, (identity, tslow)) do Ar
    @test @inferred(ndims(reinterpret(reshape, Complex{Int64}, Ar))) == 1
    @test @inferred(axes(reinterpret(reshape, Complex{Int64}, Ar))) === (Base.OneTo(2),)
    @test @inferred(size(reinterpret(reshape, Complex{Int64}, Ar))) == (2,)
end
test_many_wrappers(B, (identity, tslow)) do _B
    @test @inferred(ndims(reinterpret(reshape, Int64, _B))) == 2
    @test @inferred(axes(reinterpret(reshape, Int64, _B))) === (Base.OneTo(2), Base.OneTo(3))
    @test @inferred(size(reinterpret(reshape, Int64, _B))) == (2, 3)
    @test @inferred(ndims(reinterpret(reshape, Int128, _B))) == 1
    @test @inferred(axes(reinterpret(reshape, Int128, _B))) === (Base.OneTo(3),)
    @test @inferred(size(reinterpret(reshape, Int128, _B))) == (3,)
end

@test_throws ArgumentError("cannot reinterpret `Int64` as `Vector{Int64}`, type `Vector{Int64}` is not a bits type") reinterpret(Vector{Int64}, A)
@test_throws ArgumentError("cannot reinterpret `Vector{Int32}` as `Int32`, type `Vector{Int32}` is not a bits type") reinterpret(Int32, Av)
@test_throws ArgumentError("cannot reinterpret a zero-dimensional `Int64` array to `Int32` which is of a different size") reinterpret(Int32, reshape([Int64(0)]))
@test_throws ArgumentError("cannot reinterpret a zero-dimensional `Int32` array to `Int64` which is of a different size") reinterpret(Int64, reshape([Int32(0)]))
@test_throws ArgumentError("cannot reinterpret an `$Int` array to `Tuple{$Int, $Int}` whose first dimension has size `5`."*
                              " The resulting array would have a non-integral first dimension.") reinterpret(Tuple{Int,Int}, [1,2,3,4,5])

@test_throws ArgumentError("`reinterpret(reshape, Complex{Int64}, a)` where `eltype(a)` is Int64 requires that `axes(a, 1)` (got Base.OneTo(4)) be equal to 1:2 (from the ratio of element sizes)") reinterpret(reshape, Complex{Int64}, A)
@test_throws ArgumentError("`reinterpret(reshape, T, a)` requires that one of `sizeof(T)` (got 24) and `sizeof(eltype(a))` (got 16) be an integer multiple of the other") reinterpret(reshape, NTuple{3, Int64}, B)
@test_throws ArgumentError("cannot reinterpret `Int64` as `Vector{Int64}`, type `Vector{Int64}` is not a bits type") reinterpret(reshape, Vector{Int64}, Ars)
@test_throws ArgumentError("cannot reinterpret a zero-dimensional `UInt8` array to `UInt16` which is of a larger size") reinterpret(reshape, UInt16, reshape([0x01]))

# getindex
test_many_wrappers(A) do _A
    @test reinterpret(Complex{Int64}, _A) == [1 + 2im, 3 + 4im]
    @test reinterpret(Float64, _A) == reinterpret.(Float64, A)
    @test reinterpret(reshape, Float64, _A) == reinterpret.(Float64, A)
end
test_many_wrappers(Ars) do Ar
    @test reinterpret(reshape, Complex{Int64}, Ar) == [1 + 2im, 3 + 4im]
    @test reinterpret(reshape, Float64, Ar) == reinterpret.(Float64, Ars)
end

test_many_wrappers(B) do _B
    @test reinterpret(NTuple{3, Int64}, _B) == [(5,6,7),(8,9,10)]
    @test reinterpret(reshape, Int64, _B) == [5 7 9; 6 8 10]
end

@testset "setindex! converts before reinterpreting" begin
    for dims in ((), 1)
        z = reinterpret(UInt64, fill(1.0, dims))
        @test z[] == z[1] == 0x3ff0000000000000
        z[] = Int32(1)//Int32(1)
        @test z[] == z[1] == 0x0000000000000001
        z[1] = Int32(2)//Int32(1)
        @test z[] == z[1] == 0x0000000000000002
        z[1] = 3//1
        @test z[] == z[1] == 0x0000000000000003
        @test_throws InexactError z[] = 3//2
        @test_throws InexactError z[] = 1.5
        @test_throws InexactError z[1] = 3//2
        @test_throws InexactError z[1] = 1.5

        z = reinterpret(UInt64, fill(Int32(16)//Int32(1), dims))
        @test z[] == z[1] == 0x0000000100000010
        z[] = Int32(1)//Int32(1)
        @test z[] == z[1] == 0x0000000000000001
        z[1] = Int32(2)//Int32(1)
        @test z[] == z[1] == 0x0000000000000002
        z[1] = 3//1
        @test z[] == z[1] == 0x0000000000000003
        @test_throws InexactError z[] = 3//2
        @test_throws InexactError z[] = 1.5
        @test_throws InexactError z[1] = 3//2
        @test_throws InexactError z[1] = 1.5

        z = reinterpret(Missing, fill(nothing, dims))
        @test z[] === missing
        @test z[1] === missing
        @test_throws "cannot convert" z[] = nothing
        @test_throws "cannot convert" z[1] = nothing
        @test z[] === missing
        @test z[1] === missing
    end
end

# setindex
test_many_wrappers((A, Ars, B)) do (A, Ars, B)
    _A, Ar, _B = deepcopy(A), deepcopy(Ars), deepcopy(B)
    let Ac = deepcopy(_A), Arsc = deepcopy(Ar), Bc = deepcopy(_B)
        reinterpret(Complex{Int64}, Ac)[2] = -1 - 2im
        @test Ac == [1, 2, -1, -2]
        reinterpret(Complex{Int64}, Arsc)[2] = -1 - 2im
        @test Arsc == [1 -1; 2 -2]
        reinterpret(NTuple{3, Int64}, Bc)[2] = (4,5,6)
        @test Bc == Complex{Int64}[5+6im, 7+4im, 5+6im]
        B2 = reinterpret(NTuple{3, Int64}, Bc)
        @test setindex!(B2, (1,2,3), 1) === B2
        @test Bc == Complex{Int64}[1+2im, 3+4im, 5+6im]
        Bc = copy(_B)
        Brrs = reinterpret(reshape, Int64, Bc)
        @test setindex!(Brrs, -5, 2, 3) === Brrs
        @test Bc == Complex{Int64}[5+6im, 7+8im, 9-5im]
        Brrs[last(eachindex(Brrs))] = 22
        @test Bc == Complex{Int64}[5+6im, 7+8im, 9+22im]

        A1 = reinterpret(Float64, _A)
        A2 = reinterpret(ComplexF64, _A)
        @test setindex!(A1, 1.0, 1) === A1
        @test real(A2[1]) == 1.0
        A1 = reinterpret(reshape, Float64, _A)
        @test setindex!(A1, 2.5, 1) === A1
        @test reinterpret(Float64, _A[1]) == 2.5
        A1rs = reinterpret(Float64, Ar)
        A2rs = reinterpret(ComplexF64, Ar)
        @test setindex!(A1rs, 1.0, 1, 1) === A1rs
        @test real(A2rs[1]) == 1.0
        A1rs = reinterpret(reshape, Float64, Ar)
        A2rs = reinterpret(reshape, ComplexF64, Ar)
        @test setindex!(A1rs, 2.5, 1, 1) === A1rs
        @test real(A2rs[1]) == 2.5
    end
end
A3 = collect(reshape(1:18, 2, 3, 3))
test_many_wrappers(A3) do A3_
    A3 = deepcopy(A3_)
    A3r = reinterpret(reshape, Complex{Int}, A3)
    @test A3r[4] === A3r[1,2] === A3r[CartesianIndex(1, 2)] === 7+8im
    A3r[2,3] = -8-15im
    @test A3[1,2,3] == -8
    @test A3[2,2,3] == -15
    A3r[4] = 100+200im
    @test A3[1,1,2] == 100
    @test A3[2,1,2] == 200
    A3r[CartesianIndex(1,2)] = 300+400im
    @test A3[1,1,2] == 300
    @test A3[2,1,2] == 400
end

test_many_wrappers(C) do Cr_
    Cr = deepcopy(Cr_)
    r = reinterpret(reshape, Tuple{Int, Int}, Cr)
    @test r == fill((1,1))
    r[] = (2,2)
    @test r[] === (2,2)
    r[1] = (3,3)
    @test r[1] === (3,3)
    r[1,1] = (4,4)
    @test r[1,1] === (4,4)
end

# same-size reinterpret where one of the types is non-primitive
let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)]
    test_many_wrappers(a, (identity, wrapper, fcviews)) do a_
        a = deepcopy(a_)
        ra = reinterpret(Float32, a)
        @test ra[1] == reinterpret(Float32, 0x04030201)
        @test setindex!(ra, 2.0) === ra
        @test reinterpret(Float32, a)[1] == 2.0
    end
end
let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)]
    test_many_wrappers(a, (identity, wrapper, fcviews)) do a_
        a = deepcopy(a_)
        ra = reinterpret(reshape, Float32, a)
        @test ra[1] == reinterpret(Float32, 0x04030201)
        @test setindex!(ra, 2.0) === ra
        @test reinterpret(reshape, Float32, a)[1] == 2.0
    end
end

# Pass-through indexing
B = Complex{Int64}[5+6im, 7+8im, 9+10im]
test_many_wrappers(B) do B_
    B = deepcopy(B_)
    Br = reinterpret(reshape, Int64, B)
    W = WrapperArray(Br)
    for (b, w) in zip(5:10, W)
        @test b == w
    end
    for (i, j) in zip(eachindex(W), 11:16)
        W[i] = j
    end
    @test B[1] === Complex{Int64}(11+12im)
    @test B[2] === Complex{Int64}(13+14im)
    @test B[3] === Complex{Int64}(15+16im)
end
z3 = (0x00, 0x00, 0x00)
Az = [z3 z3; z3 z3]
test_many_wrappers(Az, (identity, wrapper)) do Az_
    Az = deepcopy(Az_)
    Azr = reinterpret(reshape, UInt8, Az)
    W = WrapperArray(Azr)
    copyto!(W, fill(0x01, 3, 2, 2))
    @test all(isequal((0x01, 0x01, 0x01)), Az)
    @test eachindex(W, W) == eachindex(W)
end

# ensure that reinterpret arrays aren't erroneously classified as strided
let A = reshape(1:20, 5, 4)
    V = view(A, :, :)
    R = reinterpret(Int32, V)
    R2 = reinterpret(Int32, A)
    @test !(R isa StridedArray)
    @test !(R2 isa StridedArray)
    @test R * ones(4, 5) == R2 * ones(4,5) == copy(R) * ones(4,5) == copy(R2) * ones(4,5)
end

# but ensure that strided views of strided reinterpret arrays are still strided
let A = collect(reshape(1:20, 5, 4))
    R = reinterpret(Int32, A)
    @test R isa StridedArray
    @test view(R, :, :) isa StridedArray
    @test reshape(R, :) isa StridedArray
end

@testset "strides for NonReshapedReinterpretArray" begin
    A = WrapperArray(Array{Int32}(reshape(1:88, 11, 8)))
    for viewax2 in (1:8, 1:2:6, 7:-1:1, 5:-2:1, 2:3:8, 7:-6:1, 3:5:11)
        # dim1 is contiguous
        for T in (Int16, Float32)
            check_strided_get(reinterpret(T, view(A, 1:8, viewax2)))
        end
        if mod(step(viewax2), 2) == 0
            check_strided_get(reinterpret(Int64, view(A, 1:8, viewax2)))
        else
            check_strides_throws("Parent's strides", reinterpret(Int64, view(A, 1:8, viewax2)))
        end
        # non-integer-multiplied classified
        if mod(step(viewax2), 3) == 0
            check_strided_get(reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2)))
        else
            check_strides_throws("Parent's strides", reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2)))
        end
        if mod(step(viewax2), 5) == 0
            check_strided_get(reinterpret(NTuple{5,Int16}, view(A, 2:11, viewax2)))
        else
            check_strides_throws("Parent's strides", reinterpret(NTuple{5,Int16}, view(A, 2:11, viewax2)))
        end
        # dim1 is not contiguous
        for T in (Int16, Int64)
            check_strides_throws("Parent must", reinterpret(T, view(A, 8:-1:1, viewax2)))
        end
        check_strided_get(reinterpret(Float32, view(A, 8:-1:1, viewax2)))
    end
    # issue 46113
    A = reinterpret(Int8, reinterpret(reshape, Int16, rand(Int8, 2, 3, 3)))
    check_strided_get(A)
end

@testset "strides for ReshapedReinterpretArray" begin
    A = WrapperArray(Array{Int32}(reshape(1:192, 3, 8, 8)))
    for viewax1 in (1:8, 1:2:8, 8:-1:1, 8:-2:1), viewax2 in (1:2, 4:-1:1)
        for T in (Int16, Float32)
            check_strided_get(reinterpret(reshape, T, view(A, 1:2, viewax1, viewax2)))
            check_strided_get(reinterpret(reshape, T, view(A, 1:2:3, viewax1, viewax2)))
        end
        if mod(step(viewax1), 2) == 0
            check_strided_get(reinterpret(reshape, Int64, view(A, 1:2, viewax1, viewax2)))
        else
            @test_throws "Parent's strides" strides(reinterpret(reshape, Int64, view(A, 1:2, viewax1, viewax2)))
        end
        @test_throws "Parent must" strides(reinterpret(reshape, Int64, view(A, 1:2:3, viewax1, viewax2)))
    end
end

# IndexStyle
test_many_wrappers(fill(1.0, 5, 3), (identity, wrapper)) do a_
    a = deepcopy(a_)
    r = reinterpret(Int64, a)
    @test @inferred(IndexStyle(r)) == IndexLinear()
    fill!(r, 2)
    @test all(a .=== reinterpret(Float64, [Int64(2)])[1])
    @test all(r .=== Int64(2))
    for badinds in ((), 0, 16, (0,1), (1,0), (6,3), (5,4))
        @test_throws BoundsError r[badinds...]
        @test_throws BoundsError r[badinds...] = -2
    end
    for goodinds in (1, 15, (1,1), (5,3))
        @test setindex!(r, -2, goodinds...) === r
        @test r[goodinds...] == -2
    end
    r = reinterpret(Int32, a)
    @test @inferred(IndexStyle(r)) == IndexLinear()
    fill!(r, 3)
    @test all(a .=== reinterpret(Float64, [(Int32(3), Int32(3))])[1])
    @test all(r .=== Int32(3))
    for badinds in ((), 0, 31, (0,1), (1,0), (11,3), (10,4))
        @test_throws BoundsError r[badinds...]
        @test_throws BoundsError r[badinds...] = -3
    end
    for goodinds in (1, 30, (1,1), (10,3))
        @test setindex!(r, -3, goodinds...) === r
        @test r[goodinds...] == -3
    end
    r = reinterpret(Int64, view(a, 1:2:5, :))
    @test @inferred(IndexStyle(r)) == IndexCartesian()
    fill!(r, 4)
    @test all(a[1:2:5,:] .=== reinterpret(Float64, [Int64(4)])[1])
    @test all(r .=== Int64(4))
    for badinds in ((), 0, 10, (0,1), (1,0), (4,3), (3,4))
        @test_throws BoundsError r[badinds...]
        @test_throws BoundsError r[badinds...] = -4
    end
    for goodinds in (1, 9, (1,1), (3,3))
        @test setindex!(r, -4, goodinds...) === r
        @test r[goodinds...] == -4
    end
    r = reinterpret(Int32, view(a, 1:2:5, :))
    @test @inferred(IndexStyle(r)) == IndexCartesian()
    fill!(r, 5)
    @test all(a[1:2:5,:] .=== reinterpret(Float64, [(Int32(5), Int32(5))])[1])
    @test all(r .=== Int32(5))
    for badinds in ((), 0, 19, (0,1), (1,0), (7,3), (6,4))
        @test_throws BoundsError r[badinds...]
        @test_throws BoundsError r[badinds...] = -5
    end
    for goodinds in (1, 18, (1,1), (6,3))
        @test setindex!(r, -5, goodinds...) === r
        @test r[goodinds...] == -5
    end
end

let a = rand(ComplexF32, 5)
    r = reinterpret(reshape, Float32, a)
    ref = Array(r)

    @test all(r .== OffsetArray(r)[:, :, :])

    @test r[1, :, 1]        == ref[1, :]
    @test r[1, :, 1, 1, 1]  == ref[1, :]
    @test r[1, :, UInt8(1)] == ref[1, :]

    r[2, :, 1] .= 0f0
    ref[2,  :] .= 0f0
    @test r[2, :, 1] == ref[2, :]

    @test r[4] == ref[4]
    @test_throws BoundsError r[1, :, 2]
end

let ar = [(1,2), (3,4)]
    arr = reinterpret(reshape, Int, ar)
    @test @inferred(IndexStyle(arr)) == Base.IndexSCartesian2{2}()
    @test @inferred(eachindex(arr)) == Base.SCartesianIndices2{2}(Base.OneTo(2))
    @test @inferred(eachindex(arr, arr)) == Base.SCartesianIndices2{2}(Base.OneTo(2))
end
# Error on reinterprets that would expose padding
struct S1
    a::Int8
    b::Int64
end

struct S2
    a::Int16
    b::Int64
end

A1 = S1[S1(0, 0)]
A2 = S2[S2(0, 0)]
test_many_wrappers((A1, A2), (identity, wrapper)) do (A1_, A2_)
    A1, A2 = deepcopy(A1_), deepcopy(A2_)
    @test reinterpret(S1, A2)[1] == S1(0, 0)
    @test_throws Base.PaddingError (reinterpret(S1, A2)[1] = S1(1, 2))
    check_strided_get(reinterpret(S1, A2))
    @test_throws Base.PaddingError reinterpret(S2, A1)[1]
    check_strided_set(
        reinterpret(S2, deepcopy(A1_)),
        reinterpret(S2, deepcopy(A1_)),
        [S2(1, 2)],
        (a, b) -> parent(a) == parent(b),
    )
    reinterpret(S2, A1)[1] = S2(1, 2)
    @test A1[1] == S1(1, 2)
end

# Unconventional axes
let a = [0.1 0.2; 0.3 0.4], at = reshape([(i,i+1) for i = 1:2:8], 2, 2)
    v = OffsetArray(a, (-1, 1))
    r = reinterpret(Int64, v)
    @test axes(r) === axes(v)
    @test r[0,2] === reinterpret(Int64, v[0,2])
    @test r[1,2] === reinterpret(Int64, v[1,2])
    @test r[0,3] === reinterpret(Int64, v[0,3])
    @test r[1,3] === reinterpret(Int64, v[1,3])
    @test_throws ArgumentError("cannot reinterpret a `Float64` array to `UInt32` when the first axis is $(repr(axes(v,1))). Try reshaping first.") reinterpret(UInt32, v)
    @test_throws ArgumentError("`reinterpret(reshape, Tuple{Float64, Float64}, a)` where `eltype(a)` is Float64 requires that `axes(a, 1)` (got $(repr(axes(v,1)))) be equal to 1:2 (from the ratio of element sizes)") reinterpret(reshape, Tuple{Float64,Float64}, v)
    v = OffsetArray(a, (0, 1))
    @test axes(reinterpret(reshape, Tuple{Float64,Float64}, v)) === (OffsetArrays.IdOffsetRange(Base.OneTo(2), 1),)
    r = reinterpret(UInt32, v)
    axsv = axes(v)
    @test axes(r) === (oftype(axsv[1], 1:4), axsv[2])
    for i = 1:2
        rval = reinterpret(Tuple{UInt32,UInt32}, [v[i,2]])[1]
        @test r[2i-1,2]   == rval[1]
        @test r[2i,2] == rval[2]
        rval = reinterpret(Tuple{UInt32,UInt32}, [v[i,3]])[1]
        @test r[2i-1,3]   == rval[1]
        @test r[2i,3] == rval[2]
    end
    r[4,2] = 7
    @test r[4,2] === UInt32(7)
    @test a[2,1] === reinterpret(Float64, [0x33333333, UInt32(7)])[1]
    offsetvt = (-2, 4)
    vt = OffsetArray(at, offsetvt)
    istr = string(Int)
    @test_throws ArgumentError("cannot reinterpret a `Tuple{$istr, $istr}` array to `$istr` when the first axis is $(repr(axes(vt,1))). Try reshaping first.") reinterpret(Int, vt)
    vt = reshape(vt, 1:1, axes(vt)...)
    r = reinterpret(Int, vt)
    @test r == OffsetArray(reshape(1:8, 2, 2, 2), (0, offsetvt...))
end

@testset "potentially aliased copies" begin
    buffer = UInt8[1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0]
    mid = length(buffer) ÷ 2
    x1 = reinterpret(Int64, @view buffer[1:mid])
    x2 = reinterpret(Int64, @view buffer[mid+1:end])
    x1 .= x2
    @test x1 == x2 == [2]
    @test x1[] === x2[] === Int64(2)
end

# avoid nesting
@test parent(reinterpret(eltype(A), reinterpret(eltype(B), A))) === A

# Test 0-dimensional Arrays
A = zeros(UInt32)
test_many_wrappers(A, (identity, wrapper)) do A_
    A = deepcopy(A_)
    B = reinterpret(Int32, A)
    Brs = reinterpret(reshape,Int32, A)
    C = reinterpret(Tuple{UInt32}, A) # non-primitive type
    Crs = reinterpret(reshape, Tuple{UInt32}, A)  # non-primitive type
    @test size(B) == size(Brs) == size(C) == size(Crs) == ()
    @test axes(B) == axes(Brs) == axes(C) == axes(Crs) == ()
    @test setindex!(B, Int32(5)) === B
    @test B[] === Int32(5)
    @test Brs[] === Int32(5)
    @test C[] === (UInt32(5),)
    @test Crs[] === (UInt32(5),)
    @test A[] === UInt32(5)
    @test setindex!(Brs, Int32(12)) === Brs
    @test A[] === UInt32(12)
    @test setindex!(C, (UInt32(7),)) === C
    @test A[] === UInt32(7)
    @test setindex!(Crs, (UInt32(3),)) === Crs
    @test A[] === UInt32(3)
end

test_many_wrappers([(1.0,2.0)], (identity, wrapper)) do a
    af = @inferred(reinterpret(reshape, Float64, a))
    anew = @inferred(reinterpret(reshape, Tuple{Float64,Float64}, vec(af)))
    @test anew[1] == a[1]
    @test ndims(anew) == 0
end

# re-reinterpret
a0 = reshape([0x22, 0x44, 0x88, 0xf0, 0x01, 0x02, 0x03, 0x04], 4, 2)
test_many_wrappers(a0, (identity, wrapper)) do a0
    a = reinterpret(reshape, NTuple{4,UInt8}, a0)
    @test a == [(0x22, 0x44, 0x88, 0xf0), (0x01, 0x02, 0x03, 0x04)]
    @test reinterpret(UInt8, a) == [0x22, 0x44, 0x88, 0xf0, 0x01, 0x02, 0x03, 0x04]
    @test reinterpret(reshape, UInt8, a) === a0
end

# reductions
a = [(1,2,3), (4,5,6)]
test_many_wrappers(a, (identity, wrapper)) do a
    ars = reinterpret(reshape, Int, a)
    @test sum(ars) == 21
    @test sum(ars; dims=1) == [6 15]
    @test sum(ars; dims=2) == reshape([5,7,9], (3, 1))
    @test sum(ars; dims=(1,2)) == reshape([21], (1, 1))
end
# also test large sizes for the pairwise algorithm
a = [(k,k+1,k+2) for k = 1:3:4000]
test_many_wrappers(a, (identity, wrapper)) do a
    ars = reinterpret(reshape, Int, a)
    @test sum(ars) == 8010003
end

@testset "similar(::ReinterpretArray)" begin
    a = reinterpret(NTuple{2,Float64}, TSlow(rand(Float64, 4, 4)))

    as = similar(a)
    @test as isa TSlow{NTuple{2,Float64},2}
    @test size(as) == (2, 4)

    as = similar(a, Int, (3, 5, 1))
    @test as isa TSlow{Int,3}
    @test size(as) == (3, 5, 1)

    as = similar(typeof(a),(3, 5, 1))
    @test as isa TSlow{Float64,3}
    @test size(as) == (3, 5, 1)
    a = reinterpret(reshape, NTuple{4,Float64}, TSlow(rand(Float64, 4, 4)))

    as = similar(a)
    @test as isa TSlow{NTuple{4,Float64},1}
    @test size(as) == (4,)
end


@testset "aliasing" begin
    a = reinterpret(NTuple{2,Float64}, rand(Float64, 4, 4))
    @test typeof(Base.unaliascopy(a)) === typeof(a)
    a = reinterpret(reshape, NTuple{4,Float64}, rand(Float64, 4, 4))
    @test typeof(Base.unaliascopy(a)) === typeof(a)
end


@testset "singleton types" begin
    mutable struct NotASingleton end # not a singleton because it is mutable
    struct SomeSingleton
        # A singleton type that does not have the internal constructor SomeSingleton()
        SomeSingleton(x) = new()
    end

    @test_throws ArgumentError reinterpret(Int, nothing)
    @test_throws ArgumentError reinterpret(Missing, 3)
    @test_throws ArgumentError reinterpret(Missing, NotASingleton())
    @test_throws ArgumentError reinterpret(NotASingleton, ())

    @test_throws ArgumentError reinterpret(NotASingleton, fill(nothing, ()))
    @test_throws ArgumentError reinterpret(reshape, NotASingleton, fill(missing, 3))
    @test_throws ArgumentError reinterpret(Tuple{}, fill(NotASingleton(), 2))
    @test_throws ArgumentError reinterpret(reshape, Nothing, fill(NotASingleton(), ()))

    t = fill(nothing, 3, 5)
    @test reinterpret(SomeSingleton, t) == reinterpret(reshape, SomeSingleton, t)
    @test reinterpret(SomeSingleton, t) == [SomeSingleton(i*j) for i in 1:3, j in 1:5]
    @test reinterpret(Int, t) == fill(17, 0, 5)
    @test_throws ArgumentError reinterpret(reshape, Float64, t)
    @test_throws ArgumentError reinterpret(Nothing, 1:6)
    @test_throws ArgumentError reinterpret(reshape, Missing, [0.0])

    # reinterpret of empty array
    @test reinterpret(reshape, Nothing, fill(missing, (1,0,3))) == fill(nothing, (1,0,3))
    @test reinterpret(reshape, Missing, fill((), (0,))) == fill(missing, (0,))
    @test_throws ArgumentError reinterpret(reshape, Nothing, fill(3.2, (0,0)))
    @test_throws ArgumentError reinterpret(Missing, fill(77, (0,1)))
    @test_throws ArgumentError reinterpret(reshape, Float64, fill(nothing, 0))

    # reinterpret of 0-dimensional array
    z = reinterpret(Tuple{}, fill(missing, ()))
    @test z == fill((), ())
    @test z == reinterpret(reshape, Tuple{}, fill(nothing, ()))
    @test z[] == ()
    @test setindex!(z, ()) === z
    @test_throws BoundsError z[2]
    @test_throws BoundsError z[3] = ()
    @test_throws ArgumentError reinterpret(UInt8, fill(nothing, ()))
    @test_throws ArgumentError reinterpret(Missing, fill(1f0, ()))
    @test_throws ArgumentError reinterpret(reshape, Float64, fill(nothing, ()))
    @test_throws ArgumentError reinterpret(reshape, Nothing, fill(17, ()))
    @test_throws MethodError z[] = nothing

    @test @inferred(ndims(reinterpret(reshape, SomeSingleton, t))) == 2
    @test @inferred(axes(reinterpret(reshape, Tuple{}, t))) == (Base.OneTo(3),Base.OneTo(5))
    @test @inferred(size(reinterpret(reshape, Missing, t))) == (3,5)

    x = reinterpret(Tuple{}, t)
    @test x == reinterpret(reshape, Tuple{}, t)
    @test x[3,5] === ()
    x1 = fill((), 3, 5)
    @test setindex!(x, (), 1, 1) == x1
    @test_throws BoundsError x[17]
    @test_throws BoundsError x[4,2]
    @test_throws BoundsError x[1,2,3]
    @test_throws BoundsError x[18] = ()
    @test_throws MethodError x[1,3] = missing
    @test x == fill((), (3, 5))
    x = reinterpret(reshape, SomeSingleton, t)
    @test_throws BoundsError x[19]
    @test_throws BoundsError x[2,6] = SomeSingleton(0xa)
    @test x[2,3] === SomeSingleton(:x)
    x2 = fill(SomeSingleton(0.7), 3, 5)
    @test x == x2
    @test setindex!(x, SomeSingleton(:), 3, 5) == x2
    @test_throws MethodError x[2,4] = nothing
end

@testset "pointer for StridedArray" begin
    a = rand(Float64, 251)
    v = view(a, UInt(2):UInt(251));
    A = reshape(v, 25, 10);
    @test A isa StridedArray && pointer(A) === pointer(a, 2)
    Av = view(A, 1:20, 1:2)
    @test Av isa StridedArray && pointer(Av) === pointer(a, 2)
    @test Av * Av' isa Array
end

@testset "effect of StridedReinterpretArray's getindex" begin
    eff = Base.infer_effects(getindex, Base.typesof(reinterpret(Int8, Int[1]), 1))
    @test Core.Compiler.is_effect_free(eff)
end

# reinterpret of arbitrary bitstypes
@testset "Reinterpret arbitrary bitstypes" begin
    struct Bytes15
        a::Int8
        b::Int16
        c::Int32
        d::Int64
    end

    @test reinterpret(Float64, ComplexF32(1, 1)) === 0.007812501848093234
    @test reinterpret(ComplexF32, 0.007812501848093234) === ComplexF32(1, 1)
    @test reinterpret(Tuple{Float64, Float64}, ComplexF64(1, 1)) === (1.0, 1.0)
    @test reinterpret(ComplexF64, (1.0, 1.0)) === ComplexF64(1, 1)
    @test reinterpret(Tuple{Int8, Int16, Int32, Int64}, (Int64(1), Int32(2), Int16(3), Int8(4))) === (Int8(1), Int16(0), Int32(0), 288233674686595584)
    @test reinterpret(Tuple{Int8, Int16, Tuple{Int32, Int64}}, (Int64(1), Int32(2), Int16(3), Int8(4))) === (Int8(1), Int16(0), (Int32(0), 288233674686595584))
    @test reinterpret(Tuple{Int64, Int32, Int16, Int8}, (Int8(1), Int16(0), (Int32(0), 288233674686595584))) === (Int64(1), Int32(2), Int16(3), Int8(4))
    @test reinterpret(Tuple{Int8, Int16, Int32, Int64}, Bytes15(Int8(1), Int16(2), Int32(3), Int64(4))) === (Int8(1), Int16(2), Int32(3), Int64(4))
    @test reinterpret(Bytes15, (Int8(1), Int16(2), Int32(3), Int64(4))) == Bytes15(Int8(1), Int16(2), Int32(3), Int64(4))

    @test_throws ArgumentError reinterpret(Tuple{Int32, Int64}, (Int16(1), Int64(4)))
end

let R = reinterpret(Float32, ComplexF32[1.0f0+2.0f0*im, 4.0f0+3.0f0*im])
    @test !isassigned(R, 0)
    @test isassigned(R, 1)
    @test isassigned(R, 4)
    @test isassigned(R, Int8(2), Int16(1), Int32(1), Int64(1))
    @test !isassigned(R, 1, 2)
    @test !isassigned(R, 5)
    @test Array(R)::Vector{Float32} == [1.0f0, 2.0f0, 4.0f0, 3.0f0]
end

let R = reinterpret(reshape, Float32, ComplexF32[1.0f0+2.0f0*im, 4.0f0+3.0f0*im])
    @test !isassigned(R, 0)
    @test isassigned(R, 1)
    @test isassigned(R, 4)
    @test isassigned(R, Int8(2), Int16(2), Int32(1), Int64(1))
    @test !isassigned(R, 1, 1, 2)
    @test !isassigned(R, 5)
    @test Array(R)::Matrix{Float32} == [1.0f0 4.0f0; 2.0f0 3.0f0]
end

@testset "issue #54623" begin
    x = 0xabcdef01234567
    @test reinterpret(reshape, UInt8, fill(x)) == [0x67, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x00]
    @test reinterpret(reshape, UInt8, [x]) == [0x67; 0x45; 0x23; 0x01; 0xef; 0xcd; 0xab; 0x00;;]
end

@testset "primitive reinterpret alignment" begin
    primitive type RUInt24 24 end
    primitive type RUInt40 40 end
    primitive type RUInt48 48 end
    primitive type RUInt17 17 end
    primitive type RUInt23 23 end
    primitive type RUInt63 63 end

    @test Base.ispacked(RUInt24)
    @test Base.ispacked(RUInt40)
    @test Base.ispacked(RUInt48)
    @test !Base.datatype_haspadding(RUInt24)
    @test !Base.datatype_haspadding(RUInt40)
    @test !Base.datatype_haspadding(RUInt48)
    @test Base.packedsize(RUInt24) == 3
    @test Base.packedsize(RUInt40) == 5
    @test Base.packedsize(RUInt48) == 6
    @test !Base.ispacked(RUInt17)
    @test !Base.ispacked(RUInt23)
    @test !Base.ispacked(RUInt63)
    @test Base.datatype_haspadding(RUInt17)
    @test Base.datatype_haspadding(RUInt23)
    @test Base.datatype_haspadding(RUInt63)

    r24 = reinterpret(RUInt24, (0xaa, 0xbb, 0xcc))
    @test reinterpret(NTuple{3, UInt8}, r24) === (0xaa, 0xbb, 0xcc)

    r40 = reinterpret(RUInt40, (0x01, 0x02, 0x03, 0x04, 0x05))
    @test reinterpret(NTuple{5, UInt8}, r40) === (0x01, 0x02, 0x03, 0x04, 0x05)

    r48 = reinterpret(RUInt48, (0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f))
    @test reinterpret(NTuple{6, UInt8}, r48) === (0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)

    @test_throws ArgumentError Base.padding(RUInt17)
    @test_throws ArgumentError Base.packedsize(RUInt17)
    @test_throws ArgumentError Base.padding(RUInt23)
    @test_throws ArgumentError Base.packedsize(RUInt23)
    @test_throws ArgumentError Base.padding(RUInt63)
    @test_throws ArgumentError Base.packedsize(RUInt63)

    @test_throws ArgumentError reinterpret(RUInt17, (0x01, 0x02, 0x03))
    @test_throws ArgumentError reinterpret(RUInt23, (0x01, 0x02, 0x03))
    @test_throws ArgumentError reinterpret(RUInt63, ntuple(i -> UInt8(i), 8))

    struct RHasUInt17
        x::RUInt17
        y::UInt8
    end
    struct RHasUnionUInt17
        x::Union{UInt8, RUInt17}
    end
    @test_throws ArgumentError reinterpret(RHasUInt17, (0x01, 0x02, 0x03, 0x04))
    @test_throws ArgumentError reinterpret(RHasUnionUInt17, (0x01, 0x02, 0x03))
    @test_throws ArgumentError reinterpret(
        NTuple{4, UInt8},
        RHasUInt17(Core.Intrinsics.trunc_int(RUInt17, UInt32(1)), 0x02),
    )

    # Dense odd-bit arrays use allocation-size strides and reject byte reinterpretation.
    for (T, W, storage_size, allocation_size) in (
        (RUInt23, UInt32, 3, 4),
        (RUInt63, UInt64, 8, 8),
    )
        values = Core.Intrinsics.trunc_int.(T, W[1, 2, 3])
        memory = Memory{T}(undef, 3)
        memory .= values
        @test sizeof(T) == storage_size
        @test Base.elsize(memory) == allocation_size
        @test Core.Intrinsics.zext_int.(W, memory) == W[1, 2, 3]
        memory[2] = Core.Intrinsics.trunc_int(T, W(4))
        @test Core.Intrinsics.zext_int(W, memory[2]) == 4

        array = Array(memory)
        @test Base.elsize(array) == allocation_size
        @test Core.Intrinsics.zext_int.(W, array) == W[1, 4, 3]
        array[3] = Core.Intrinsics.trunc_int(T, W(5))
        @test Core.Intrinsics.zext_int(W, array[3]) == 5

        @test_throws ArgumentError reinterpret(UInt8, memory)
        @test_throws ArgumentError reinterpret(UInt8, array)
        @test_throws ArgumentError reinterpret(T, zeros(UInt8, storage_size))
    end
end

@testset "elsize for ReinterpretArray with aligned types" begin
    primitive type RInt24 24 end
    primitive type RAlsoInt24 24 end
    RInt24(x::Int) = Core.Intrinsics.trunc_int(RInt24, x)
    RAlsoInt24(x::Int) = Core.Intrinsics.trunc_int(RAlsoInt24, x)
    Base.zero(::Type{RInt24}) = RInt24(0)
    Base.zero(::Type{RAlsoInt24}) = RAlsoInt24(0)

    # sizeof(T) == sizeof(S): elsize delegates through parent chain
    a = zeros(RInt24, 3)
    b = reinterpret(RAlsoInt24, a)
    @test Base.elsize(b) == Base.elsize(a) == 4

    # View-wrapped parent: delegates through SubArray to Array
    v = view(a, 1:2)
    c = reinterpret(RAlsoInt24, v)
    @test Base.elsize(c) == 4

    # Reshape-variant with packed parent: sizeof(T) != sizeof(S) → sizeof(T)
    d = reinterpret(reshape, UInt8, zeros(RInt24, 6))
    @test Base.elsize(d) == sizeof(UInt8)

    # sizeof(T) != sizeof(S) flat reinterpret
    e = reinterpret(UInt8, zeros(Int32, 5))
    @test Base.elsize(e) == sizeof(UInt8)
    f = reinterpret(Int32, zeros(UInt8, 20))
    @test Base.elsize(f) == sizeof(Int32)
end

@testset "ReinterpretArray with Memory parent and aligned types" begin
    primitive type NInt24 24 end
    primitive type NAlsoInt24 24 end
    NInt24(x::Int) = Core.Intrinsics.trunc_int(NInt24, x)
    NAlsoInt24(x::Int) = Core.Intrinsics.trunc_int(NAlsoInt24, x)
    Base.zero(::Type{NInt24}) = NInt24(0)
    Base.zero(::Type{NAlsoInt24}) = NAlsoInt24(0)

    mem = Memory{NInt24}(undef, 3)
    mem[1] = NInt24(10); mem[2] = NInt24(20); mem[3] = NInt24(30)
    b = reinterpret(NAlsoInt24, mem)

    @test Base.elsize(b) == Base.elsize(mem) == 4
    @test b[1] == reinterpret(NAlsoInt24, NInt24(10))
    @test b[2] == reinterpret(NAlsoInt24, NInt24(20))
    @test b[3] == reinterpret(NAlsoInt24, NInt24(30))

    b[2] = reinterpret(NAlsoInt24, NInt24(99))
    @test b[2] == reinterpret(NAlsoInt24, NInt24(99))
    @test b[1] == reinterpret(NAlsoInt24, NInt24(10))
    @test b[3] == reinterpret(NAlsoInt24, NInt24(30))
end

@testset "ReinterpretArray over non-DenseArray parents" begin
    # elsize should throw MethodError for non-Dense parents
    r = reinterpret(UInt, 1:6)
    @test_throws MethodError Base.elsize(r)

    # But indexing should still work via _getindex_ra
    @test r[2] == 2
    @test length(r) == 6
    @test collect(r) == UInt[1, 2, 3, 4, 5, 6]

    # Reshape wrapping a non-Dense parent must also work
    rr = reinterpret(UInt, reshape(1:6, 2, 3))
    @test rr[2] == 2
    @test rr[4] == 4
end

@testset "Padded-parent write routes to data byte, not padding byte" begin
    primitive type PadInt24 24 end
    PadInt24(x::Int) = Core.Intrinsics.trunc_int(PadInt24, x)
    Base.zero(::Type{PadInt24}) = PadInt24(0)

    # Array backing: elementwise path writes to correct data bytes
    a = zeros(PadInt24, 2)
    b = reinterpret(UInt8, a)
    b[2] = 0xab                     # packed byte 2 = second data byte of element 1
    @test a[1] === PadInt24(0xab00 % Int)
    @test a[2] === PadInt24(0)
    b[4] = 0xcd                     # packed byte 4 = first data byte of element 2
    @test a[2] === PadInt24(0xcd % Int)

    # Memory backing: same-size reinterpret also takes elementwise path
    mem = Memory{PadInt24}(undef, 2)
    mem[1] = PadInt24(0); mem[2] = PadInt24(0)
    b = reinterpret(UInt8, mem)
    b[2] = 0xab
    @test mem[1] === PadInt24(0xab00 % Int)
    @test mem[2] === PadInt24(0)
    b[4] = 0xcd
    @test mem[2] === PadInt24(0xcd % Int)
end

@testset "getindex/setindex! pointer path with aligned types" begin
    primitive type PInt24 24 end
    primitive type PAlsoInt24 24 end
    PInt24(x::Int) = Core.Intrinsics.trunc_int(PInt24, x)
    PAlsoInt24(x::Int) = Core.Intrinsics.trunc_int(PAlsoInt24, x)
    Base.zero(::Type{PInt24}) = PInt24(0)
    Base.zero(::Type{PAlsoInt24}) = PAlsoInt24(0)

    a = zeros(PInt24, 3)
    a[1] = PInt24(10)
    a[2] = PInt24(20)
    a[3] = PInt24(30)
    b = reinterpret(PAlsoInt24, a)

    @test b[1] == reinterpret(PAlsoInt24, PInt24(10))
    @test b[2] == reinterpret(PAlsoInt24, PInt24(20))
    @test b[3] == reinterpret(PAlsoInt24, PInt24(30))

    b[2] = reinterpret(PAlsoInt24, PInt24(99))
    @test b[2] == reinterpret(PAlsoInt24, PInt24(99))
    @test b[1] == reinterpret(PAlsoInt24, PInt24(10))
    @test b[3] == reinterpret(PAlsoInt24, PInt24(30))
end
