//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//============================================================================

#ifndef vtk_m_exec_serial_internal_TaskTiling_h
#define vtk_m_exec_serial_internal_TaskTiling_h

#include <vtkm/exec/TaskBase.h>

#include <vtkmtaotuple/include/Tuple.h>
#include <vtkmtaotuple/include/tao/seq/make_integer_sequence.hpp>

namespace vtkm
{
namespace exec
{
namespace serial
{
namespace internal
{

template <typename Functor, typename IndexType, typename TupleType, std::size_t... I>
inline void TaskTilingApply(Functor&& functor,
                            IndexType&& index,
                            TupleType&& tuple,
                            tao::seq::index_sequence<I...>)
{
  functor(std::forward<IndexType>(index), vtkmstd::get<I>(tuple)...);
}

template <typename FType, typename TupleType>
VTKM_NEVER_EXPORT void TaskTilingSetErrorBuffer(
  void* f,
  const vtkm::exec::internal::ErrorMessageBuffer& buffer)
{
  using FunctorType = typename std::remove_cv<FType>::type;
  FunctorType* const functor = static_cast<FunctorType*>(f);
  functor->SetErrorMessageBuffer(buffer);
}

template <typename FType, typename TupleType>
VTKM_NEVER_EXPORT void TaskTiling1DExecute(void* f, void* const t, vtkm::Id start, vtkm::Id end)
{
  using FunctorType = typename std::remove_cv<FType>::type;

  FunctorType const* const functor = static_cast<FunctorType*>(f);
  TupleType const* const tuple = static_cast<TupleType*>(t);

  for (vtkm::Id index = start; index < end; ++index)
  {
    TaskTilingApply(*functor,
                    index,
                    *tuple,
                    tao::seq::make_index_sequence<vtkmstd::tuple_size<TupleType>::value>{});
  }
}

template <typename FType, typename TupleType>
VTKM_NEVER_EXPORT void TaskTiling3DExecute(void* f,
                                           void* const t,
                                           vtkm::Id istart,
                                           vtkm::Id iend,
                                           vtkm::Id j,
                                           vtkm::Id k)
{
  using FunctorType = typename std::remove_cv<FType>::type;

  FunctorType const* const functor = static_cast<FunctorType*>(f);
  TupleType const* const tuple = static_cast<TupleType*>(t);

  vtkm::Id3 index(istart, j, k);
  for (; index[0] < iend; ++index[0])
  {
    TaskTilingApply(*functor,
                    index,
                    *tuple,
                    tao::seq::make_index_sequence<vtkmstd::tuple_size<TupleType>::value>{});
  }
}

// TaskTiling1D represents an execution pattern for a worklet
// that is best expressed in terms of single dimension iteration space. TaskTiling1D
// also states that for best performance a linear consecutive range of values
// should be given to the worklet for optimal performance.
//
// Note: The worklet and invocation must have a lifetime that is at least
// as long as the Task
class VTKM_NEVER_EXPORT TaskTiling1D : public vtkm::exec::TaskBase
{
public:
  TaskTiling1D()
    : Functor(nullptr)
    , Arguments(nullptr)
  {
  }

  /// This constructor supports general functors that have a call
  /// operator with the signature:
  ///   operator()(vtkm::Id, arguments...)
  template <typename FunctorType, typename... Args>
  TaskTiling1D(FunctorType& functor, Args&&... arguments)
    : Functor(&functor)
    , Arguments(new vtkmstd::tuple<Args...>(std::forward<Args>(arguments)...))
    , ExecuteFunction(&TaskTiling1DExecute<FunctorType, vtkmstd::tuple<Args...>>)
    , SetErrorBufferFunction(&TaskTilingSetErrorBuffer<FunctorType, vtkmstd::tuple<Args...>>)
  {
  }

