/*=========================================================================

  Program:   ParaView
  Module:    vtkReflection.h

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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.

=========================================================================*/
/**
 * @class vtkReflection
 * @brief
 *
 */

#ifndef vtkReflection_h
#define vtkReflection_h

#include "vtkLogger.h" // for vtkLogger
#include "vtkObject.h"
#include "vtkRemotingReflectionModule.h" // for exports
#include "vtkSmartPointer.h"             // for vtkSmartPointer
#include "vtkVariant.h"                  // for vtkVariant

#include <algorithm>   //for std::transform
#include <functional>  // for std::function
#include <string>      // for std::string
#include <type_traits> // for type_traits
#include <vector>      // for std::vector

class VTKREMOTINGREFLECTION_EXPORT vtkReflection : public vtkObject
{
public:
  static vtkReflection* New();
  vtkTypeMacro(vtkReflection, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Create an instance given the type name.
   * For this to work, the instantiator function must have been previously
   * registered using `RegisterNew`.
   */
  static vtkSmartPointer<vtkObject> CreateInstance(const std::string& name);

  template <typename T,
    typename SFINAE = typename std::enable_if<std::is_base_of<vtkObject, T>::value>::type>
  static vtkSmartPointer<T> CreateInstance(const std::string& name)
  {
    auto object = vtkReflection::CreateInstance(name);
    return T::SafeDownCast(object);
  }

  static bool Set(vtkObject* object, const std::string& methodName,
    const std::vector<vtkVariant>& args, const std::string& className = {});

  template <typename T>
  static bool Set(vtkObject* object, const std::string& methodName, const std::vector<T>& args)
  {
    std::vector<vtkVariant> variantArgs(args.size());
    std::copy(args.begin(), args.end(), variantArgs.begin());
    return vtkReflection::Set(object, methodName, variantArgs);
  }

  static bool Set(
    vtkObject* object, const std::string& methodName, const std::vector<std::string>& args)
  {
    std::vector<vtkVariant> variantArgs(args.size());
    std::transform(args.begin(), args.end(), variantArgs.begin(),
      [](const std::string& str) { return vtkVariant(vtkStdString(str)); });
    return vtkReflection::Set(object, methodName, variantArgs);
  }

  static bool Get(vtkObject* object, const std::string& methodName, std::vector<vtkVariant>& args,
    const std::string& className = {});

  /**
   * Register an instantiator callback.
   */
  static void RegisterNew(const std::string& className, std::function<vtkObject*()> constructor);

  /**
   * Register an instantiator callback for a vtkObject using `vtkObject::New` as
   * the instantiator.
   */
  template <typename VTKObject>
  static void RegisterNew(const std::string& className)
  {
    vtkReflection::RegisterNew(className, []() { return VTKObject::New(); });
  }

  static void RegisterSetter(const std::string& className,
    std::function<bool(vtkObject*, const std::string&, const std::vector<vtkVariant>&)> setter);
  static void RegisterGetter(const std::string& className,
    std::function<bool(vtkObject*, const std::string&, std::vector<vtkVariant>&)> getter);

  /**
   * Verbosity to use when logging reflection related debug messages.
   */
  static vtkLogger::Verbosity GetLogVerbosity();

protected:
  vtkReflection();
  ~vtkReflection() override;

private:
  vtkReflection(const vtkReflection&) = delete;
  void operator=(const vtkReflection&) = delete;
};

namespace vtk
{
template <typename T>
T variant_cast(const vtkVariant& variant);

template <>
inline double variant_cast<double>(const vtkVariant& variant)
{
  bool valid = false;
  double result = variant.ToDouble(&valid);
  if (!valid)
  {
    throw std::runtime_error("variant_cast<double> : Invalid cast");
  }
  return result;
}

template <>
inline bool variant_cast<bool>(const vtkVariant& variant)
{
  bool valid = false;
  // FIXME shold be add ToBool to vtkVariant ?
  bool result = static_cast<bool>(variant.ToInt(&valid));
  if (!valid)
  {
    throw std::runtime_error("variant_cast<bool> : Invalid cast");
  }
  return result;
}

template <>
inline int32_t variant_cast<int32_t>(const vtkVariant& variant)
{
  bool valid = false;
  int32_t result = variant.ToInt(&valid);
  if (!valid)
  {
    throw std::runtime_error("variant_cast<int32_t> : invalid cast");
  }
  return result;
}

template <>
inline int64_t variant_cast<int64_t>(const vtkVariant& variant)
{
  bool valid = false;
  int64_t result = variant.ToTypeInt64(&valid);
  if (!valid)
  {
    throw std::runtime_error("variant_cast<int64_t> :invalid cast");
  }
  return result;
}

template <>
inline std::string variant_cast<std::string>(const vtkVariant& variant)
{
  std::string result = variant.ToString();
  return result;
}

template <typename T>
inline T variant_cast_to_VTKObject(const vtkVariant& variant)
{
  if (!variant.IsValid())
  {
    return nullptr;
  }

  if (!variant.IsVTKObject())
  {
    throw std::runtime_error("variant_cast_to_VTKObject : invalid cast");
  }
  using VTK_TYPE = typename std::remove_pointer<T>::type;
  return VTK_TYPE::SafeDownCast(variant.ToVTKObject());
}

/**
 * Check whether T and the value in \p variant are compatible based on the conversions performed in
 * `vtk::variant_cast` In practice, we deem as incompatible only a couple of cases:
 * - T = arithmetic and variant cannot be converted to a string.
 * - T, and variant is a vtkObject with no inheritance relation to T.
 * - Any case where the variant is invalid and T != string.
 */
/**@{*/
template <typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
inline bool variant_is_compatible(const vtkVariant& variant)
{
  if (!variant.IsValid())
  {
    return false;
  }

  // Check if the string can become a double. This is enough to support all 4
  // numeric types we are interested in.
  if (variant.IsString())
  {
    bool validAsDouble;
    (void)variant.ToDouble(&validAsDouble);
    return validAsDouble;
  }
  return variant.IsNumeric();
}

template <typename T, std::enable_if_t<std::is_same<std::string, T>::value, bool> = true>
inline bool variant_is_compatible(const vtkVariant& variant)
{
  // even if variant is invalid variant.ToString() will return an empty string
  // this allows to accept nullptr as valid value for filters consuming a const char*.
  if (!variant.IsValid())
  {
    return true;
  }
  return !variant.IsVTKObject(); // everything else (numeric types) has meaningfull conversion
}

template <typename T, std::enable_if_t<std::is_base_of<vtkObjectBase, T>::value, bool> = true>
inline bool variant_is_compatible(const vtkVariant& variant)
{
  if (!variant.IsValid())
  {
    return false;
  }
  using VTK_TYPE = typename std::remove_pointer<T>::type;
  return VTK_TYPE::SafeDownCast(variant.ToVTKObject()) != nullptr;
}
/**@}*/
}

#define vtkRLogF(...) vtkVLogF(vtkReflection::GetLogVerbosity(), __VA_ARGS__)

#endif
