// Author: Stefan Wunsch CERN  04/2019
// Original PyROOT code by Wim Lavrijsen, LBL

/*************************************************************************
 * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#include "CPyCppyy.h"
#include "CPPInstance.h"
#include "ProxyWrappers.h"
#include "PyROOTPythonize.h"
#include "RConfig.h"
#include "TInterpreter.h"
#include "CPyCppyy/API.h"

#include <utility> // std::pair
#include <sstream> // std::stringstream

////////////////////////////////////////////////////////////////////////////
/// \brief Make an RDataFrame from a dictionary of numpy arrays
/// \param[in] pydata Dictionary with numpy arrays
///
/// This function takes a dictionary of numpy arrays and creates an RDataFrame
/// using the keys as column names and the numpy arrays as data.
PyObject *PyROOT::MakeNumpyDataFrame(PyObject * /*self*/, PyObject * pydata)
{
   if (!pydata) {
      PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Invalid Python object.");
      return NULL;
   }

   if (!PyDict_Check(pydata)) {
      PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Python object is not a dictionary.");
      return NULL;
   }

   if (PyDict_Size(pydata) == 0) {
      PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Dictionary is empty.");
      return NULL;
   }


   // Add PyObject (dictionary) holding RVecs to data source
   std::stringstream code;
   code << "ROOT::Internal::RDF::MakeNumpyDataFrame(";
   std::stringstream pyaddress;
   auto pyvecs = PyDict_New();
   pyaddress << pyvecs;
   code << "reinterpret_cast<PyObject*>(" << pyaddress.str() << "), ";

   // Iterate over dictionary, convert numpy arrays to RVecs and put together interpreter code
   PyObject *key, *value;
   Py_ssize_t pos = 0;
   const auto size = PyObject_Size(pydata);
   auto counter = 0u;
   while (PyDict_Next(pydata, &pos, &key, &value)) {
      // Get name of key
      if (!CPyCppyy_PyUnicode_Check(key)) {
         PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Dictionary key is not convertible to a string.");
         return NULL;
      }
      std::string keystr = CPyCppyy_PyUnicode_AsString(key);

      // Convert value to RVec and attach to dictionary
      auto pyvec = PyROOT::AsRVec(NULL, value);
      if (pyvec == NULL) {
         PyErr_SetString(PyExc_RuntimeError,
                         ("Object not convertible: Dictionary entry " + keystr + " is not convertible with AsRVec.").c_str());
         return NULL;
      }
      PyDict_SetItem(pyvecs, key, pyvec);
      Py_DECREF(pyvec);

      // Add pairs of column name and associated RVec to signature
      std::string vectype = Cppyy::GetScopedFinalName(((CPyCppyy::CPPInstance*)pyvec)->ObjectIsA());
      std::stringstream vecaddress;
      vecaddress << ((CPyCppyy::CPPInstance*)pyvec)->GetObject();
      code << "std::pair<std::string, " << vectype <<  "*>(\"" + keystr
           << "\", reinterpret_cast<" << vectype+ "*>(" << vecaddress.str() << "))";
      if (counter != size - 1) {
         code << ",";
      } else {
         code << ");";
      }
      counter++;
   }

   // Create RDataFrame and build Python proxy
   const auto err = gInterpreter->Declare("#include \"ROOT/RNumpyDS.hxx\"");
   if (!err) {
      PyErr_SetString(PyExc_RuntimeError, "Failed to find \"ROOT/RNumpyDS.hxx\".");
      return NULL;
   }
   const auto codeStr = code.str();
   auto address = (void*) gInterpreter->Calc(codeStr.c_str());
   const auto pythonOwns = true;
   auto pyobj = TPython::CPPInstance_FromVoidPtr(address, "ROOT::RDataFrame", pythonOwns);

   // Bind pyobject holding adopted memory to the RVec
   if (PyObject_SetAttrString(pyobj, "__data__", pyvecs)) {
      PyErr_SetString(PyExc_RuntimeError, "Object not convertible: Failed to set dictionary as attribute __data__.");
      return NULL;
   }
   Py_DECREF(pyvecs);

   return pyobj;
}