// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkPythonItem.h"
#include "vtkObjectFactory.h"

#include "vtkContext2D.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkPythonUtil.h"
#include "vtkSmartPyObject.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPythonItem);

//------------------------------------------------------------------------------
void vtkPythonItem::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  vtkPythonScopeGilEnsurer gilEnsurer;
  vtkSmartPyObject str;
  if (this->Object)
  {
    str.TakeReference(PyObject_Str(this->Object));
  }

  os << indent << "Object: " << Object << std::endl;
  if (str)
  {
    os << indent << "Object (string): ";
    PyObject* bytes = PyUnicode_EncodeLocale(str, VTK_PYUNICODE_ENC);
    if (bytes)
    {
      os << PyBytes_AsString(bytes);
      Py_DECREF(bytes);
    }
    os << std::endl;
  }
}

//------------------------------------------------------------------------------
vtkPythonItem::vtkPythonItem()
{
  this->Object = nullptr;
}

//------------------------------------------------------------------------------
vtkPythonItem::~vtkPythonItem()
{
  // we check if Python is still initialized since the Python interpreter may
  // have been finalized before the VTK object is released.
  if (Py_IsInitialized())
  {
    vtkPythonScopeGilEnsurer gilEnsurer;
    Py_XDECREF(this->Object);
  }
}

// This macro gets the method passed in as the parameter method
// from the PyObject passed in as the parameter obj and creates a
// vtkSmartPyObject variable with the name passed in as the parameter
// var containing that method's PyObject.  If obj is nullptr, obj.method
// does not exist or obj.method is not a callable method, this macro
// causes the function using it to return with the return value
// passed in as the parameter failValue
//    var - the name of the resulting vtkSmartPyObject with the
//          method object in it.  Can be used in the code following
//          the macro's use as the variable name
//    obj - the PyObject to get the method from
//    method - the name of the method to look for.  Should be a
//          C string.
//    failValue - the value to return if the lookup fails and the
//          function using the macro should return.  Pass in a
//          block comment /**/ for void functions using this macro
#define VTK_GET_METHOD(var, obj, method, failValue)                                                \
  if (!(obj))                                                                                      \
  {                                                                                                \
    return failValue;                                                                              \
  }                                                                                                \
  vtkSmartPyObject var(PyObject_GetAttrString(obj, method));                                       \
  if (!(var))                                                                                      \
  {                                                                                                \
    return failValue;                                                                              \
  }                                                                                                \
  if (!PyCallable_Check(var))                                                                      \
  {                                                                                                \
    return failValue;                                                                              \
  }

//------------------------------------------------------------------------------
static PyObject* VTKToPython(vtkObjectBase* obj)
{
  // Return value: New reference.
  return vtkPythonUtil::GetObjectFromPointer(obj);
}

//------------------------------------------------------------------------------
bool vtkPythonItem::CheckResult(const char* method, const vtkSmartPyObject& res)
{
  vtkPythonScopeGilEnsurer gilEnsurer;
  if (!res)
  {
    vtkErrorMacro("Failure when calling method: \"" << method << "\":");
    if (PyErr_Occurred() != nullptr)
    {
      PyErr_Print();
      PyErr_Clear();
    }
    return false;
  }
  if (!PyBool_Check(res))
  {
    vtkWarningMacro("The method \"" << method << "\" should have returned boolean but did not");
    return false;
  }

  return res != Py_False;
}

//------------------------------------------------------------------------------
void vtkPythonItem::SetPythonObject(PyObject* obj)
{
  vtkPythonScopeGilEnsurer gilEnsurer;

  if (!obj)
  {
    return;
  }

  Py_XDECREF(this->Object);

  this->Object = obj;
  Py_INCREF(this->Object);

  const char* mname = "Initialize";
  VTK_GET_METHOD(method, this->Object, mname, /* no return */)

  PyObject* vtkself = VTKToPython(this);
  vtkSmartPyObject args(PyTuple_Pack(1, vtkself));
  Py_DECREF(vtkself);

  vtkSmartPyObject result(PyObject_Call(method, args, nullptr));

  CheckResult(mname, result);
}

//------------------------------------------------------------------------------
bool vtkPythonItem::Paint(vtkContext2D* painter)
{
  vtkPythonScopeGilEnsurer gilEnsurer;
  const char* mname = "Paint";
  VTK_GET_METHOD(method, this->Object, mname, 0)

  PyObject* vtkself = VTKToPython(this);
  PyObject* pypainter = VTKToPython(painter);
  vtkSmartPyObject args(PyTuple_Pack(2, vtkself, pypainter));
  Py_DECREF(vtkself);
  Py_DECREF(pypainter);

  vtkSmartPyObject result(PyObject_Call(method, args, nullptr));

  return CheckResult(mname, result);
}
VTK_ABI_NAMESPACE_END
