/***************************************************************************************************
 * Copyright (c) 2023 - 2025 Hygon Information Technology Co., Ltd. All rights reserved.
 * Copyright (c) 2017 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **************************************************************************************************/
/*! \file
    \brief Tests hytlass::transform::threadblock::PredicatedTileIterator 
*/
#include "hip/hip_runtime.h"
#include "../../common/hytlass_unit_test.h"

#include "hytlass/hytlass.h"

#include "hytlass/transform/pitch_linear_thread_map.h"
#include "hytlass/transform/threadblock/predicated_tile_iterator.h"
#include "hytlass/transform/threadblock/predicated_tile_iterator_2dthreadtile.h"

#include "hytlass/util/tensor_view_io.h"

#include "hytlass/util/host_tensor.h"
#include "hytlass/util/reference/host/tensor_fill.h"

/////////////////////////////////////////////////////////////////////////////////////////////////

namespace test {
namespace transform {
namespace threadblock {
namespace kernel {

/// Copy with an iterator
template <typename Iterator>
__global__ void copy(
  typename Iterator::Params dst_params, 
  typename Iterator::Element *dst_pointer,
  typename Iterator::Params src_params,
  typename Iterator::Element *src_pointer,
  hytlass::Coord<2> extent) {

  Iterator dst_iterator(dst_params, dst_pointer, extent, threadIdx.x);
  Iterator src_iterator(src_params, src_pointer, extent, threadIdx.x);

  int iterations = (extent[1] + Iterator::Shape::kStrided - 1) / Iterator::Shape::kStrided;

  typename Iterator::Fragment frag;

  for(size_t i = 0; i < frag.size(); i++)
    frag[i] = 0;

  src_iterator.load(frag);
  dst_iterator.store(frag);

  ++dst_iterator;
  ++src_iterator;

  for (; iterations > 1; --iterations) {
    
    src_iterator.load(frag);
    dst_iterator.store(frag);

    ++dst_iterator;
    ++src_iterator;
  }
}

} // namespace kernel
} // namespace threadblock
} // namespace transform
} // namespace test

