import tilelang
import tilelang.testing
import tilelang.language as T


@tilelang.jit
def loop_layout_kernel(A, B, loop_layout):
    M, N = T.const("M, N")
    A: T.Tensor[(M, N), T.float32]
    B: T.Tensor[(M, N), T.float32]

    with T.Kernel(1, threads=128):
        for i, j in T.Parallel(M, N, loop_layout=loop_layout):
            B[i, j] = A[i, j]


@tilelang.testing.requires_cuda
def test_loop_layout_fragment_vec4():
    def loop_layout_fn(i, j):
        elems = i * 32 + j
        forward_thread = (elems // 4) % 128
        forward_local = elems % 4 + (elems // 512) * 4
        return forward_thread, forward_local

    M, N = 128, 32
    loop_layout = T.Fragment((M, N), forward_fn=loop_layout_fn)
    kernel = loop_layout_kernel.compile(M=M, N=N, loop_layout=loop_layout)
    code = kernel.get_kernel_source()

    # Expect vectorized copy along innermost dimension (float4)
    assert "*(float4*)(B + ((i * 512) + (((int)threadIdx.x) * 4))) = *(float4*)(A + ((i * 512) + (((int)threadIdx.x) * 4)));" in code


@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_lt(9, 0)
def test_loop_layout_identity():
    def loop_layout_fn(i, j):
        forward_thread = i
        forward_local = j
        return forward_thread, forward_local

    M, N = 128, 32
    loop_layout = T.Fragment((M, N), forward_fn=loop_layout_fn)
    kernel = loop_layout_kernel.compile(M=M, N=N, loop_layout=loop_layout)
    code = kernel.get_kernel_source()
    assert "*(float4*)(B + ((((int)threadIdx.x) * 32) + (i * 4))) = *(float4*)(A + ((((int)threadIdx.x) * 32) + (i * 4)));" in code


@tilelang.jit
def copy_with_layout_kernel(A, B, loop_layout):
    M, N = T.const("M, N")
    A: T.Tensor[(M, N), T.float32]
    B: T.Tensor[(M, N), T.float32]

    with T.Kernel(1, threads=128):
        T.copy(A, B, loop_layout=loop_layout)


@tilelang.testing.requires_cuda
def test_copy_loop_layout_annotated_replicate_vec4():
    def loop_layout_fn(i, j, rep):
        elems = i * 32 + j
        fth = (elems // 4) % 64 + rep * 64
        floc = elems % 4 + (elems // (64 * 4)) * 4
        return fth, floc

    M, N = 128, 32
    loop_layout = T.Fragment((M, N), forward_fn=loop_layout_fn, replicate=2)
    kernel = copy_with_layout_kernel.compile(M=M, N=N, loop_layout=loop_layout)
    code = kernel.get_kernel_source()

    assert (
        "*(float4*)(B + ((i * 256) + ((((int)threadIdx.x) & 63) * 4))) = *(float4*)(A + ((i * 256) + ((((int)threadIdx.x) & 63) * 4)));"
        in code
    )


@tilelang.jit
def replicate_loop_layout_kernel(A, B, loop_layout):
    M, N = T.const("M, N")
    A: T.Tensor[(M, N), T.float32]
    B: T.Tensor[(M, N), T.float32]

    with T.Kernel(1, threads=128):
        for i, j in T.Parallel(M, N, loop_layout=loop_layout):
            B[i, j] = A[i, j]


@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version(9, 0)
def test_annotate_replicate_loop_layout_vec4():
    M, N = 128, 32

    def loop_layout_fn(i, j, rep):
        elems = i * 32 + j
        forward_thread = (elems // 4) % 64 + rep * 64
        forward_local = elems % 4 + (elems // (64 * 4)) * 4
        return forward_thread, forward_local

    loop_layout = T.Fragment((M, N), forward_fn=loop_layout_fn, replicate=2)
    kernel = replicate_loop_layout_kernel.compile(M=M, N=N, loop_layout=loop_layout)
    code = kernel.get_kernel_source()
    assert (
        "*(float4*)(B + ((i * 256) + ((((int)threadIdx.x) & 63) * 4))) = *(float4*)(A + ((i * 256) + ((((int)threadIdx.x) & 63) * 4)));"
        in code
    )


if __name__ == "__main__":
    tilelang.testing.main()
