/**
 * @file   dense_tiler.cc
 *
 * @section LICENSE
 *
 * The MIT License
 *
 * @copyright Copyright (c) 2021 TileDB, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * This file implements class DenseTiler.
 */

#include "tiledb/sm/query/dense_tiler.h"
#include "tiledb/common/logger.h"
#include "tiledb/sm/array/array.h"
#include "tiledb/sm/array_schema/dimension.h"
#include "tiledb/sm/array_schema/domain.h"
#include "tiledb/sm/misc/constants.h"
#include "tiledb/sm/misc/parallel_functions.h"
#include "tiledb/sm/tile/tile.h"

using namespace tiledb::common;
using namespace tiledb::sm::stats;

namespace tiledb {
namespace sm {

/* ****************************** */
/*   CONSTRUCTORS & DESTRUCTORS   */
/* ****************************** */

template <class T>
DenseTiler<T>::DenseTiler(
    const std::unordered_map<std::string, QueryBuffer>* buffers,
    const Subarray* subarray,
    Stats* const parent_stats,
    const std::string& offsets_format_mode,
    uint64_t offsets_bitsize,
    bool offsets_extra_element)
    : stats_(parent_stats->create_child("DenseTiler"))
    , array_schema_(subarray->array()->array_schema_latest())
    , buffers_(buffers)
    , subarray_(subarray)
    , offsets_format_mode_(offsets_format_mode)
    , offsets_bytesize_(offsets_bitsize / 8)
    , offsets_extra_element_(offsets_extra_element) {
  // Assertions
  assert(subarray != nullptr);
  assert(buffers != nullptr);
  for (const auto& buff : *buffers) {
    assert(array_schema_->is_attr(buff.first));
    (void)buff;
  }

  // Initializations
  calculate_tile_num();
  calculate_subarray_tile_coord_strides();
  calculate_first_sub_tile_coords();
  calculate_tile_and_subarray_strides();
}

template <class T>
DenseTiler<T>::~DenseTiler() {
}

/* ****************************** */
/*               API              */
/* ****************************** */

template <class T>
const typename DenseTiler<T>::CopyPlan DenseTiler<T>::copy_plan(
    uint64_t id) const {
  assert(id < tile_num_);

  // For easy reference
  CopyPlan ret;
  auto dim_num = (int32_t)array_schema_->dim_num();
  auto domain = array_schema_->domain();
  auto subarray = subarray_->ndrange(0);  // Guaranteed to be unary
  std::vector<std::array<T, 2>> sub(dim_num);
  for (int32_t d = 0; d < dim_num; ++d)
    sub[d] = {*(const T*)subarray[d].start(), *(const T*)subarray[d].end()};
  auto tile_layout = array_schema_->cell_order();
  auto sub_layout = subarray_->layout();

  // Copy tile and subarray strides
  ret.tile_strides_el_ = tile_strides_el_;
  ret.sub_strides_el_ = sub_strides_el_;

  // Focus on the input tile
  auto tile_sub = this->tile_subarray(id);
  auto sub_in_tile = utils::geometry::intersection<T>(sub, tile_sub);

  // Compute the starting element to copy from in the subarray, and
  // to copy to in the tile
  ret.sub_start_el_ = 0;
  ret.tile_start_el_ = 0;
  for (int32_t d = 0; d < dim_num; ++d) {
    ret.sub_start_el_ += (sub_in_tile[d][0] - sub[d][0]) * sub_strides_el_[d];
    ret.tile_start_el_ +=
        (sub_in_tile[d][0] - tile_sub[d][0]) * tile_strides_el_[d];
  }

  // Calculate the copy elements per iteration, as well as the
  // dimension ranges to focus on
  if (dim_num == 1) {  // Special case, copy the entire subarray 1D range
    ret.dim_ranges_.push_back({0, 0});
    ret.copy_el_ = sub_in_tile[0][1] - sub_in_tile[0][0] + 1;
    ret.first_d_ = 0;
  } else if (sub_layout != tile_layout) {
    ret.copy_el_ = 1;
    ret.first_d_ = 0;
    for (int32_t d = 0; d < dim_num; ++d) {
      ret.dim_ranges_.push_back(
          {(uint64_t)0, uint64_t(sub_in_tile[d][1] - sub_in_tile[d][0])});
    }
  } else {  // dim_num > 1 && same layout of tile and subarray cells
    if (tile_layout == Layout::ROW_MAJOR) {
      ret.copy_el_ =
          sub_in_tile[dim_num - 1][1] - sub_in_tile[dim_num - 1][0] + 1;
      int32_t last_d = dim_num - 2;
      for (; last_d >= 0; --last_d) {
        auto tile_extent = *(const T*)domain->tile_extent(last_d + 1).data();
        if (sub_in_tile[last_d + 1][1] - sub_in_tile[last_d + 1][0] + 1 ==
                tile_extent &&
            sub_in_tile[last_d + 1][0] == sub[last_d + 1][0] &&
            sub_in_tile[last_d + 1][1] == sub[last_d + 1][1])
          ret.copy_el_ *= sub_in_tile[last_d][1] - sub_in_tile[last_d][0] + 1;
        else
          break;
      }
      if (last_d < 0) {
        ret.dim_ranges_.push_back({0, 0});
      } else {
        for (int32_t d = 0; d <= last_d; ++d)
          ret.dim_ranges_.push_back(
              {0, (uint64_t)(sub_in_tile[d][1] - sub_in_tile[d][0])});
      }
      ret.first_d_ = 0;
    } else {  // COL_MAJOR
      ret.copy_el_ = sub_in_tile[0][1] - sub_in_tile[0][0] + 1;
      int32_t last_d = 1;
      for (; last_d < dim_num; ++last_d) {
        auto tile_extent = *(const T*)domain->tile_extent(last_d - 1).data();
        if (sub_in_tile[last_d - 1][1] - sub_in_tile[last_d - 1][0] + 1 ==
                tile_extent &&
            sub_in_tile[last_d - 1][0] == sub[last_d - 1][0] &&
            sub_in_tile[last_d - 1][1] == sub[last_d - 1][1])
          ret.copy_el_ *= sub_in_tile[last_d][1] - sub_in_tile[last_d][0] + 1;
        else
          break;
      }
      if (last_d == dim_num) {
        ret.dim_ranges_.push_back({0, 0});
        ret.first_d_ = dim_num - 1;
      } else {
        for (int32_t d = last_d; d < dim_num; ++d)
          ret.dim_ranges_.push_back(
              {0, (uint64_t)(sub_in_tile[d][1] - sub_in_tile[d][0])});
        ret.first_d_ = last_d;
      }
    }
  }

  return ret;
}

template <class T>
Status DenseTiler<T>::get_tile(
    uint64_t id, const std::string& name, Tile* tile) {
  auto timer_se = stats_->start_timer("get_tile");

  // Checks
  if (id >= tile_num_)
    return LOG_STATUS(
        Status::DenseTilerError("Cannot get tile; Invalid tile id"));
  if (!array_schema_->is_attr(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name + "' is not an attribute"));
  if (array_schema_->var_size(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name +
        "' is not a fixed-sized attribute"));

  // For easy reference
  auto domain = array_schema_->domain();
  auto type = array_schema_->type(name);
  auto cell_num_per_tile = domain->cell_num_per_tile();
  auto cell_size = array_schema_->cell_size(name);
  auto tile_size = cell_num_per_tile * cell_size;
  auto buff = (uint8_t*)buffers_->find(name)->second.buffer_;
  assert(buff != nullptr);

  // Initialize tile
  RETURN_NOT_OK(tile->init_unfiltered(
      constants::format_version, type, tile_size, cell_size, 0, true));
  tile->reset_offset();

  // Copy tile from buffer
  RETURN_NOT_OK(copy_tile(id, cell_size, buff, tile));

  return Status::Ok();
}

template <class T>
Status DenseTiler<T>::get_tile_null(
    uint64_t id, const std::string& name, Tile* tile) const {
  // Checks
  if (id >= tile_num_)
    return LOG_STATUS(
        Status::DenseTilerError("Cannot get tile; Invalid tile id"));
  if (!array_schema_->is_attr(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name + "' is not an attribute"));
  if (!array_schema_->is_nullable(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name +
        "' is not a nullable attribute"));

  // For easy reference
  auto domain = array_schema_->domain();
  auto type = array_schema_->type(name);
  auto cell_num_per_tile = domain->cell_num_per_tile();
  auto cell_size = constants::cell_validity_size;
  auto tile_size = cell_num_per_tile * cell_size;
  auto buff = (uint8_t*)buffers_->find(name)->second.validity_vector_.buffer();
  assert(buff != nullptr);

  // Initialize tile
  RETURN_NOT_OK(tile->init_unfiltered(
      constants::format_version, type, tile_size, cell_size, 0, true));
  tile->reset_offset();
  assert(tile->size() == tile_size);

  // Copy tile from buffer
  return copy_tile(id, cell_size, buff, tile);
}

template <class T>
Status DenseTiler<T>::get_tile_var(
    uint64_t id,
    const std::string& name,
    Tile* tile_off,
    Tile* tile_val) const {
  // Checks
  if (id >= tile_num_)
    return LOG_STATUS(
        Status::DenseTilerError("Cannot get tile; Invalid tile id"));
  if (!array_schema_->is_attr(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name + "' is not an attribute"));
  if (!array_schema_->var_size(name))
    return LOG_STATUS(Status::DenseTilerError(
        std::string("Cannot get tile; '") + name +
        "' is not a var-sized attribute"));

  // For easy reference
  auto domain = array_schema_->domain();
  auto type = array_schema_->type(name);
  auto cell_num_in_tile = domain->cell_num_per_tile();
  auto tile_off_size = constants::cell_var_offset_size * cell_num_in_tile;
  auto buff_it = buffers_->find(name);
  assert(buff_it != buffers_->end());
  auto buff_var = (uint8_t*)buff_it->second.buffer_var_;
  auto div = (offsets_bytesize_ == 8) ? 1 : 2;
  uint64_t buff_off_size = *(buff_it->second.buffer_size_) / div;
  uint64_t buff_var_size = *(buff_it->second.buffer_var_size_);
  auto cell_size = datatype_size(type);
  std::vector<uint8_t> fill_var(sizeof(uint64_t), 0);

  // Initialize position tile
  Tile tile_pos;
  RETURN_NOT_OK(tile_pos.init_unfiltered(
      constants::format_version,
      constants::cell_var_offset_type,
      tile_off_size,
      constants::cell_var_offset_size,
      0));

  // Fill entire tile with MAX_UINT64
  std::vector<uint64_t> to_write(
      cell_num_in_tile, std::numeric_limits<uint64_t>::max());
  tile_pos.write(to_write.data(), tile_off_size);
  to_write.clear();
  to_write.shrink_to_fit();
  tile_pos.reset_offset();

  // Get position tile
  auto cell_num_in_buff =  // TODO: fix
      (buff_off_size - (offsets_extra_element_ * offsets_bytesize_)) /
      offsets_bytesize_;
  std::vector<uint64_t> cell_pos(cell_num_in_buff);
  for (uint64_t i = 0; i < cell_num_in_buff; ++i)
    cell_pos[i] = i;
  RETURN_NOT_OK(copy_tile(
      id, constants::cell_var_offset_size, (uint8_t*)&cell_pos[0], &tile_pos));

  // Initialize offset and value tiles
  RETURN_NOT_OK(tile_off->init_unfiltered(
      constants::format_version,
      constants::cell_var_offset_type,
      tile_off_size,
      constants::cell_var_offset_size,
      0));
  RETURN_NOT_OK(tile_val->init_unfiltered(
      constants::format_version,
      type,
      buff_var_size,  // This is only the initial allocation
      cell_size,
      0));

  // Copy real offsets and values to the corresponding tiles
  void* tile_pos_buff_tmp = tile_pos.buffer()->data();
  auto tile_pos_buff = (uint64_t*)tile_pos_buff_tmp;
  uint64_t offset = 0, val_offset, val_size, pos;
  auto mul = (offsets_format_mode_ == "bytes") ? 1 : cell_size;
  for (uint64_t i = 0; i < cell_num_in_tile; ++i) {
    pos = tile_pos_buff[i];
    tile_off->write(&offset, sizeof(offset));
    if (pos == std::numeric_limits<uint64_t>::max()) {  // Empty
      tile_val->write(&fill_var[0], cell_size);
      offset += cell_size;
    } else {  // Non-empty
      val_offset = ((offsets_bytesize_ == 8) ?
                        ((uint64_t*)buff_it->second.buffer_)[pos] :
                        ((uint32_t*)buff_it->second.buffer_)[pos]) *
                   mul;
      val_size = (pos < cell_num_in_buff - 1) ?
                     (((offsets_bytesize_ == 8) ?
                           ((uint64_t*)buff_it->second.buffer_)[pos + 1] :
                           ((uint32_t*)buff_it->second.buffer_)[pos + 1]) *
                          mul -
                      val_offset) :
                     buff_var_size - val_offset;
      tile_val->write(&buff_var[val_offset], val_size);
      offset += val_size;
    }
  }

  // Reset tile offsets
  tile_off->reset_offset();
  tile_val->reset_offset();

  return Status::Ok();
}

template <class T>
uint64_t DenseTiler<T>::tile_num() const {
  return tile_num_;
}

template <class T>
const std::vector<uint64_t>& DenseTiler<T>::tile_strides_el() const {
  return tile_strides_el_;
}

template <class T>
const std::vector<uint64_t>& DenseTiler<T>::sub_strides_el() const {
  return sub_strides_el_;
}

template <class T>
const std::vector<uint64_t>& DenseTiler<T>::sub_tile_coord_strides() const {
  return sub_tile_coord_strides_;
}

template <class T>
const std::vector<uint64_t>& DenseTiler<T>::first_sub_tile_coords() const {
  return first_sub_tile_coords_;
}

/* ****************************** */
/*          PRIVATE METHODS       */
/* ****************************** */

template <class T>
void DenseTiler<T>::calculate_first_sub_tile_coords() {
  // For easy reference
  auto dim_num = array_schema_->dim_num();
  auto domain = array_schema_->domain();
  auto subarray = subarray_->ndrange(0);

  // Calculate the coordinates of the first tile in the entire
  // domain that intersects the subarray (essentially its upper
  // left cell)
  first_sub_tile_coords_.resize(dim_num);
  for (unsigned d = 0; d < dim_num; ++d) {
    T dom_start = *(const T*)domain->dimension(d)->domain().start();
    T sub_start = *(const T*)subarray[d].start();
    T tile_extent = *(const T*)domain->tile_extent(d).data();
    first_sub_tile_coords_[d] =
        Dimension::tile_idx(sub_start, dom_start, tile_extent);
  }
}

template <class T>
void DenseTiler<T>::calculate_subarray_tile_coord_strides() {
  // For easy reference
  auto dim_num = (int32_t)array_schema_->dim_num();
  auto domain = array_schema_->domain();
  auto subarray = subarray_->ndrange(0);
  auto layout = array_schema_->tile_order();

  // Compute offsets
  sub_tile_coord_strides_.reserve(dim_num);
  if (layout == Layout::ROW_MAJOR) {
    sub_tile_coord_strides_.push_back(1);
    for (auto d = dim_num - 2; d >= 0; --d) {
      auto tile_num = domain->dimension(d + 1)->tile_num(subarray[d + 1]);
      sub_tile_coord_strides_.push_back(
          sub_tile_coord_strides_.back() * tile_num);
    }
    std::reverse(
        sub_tile_coord_strides_.begin(), sub_tile_coord_strides_.end());
  } else {  // COL_MAJOR
    sub_tile_coord_strides_.push_back(1);
    for (int32_t d = 1; d < dim_num; ++d) {
      auto tile_num = domain->dimension(d - 1)->tile_num(subarray[d - 1]);
      sub_tile_coord_strides_.push_back(
          sub_tile_coord_strides_.back() * tile_num);
    }
  }
}

template <class T>
void DenseTiler<T>::calculate_tile_and_subarray_strides() {
  // For easy reference
  auto sub_layout = subarray_->layout();
  assert(sub_layout == Layout::ROW_MAJOR || sub_layout == Layout::COL_MAJOR);
  auto tile_layout = array_schema_->cell_order();
  auto dim_num = (int32_t)array_schema_->dim_num();
  auto domain = array_schema_->domain();
  auto subarray = subarray_->ndrange(0);

  // Compute tile strides
  tile_strides_el_.resize(dim_num);
  if (tile_layout == Layout::ROW_MAJOR) {
    tile_strides_el_[dim_num - 1] = 1;
    if (dim_num > 1) {
      for (auto d = dim_num - 2; d >= 0; --d) {
        auto tile_extent = (const T*)(domain->tile_extent(d + 1).data());
        assert(tile_extent != nullptr);
        tile_strides_el_[d] = Dimension::tile_extent_mult<T>(
            tile_strides_el_[d + 1], *tile_extent);
      }
    }
  } else {  // COL_MAJOR
    tile_strides_el_[0] = 1;
    if (dim_num > 1) {
      for (auto d = 1; d < dim_num; ++d) {
        auto tile_extent = (const T*)(domain->tile_extent(d - 1).data());
        assert(tile_extent != nullptr);
        tile_strides_el_[d] = Dimension::tile_extent_mult<T>(
            tile_strides_el_[d - 1], *tile_extent);
      }
    }
  }

  // Compute subarray slides
  sub_strides_el_.resize(dim_num);
  if (sub_layout == Layout::ROW_MAJOR) {
    sub_strides_el_[dim_num - 1] = 1;
    if (dim_num > 1) {
      for (auto d = dim_num - 2; d >= 0; --d) {
        auto sub_range_start = *(const T*)subarray[d + 1].start();
        auto sub_range_end = *(const T*)subarray[d + 1].end();
        auto sub_extent = sub_range_end - sub_range_start + 1;
        sub_strides_el_[d] = sub_strides_el_[d + 1] * sub_extent;
      }
    }
  } else {  // COL_MAJOR
    sub_strides_el_[0] = 1;
    if (dim_num > 1) {
      for (auto d = 1; d < dim_num; ++d) {
        auto sub_range_start = *(const T*)subarray[d - 1].start();
        auto sub_range_end = *(const T*)subarray[d - 1].end();
        auto sub_extent = sub_range_end - sub_range_start + 1;
        sub_strides_el_[d] = sub_strides_el_[d - 1] * sub_extent;
      }
    }
  }
}

template <class T>
void DenseTiler<T>::calculate_tile_num() {
  tile_num_ = array_schema_->domain()->tile_num(subarray_->ndrange(0));
}

template <class T>
std::vector<uint64_t> DenseTiler<T>::tile_coords_in_sub(uint64_t id) const {
  // For easy reference
  auto dim_num = (int32_t)array_schema_->dim_num();
  auto layout = array_schema_->tile_order();
  std::vector<uint64_t> ret;
  auto tmp_idx = id;

  if (layout == Layout::ROW_MAJOR) {
    for (int32_t d = 0; d < dim_num; ++d) {
      ret.push_back(tmp_idx / sub_tile_coord_strides_[d]);
      tmp_idx %= sub_tile_coord_strides_[d];
    }
  } else {  // COL_MAJOR
    for (auto d = dim_num - 1; d >= 0; --d) {
      ret.push_back(tmp_idx / sub_tile_coord_strides_[d]);
      tmp_idx %= sub_tile_coord_strides_[d];
    }
    std::reverse(ret.begin(), ret.end());
  }

  return ret;
}

template <class T>
std::vector<std::array<T, 2>> DenseTiler<T>::tile_subarray(uint64_t id) const {
  // For easy reference
  auto dim_num = array_schema_->dim_num();
  auto domain = array_schema_->domain();

  // Get tile coordinates in the subarray tile domain
  auto tile_coords_in_sub = this->tile_coords_in_sub(id);

  // Get the tile coordinates in the array tile domain
  std::vector<uint64_t> tile_coords_in_dom(dim_num);
  for (unsigned d = 0; d < dim_num; ++d)
    tile_coords_in_dom[d] = tile_coords_in_sub[d] + first_sub_tile_coords_[d];

  // Calculate tile subarray based on the tile coordinates in the domain
  std::vector<std::array<T, 2>> ret(dim_num);
  for (unsigned d = 0; d < dim_num; ++d) {
    auto dom_start = *(const T*)domain->dimension(d)->domain().start();
    auto tile_extent = *(const T*)domain->tile_extent(d).data();
    ret[d][0] = Dimension::tile_coord_low(
        tile_coords_in_dom[d], dom_start, tile_extent);
    ret[d][1] = Dimension::tile_coord_high(
        tile_coords_in_dom[d], dom_start, tile_extent);
  }

  return ret;
}

template <class T>
Status DenseTiler<T>::copy_tile(
    uint64_t id, uint64_t cell_size, uint8_t* buff, Tile* tile) const {
  // Calculate copy plan
  const CopyPlan copy_plan = this->copy_plan(id);

  // For easy reference
  auto sub_offset = copy_plan.sub_start_el_ * cell_size;
  auto tile_offset = copy_plan.tile_start_el_ * cell_size;
  auto copy_nbytes = copy_plan.copy_el_ * cell_size;
  auto sub_strides_nbytes = copy_plan.sub_strides_el_;
  for (auto& bsn : sub_strides_nbytes)
    bsn *= cell_size;
  auto tile_strides_nbytes = copy_plan.tile_strides_el_;
  for (auto& tsn : tile_strides_nbytes)
    tsn *= cell_size;
  const auto& dim_ranges = copy_plan.dim_ranges_;
  auto first_d = copy_plan.first_d_;
  auto dim_num = (int64_t)dim_ranges.size();
  assert(dim_num > 0);

  // Auxiliary information needed in the copy loop
  std::vector<uint64_t> tile_offsets(dim_num);
  for (int64_t i = 0; i < dim_num; ++i)
    tile_offsets[i] = tile_offset;
  std::vector<uint64_t> sub_offsets(dim_num);
  for (int64_t i = 0; i < dim_num; ++i)
    sub_offsets[i] = sub_offset;
  std::vector<uint64_t> cell_coords(dim_num);
  for (int64_t i = 0; i < dim_num; ++i)
    cell_coords[i] = dim_ranges[i][0];

  // Perform the tile copy (always in row-major order)
  auto d = dim_num - 1;
  while (true) {
    // Copy a slab
    RETURN_NOT_OK(
        tile->write(&buff[sub_offsets[d]], tile_offsets[d], copy_nbytes));

    // Advance cell coordinates, tile and buffer offsets
    auto last_dim_changed = d;
    for (; last_dim_changed >= 0; --last_dim_changed) {
      ++cell_coords[last_dim_changed];
      if (cell_coords[last_dim_changed] > dim_ranges[last_dim_changed][1])
        cell_coords[last_dim_changed] = dim_ranges[last_dim_changed][0];
      else
        break;
    }

    // Check if copy loop is done
    if (last_dim_changed < 0)
      break;

    // Update the offsets
    tile_offsets[last_dim_changed] +=
        tile_strides_nbytes[last_dim_changed + first_d];
    sub_offsets[last_dim_changed] +=
        sub_strides_nbytes[last_dim_changed + first_d];
    for (auto i = last_dim_changed + 1; i < dim_num; ++i) {
      tile_offsets[i] = tile_offsets[i - 1];
      sub_offsets[i] = sub_offsets[i - 1];
    }
  }

  // Reset the tile offset to the beginning of the tile
  tile->reset_offset();

  return Status::Ok();
}

// Explicit template instantiations
template class DenseTiler<int8_t>;
template class DenseTiler<uint8_t>;
template class DenseTiler<int16_t>;
template class DenseTiler<uint16_t>;
template class DenseTiler<int32_t>;
template class DenseTiler<uint32_t>;
template class DenseTiler<int64_t>;
template class DenseTiler<uint64_t>;

}  // namespace sm
}  // namespace tiledb