/////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined) {

  using Shape = hytlass::layout::PitchLinearShape<64, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinearStripminedThreadMap<Shape, kThreads>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator<
    Shape, Element, Layout, 1, ThreadMap
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(57, 35);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(64, 35);
  
  hytlass::HostTensor<int, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::BlockFillSequential(src_tensor.host_data(), src_tensor.capacity());

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]; ++s) {
    for (int c = 0; c < alloc_extent[0]; ++c) {

      Element expected = Element(0);

      if (c < copy_extent[0] && s < copy_extent[1]) {
        expected = src_tensor.at({c, s});
      }
      else {
        expected = oob_value;
      }

      Element got = dst_tensor.at({c, s});
      bool equal = (expected == got);

      EXPECT_EQ(expected, got)
        << "Source:\n" << src_tensor.host_view() << "\n\n"
        << "Destination:\n" << dst_tensor.host_view() << "\n";

      if (!equal) {
        return;
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////
TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_128x4) {

  using Shape = hytlass::layout::PitchLinearShape<128, 8>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap, false
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(128, 8);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(128, 8);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::BlockFillSequential(src_tensor.host_data(), src_tensor.capacity());

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]; ++s) {
    for (int c = 0; c < alloc_extent[0]; ++c) {

      Element expected = Element(0);

      if (c < copy_extent[0] && s < copy_extent[1]) {
        expected = src_tensor.at({c, s});
      }
      else {
        expected = oob_value;
      }

      Element got = dst_tensor.at({c, s});
      bool equal = (expected == got);

      EXPECT_EQ(expected, got)
        << "Source:\n" << src_tensor.host_view() << "\n\n"
        << "Destination:\n" << dst_tensor.host_view() << "\n";

      if (!equal) {
        return;
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_128x64) {

  using Shape = hytlass::layout::PitchLinearShape<128, 64>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(128, 64);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(128, 64);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::BlockFillSequential(src_tensor.host_data(), src_tensor.capacity());

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]; ++s) {
    for (int c = 0; c < alloc_extent[0]; ++c) {

      Element expected = Element(0);

      if (c < copy_extent[0] && s < copy_extent[1]) {
        expected = src_tensor.at({c, s});
      }
      else {
        expected = oob_value;
      }

      Element got = dst_tensor.at({c, s});
      bool equal = (expected == got);

      EXPECT_EQ(expected, got)
        << "Source:\n" << src_tensor.host_view() << "\n\n"
        << "Destination:\n" << dst_tensor.host_view() << "\n";

      if (!equal) {
        return;
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_64x64) {

  using Shape = hytlass::layout::PitchLinearShape<64, 64>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(64, 64);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(64, 64);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::BlockFillSequential(src_tensor.host_data(), src_tensor.capacity());

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]; ++s) {
    for (int c = 0; c < alloc_extent[0]; ++c) {

      Element expected = Element(0);

      if (c < copy_extent[0] && s < copy_extent[1]) {
        expected = src_tensor.at({c, s});
      }
      else {
        expected = oob_value;
      }

      Element got = dst_tensor.at({c, s});
      bool equal = (expected == got);

      EXPECT_EQ(expected, got)
        << "Source:\n" << src_tensor.host_view() << "\n\n"
        << "Destination:\n" << dst_tensor.host_view() << "\n";

      if (!equal) {
        return;
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_64x8) {

  using Shape = hytlass::layout::PitchLinearShape<64, 16>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(32, 16);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(64, 16);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::BlockFillSequential(src_tensor.host_data(), src_tensor.capacity());

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]; ++s) {
    for (int c = 0; c < alloc_extent[0]; ++c) {

      Element expected = Element(0);

      if (c < copy_extent[0] && s < copy_extent[1]) {
        expected = src_tensor.at({c, s});
      }
      else {
        expected = oob_value;
      }

      Element got = dst_tensor.at({c, s});
      bool equal = (expected == got);

      EXPECT_EQ(expected, got)
        << "Source:\n" << src_tensor.host_view() << "\n\n"
        << "Destination:\n" << dst_tensor.host_view() << "\n";

      if (!equal) {
        return;
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_64x32_transpose4x4) {

  using Shape = hytlass::layout::PitchLinearShape<64, 16>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap, true
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(64, 32);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(64, 32);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  uint64_t seed = 7;
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::TensorFillRandomUniform(src_tensor.host_view(), seed, 8, -8, 0);

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]/4; ++s) {
    for (int c = 0; c < alloc_extent[0]/4; ++c) {
      for (int s1 = 0; s1 < 4; s1++){
        for(int c1 = 0; c1 < 4; c1++){
          Element expected = Element(0);

          int l_c = c * 4 + c1;
          int l_s = s * 4 + s1;

          int l_tc = c * 4 + s1;
          int l_ts = s * 4 + c1;

          if (l_c < copy_extent[0] && l_s < copy_extent[1]) {
            expected = src_tensor.at({l_c, l_s});
          }
          else {
            expected = oob_value;
          }    

          Element got = dst_tensor.at({l_tc, l_ts});
          bool equal = (expected == got);

          EXPECT_EQ(expected, got)
            << "Source:\n" << src_tensor.host_view() << "\n\n"
            << "Destination:\n" << dst_tensor.host_view() << "\n";

          if (!equal) {
            return;
          }
        }
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_64x29_transpose4x4) {

  using Shape = hytlass::layout::PitchLinearShape<64, 16>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap, true
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(64, 29);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(64, 29);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  uint64_t seed = 7;
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::TensorFillRandomUniform(src_tensor.host_view(), seed, 8, -8, 0);

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]/4; ++s) {
    for (int c = 0; c < alloc_extent[0]/4; ++c) {
      for (int s1 = 0; s1 < 4; s1++){
        for(int c1 = 0; c1 < 4; c1++){
          Element expected = Element(0);

          int l_c = c * 4 + c1;
          int l_s = s * 4 + s1;

          int l_tc = c * 4 + s1;
          int l_ts = s * 4 + c1;

          if (l_c < copy_extent[0] && l_s < copy_extent[1]) {
            expected = src_tensor.at({l_c, l_s});
          }
          else {
            expected = oob_value;
          }    

          Element got = dst_tensor.at({l_tc, l_ts});
          bool equal = (expected == got);

          EXPECT_EQ(expected, got)
            << "Source:\n" << src_tensor.host_view() << "\n\n"
            << "Destination:\n" << dst_tensor.host_view() << "\n";

          if (!equal) {
            return;
          }
        }
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_120x4_transpose4x4) {

  using Shape = hytlass::layout::PitchLinearShape<128, 8>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap, true
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(120, 8);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(120, 8);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  uint64_t seed = 7;
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::TensorFillRandomUniform(src_tensor.host_view(), seed, 8, -8, 0);

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]/4; ++s) {
    for (int c = 0; c < alloc_extent[0]/4; ++c) {
      for (int s1 = 0; s1 < 4; s1++){
        for(int c1 = 0; c1 < 4; c1++){
          Element expected = Element(0);

          int l_c = c * 4 + c1;
          int l_s = s * 4 + s1;

          int l_tc = c * 4 + s1;
          int l_ts = s * 4 + c1;

          if (l_c < copy_extent[0] && l_s < copy_extent[1]) {
            expected = src_tensor.at({l_c, l_s});
          }
          else {
            expected = oob_value;
          }    

          Element got = dst_tensor.at({l_tc, l_ts});
          bool equal = (expected == got);

          EXPECT_EQ(expected, got)
            << "Source:\n" << src_tensor.host_view() << "\n\n"
            << "Destination:\n" << dst_tensor.host_view() << "\n";

          if (!equal) {
            return;
          }
        }
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////

TEST(Transform_threadblock_PredicatedTileIterator, PitchLinear_Stripmined_2dtile_48x29_transpose4x4) {

  using Shape = hytlass::layout::PitchLinearShape<64, 16>;
  using ThreadTileShape = hytlass::layout::PitchLinearShape<4, 4>;
  using Layout = hytlass::layout::PitchLinear;
  using Element = int8_t;
  static int const kThreads = WARP_SIZE_GPU;
  
  using ThreadMap = hytlass::transform::PitchLinear2DThreadTileStripminedThreadMap<Shape, kThreads, ThreadTileShape>;

  using Iterator = hytlass::transform::threadblock::PredicatedTileIterator2dThreadTile<
    Shape, Element, Layout, 1, ThreadMap, true
  >;

  hytlass::Coord<2> copy_extent = hytlass::make_Coord(48, 29);
  hytlass::Coord<2> alloc_extent = hytlass::make_Coord(48, 29);
  
  hytlass::HostTensor<int8_t, Layout> src_tensor(alloc_extent);
  hytlass::HostTensor<int8_t, Layout> dst_tensor(alloc_extent);

  Element oob_value = Element(-1);
  uint64_t seed = 7;
  hytlass::reference::host::TensorFill(dst_tensor.host_view(), oob_value);
  hytlass::reference::host::TensorFillRandomUniform(src_tensor.host_view(), seed, 8, -8, 0);

  dst_tensor.sync_device();
  src_tensor.sync_device();

  typename Iterator::Params dst_params(dst_tensor.layout());
  typename Iterator::Params src_params(src_tensor.layout());

  dim3 block(kThreads, 1);
  dim3 grid(1, 1);

  test::transform::threadblock::kernel::copy<Iterator><<< grid, block >>>(
    dst_params,
    dst_tensor.device_data(),
    src_params,
    src_tensor.device_data(),
    copy_extent
  );

  hipError_t result = hipGetLastError();
  EXPECT_EQ(result, hipSuccess) << " - HIP error: " << hipGetErrorString(result);
  
  dst_tensor.sync_host();

  for (int s = 0; s < alloc_extent[1]/4; ++s) {
    for (int c = 0; c < alloc_extent[0]/4; ++c) {
      for (int s1 = 0; s1 < 4; s1++){
        for(int c1 = 0; c1 < 4; c1++){
          Element expected = Element(0);

          int l_c = c * 4 + c1;
          int l_s = s * 4 + s1;

          int l_tc = c * 4 + s1;
          int l_ts = s * 4 + c1;

          if (l_c < copy_extent[0] && l_s < copy_extent[1]) {
            expected = src_tensor.at({l_c, l_s});
          }
          else {
            expected = oob_value;
          }    

          Element got = dst_tensor.at({l_tc, l_ts});
          bool equal = (expected == got);

          EXPECT_EQ(expected, got)
            << "Source:\n" << src_tensor.host_view() << "\n\n"
            << "Destination:\n" << dst_tensor.host_view() << "\n";
          if (!equal) {
            return;
          }
        }
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////