  /// explicit Copy constructor.
  /// Note this required so that compilers don't use the templated constructor
  /// as the copy constructor which will cause compile issues
  TaskTiling1D(const TaskTiling1D& task)
    : Functor(task.Functor)
    , Arguments(task.Arguments)
    , ExecuteFunction(task.ExecuteFunction)
    , SetErrorBufferFunction(task.SetErrorBufferFunction)
  {
  }

  TaskTiling1D(TaskTiling1D&& task) = default;

  void SetErrorMessageBuffer(const vtkm::exec::internal::ErrorMessageBuffer& buffer)
  {
    this->SetErrorBufferFunction(this->Functor, buffer);
  }

  void operator()(vtkm::Id start, vtkm::Id end) const
  {
    this->ExecuteFunction(this->Functor, this->Arguments.get(), start, end);
  }

protected:
  void* Functor;
  std::shared_ptr<void> Arguments;

  using ExecuteSignature = void (*)(void*, void* const, vtkm::Id, vtkm::Id);
  ExecuteSignature ExecuteFunction;

  using SetErrorBufferSignature = void (*)(void*, const vtkm::exec::internal::ErrorMessageBuffer&);
  SetErrorBufferSignature SetErrorBufferFunction;
};

// TaskTiling3D represents an execution pattern for a worklet
// that is best expressed in terms of an 3 dimensional iteration space. TaskTiling3D
// also states that for best performance a linear consecutive range of values
// in the X dimension should be given to the worklet for optimal performance.
//
// Note: The worklet and invocation must have a lifetime that is at least
// as long as the Task
class VTKM_NEVER_EXPORT TaskTiling3D : public vtkm::exec::TaskBase
{
public:
  TaskTiling3D()
    : Functor(nullptr)
    , Arguments(nullptr)
  {
  }

  /// This constructor supports general functors that which have a call
  /// operator with the signature:
  ///   operator()(vtkm::Id3, arguments...)
  template <typename WorkletType, typename... Args>
  TaskTiling3D(WorkletType& worklet, Args&&... arguments)
    : Functor(reinterpret_cast<void*>(&worklet))
    , Arguments(new vtkmstd::tuple<Args...>(std::forward<Args>(arguments)...))
    , ExecuteFunction(&TaskTiling3DExecute<WorkletType, vtkmstd::tuple<Args...>>)
    , SetErrorBufferFunction(&TaskTilingSetErrorBuffer<WorkletType, vtkmstd::tuple<Args...>>)
  {
  }

  /// explicit Copy constructor.
  /// Note this required so that compilers don't use the templated constructor
  /// as the copy constructor which will cause compile issues
  TaskTiling3D(TaskTiling3D& task)
    : Functor(task.Functor)
    , Arguments(task.Arguments)
    , ExecuteFunction(task.ExecuteFunction)
    , SetErrorBufferFunction(task.SetErrorBufferFunction)
  {
  }

  TaskTiling3D(TaskTiling3D&& task) = default;

  void SetErrorMessageBuffer(const vtkm::exec::internal::ErrorMessageBuffer& buffer)
  {
    this->SetErrorBufferFunction(this->Functor, buffer);
  }

  void operator()(vtkm::Id istart, vtkm::Id iend, vtkm::Id j, vtkm::Id k) const
  {
    this->ExecuteFunction(this->Functor, this->Arguments.get(), istart, iend, j, k);
  }

protected:
  void* Functor;
  std::shared_ptr<void> Arguments;

  using ExecuteSignature = void (*)(void*, void* const, vtkm::Id, vtkm::Id, vtkm::Id, vtkm::Id);
  ExecuteSignature ExecuteFunction;

  using SetErrorBufferSignature = void (*)(void*, const vtkm::exec::internal::ErrorMessageBuffer&);
  SetErrorBufferSignature SetErrorBufferFunction;
};
}
}
}
} // vtkm::exec::serial::internal

#endif //vtk_m_exec_serial_internal_TaskTiling_h
