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

  Program:   ParaView
  Module:    vtkPythonObservableWrapper.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 vtkPythonObservableWrapper
 * @brief This class is used as an opaque object that allows to pass an
 * observable around when working on python.
 *
 * Currently the Python Wrapping Tools will skip functions returning
 * rxcpp::observable since they do not know how to handle it. To enable passing the
 * observable in the Python world we use vtkPythonObservableWrapper. It can be
 * constructed implicitly by an rxcpp::observable<T> (assuming `T` is one of
 * type appears in the list at the end of the file. To enable wrapping of a
 * function that returns an rxcpp::observable<T> simply add:
 *
 *  \code{.cpp}
 *  rxcpp::observable<T> FunctionsThaReturnsObservable(args)
 *  VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(T, FunctionsThaReturnsObservable(args))
 *  \endcode
 *
 *  For example:
 *  \code{.cpp}
 *   rxcpp::observable<bool> DeleteProxy(vtkSMSourceProxy* proxy);
 *   VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(bool, DeleteProxy(vtkSMSourceProxy* proxy))
 * \endcode
 *
 * @Note  When the return value is a raw pointer to a VTK Object use vtkObject*
 * in the macro to avoid having to derive a special vtkPythonObservableWrapper
 * class.
 * @TODO automatically handle this case ?
 *
 */

#ifndef vtkPythonObservableWrapper_h
#define vtkPythonObservableWrapper_h

#include "vtkObject.h"
#include "vtkRemotingWrappingModule.h" // for exports
#include "vtkSmartPointer.h"

#include "vtk_rxcpp.h" // for rxcpp
// clang-format off
#include VTK_REMOTING_RXCPP(rx.hpp)
// clang-format on

#include <algorithm> // for std::transform
#include <vector>    // for std::vector

template <typename T>
class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper
{
public:
  vtkPythonObservableWrapper() = default;

  // @brief Instantiate a wrapper using any observable
  //
  // Using a converting constructor allows to perform implicit conversion of any observable.
  template <typename U = T,
    typename SFINAE = typename std::enable_if<!std::is_base_of<vtkObject, U>::value>::type>
  vtkPythonObservableWrapper(rxcpp::observable<U> observable)
  {
    this->Observable = observable;
  }

  // Overload for vtkSmartPointers. Any vtkSmartPointer<U> is stored as vtkSmartPointer<vtkObject>
  template <typename U>
  vtkPythonObservableWrapper(rxcpp::observable<vtkSmartPointer<U>> observable)
  {
    this->Observable = observable.map([](vtkSmartPointer<U> value) {
      vtkSmartPointer<vtkObject> returnValue = vtkObject::SafeDownCast(value);
      return returnValue;
    });
  }

  template <typename U>
  vtkPythonObservableWrapper(rxcpp::observable<std::vector<vtkSmartPointer<U>>> observable)
  {
    this->Observable = observable.map([](std::vector<vtkSmartPointer<U>> values) {
      std::vector<vtkSmartPointer<vtkObject>> result;
      std::transform(
        values.begin(), values.end(), std::back_inserter(result), [](vtkSmartPointer<U> value) {
          vtkSmartPointer<vtkObject> returnValue = vtkObject::SafeDownCast(value);
          return returnValue;
        });
      return result;
    });
  }

  // Overload for vtkObject*. This should be used for any observable that holds any class derived
  // from vtkObject. An additional `.map` can be used to cast back to the expected class.
  template <typename U = T,
    typename SFINAE = typename std::enable_if<std::is_base_of<vtkObject, U>::value>::type>
  vtkPythonObservableWrapper(rxcpp::observable<U*> observable)
  {
    this->Observable = observable.map([](U* value) {
      vtkObject* returnValue = vtkObject::SafeDownCast(value);
      return returnValue;
    });
  }

  rxcpp::observable<T>& GetObservable() { return this->Observable; }

private:
  rxcpp::observable<T> Observable;
};

// The python wrapping infrastructure looks for derived classes to decide which template types to
// instantiate so, all supported types need to appear here
class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_bool
  : public vtkPythonObservableWrapper<bool>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_long
  : public vtkPythonObservableWrapper<long>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_vtkObject
  : public vtkPythonObservableWrapper<vtkObject*>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_vtkSmartPointer
  : public vtkPythonObservableWrapper<vtkSmartPointer<vtkObject>>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_vector_vtkSmartPointer
  : public vtkPythonObservableWrapper<std::vector<vtkSmartPointer<vtkObject>>>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_string
  : public vtkPythonObservableWrapper<std::string>
{
};

class VTKREMOTINGWRAPPING_EXPORT vtkPythonObservableWrapper_vtkTypeUInt32
  : public vtkPythonObservableWrapper<vtkTypeUInt32>
{
};

/* Use it to duplicate a function's signature in a method suitable for the wrapping code */

#ifdef __WRAP__
#define VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(returnType, functionSignature)                         \
  vtkPythonObservableWrapper<returnType> functionSignature;
#else
#define VTK_REMOTING_MAKE_PYTHON_OBSERVABLE(returnType, functionSignature)
#endif

#endif
