// Filename: interfaceMakerPythonObj.cxx
// Created by:  drose (19Sep01)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "interfaceMakerPythonObj.h"
#include "interrogateBuilder.h"
#include "interrogate.h"
#include "functionRemap.h"
#include "parameterRemapUnchanged.h"
#include "typeManager.h"
#include "functionWriterPtrFromPython.h"
#include "functionWriterPtrToPython.h"

#include "interrogateDatabase.h"
#include "interrogateType.h"
#include "interrogateFunction.h"
#include "cppFunctionType.h"

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
InterfaceMakerPythonObj::
InterfaceMakerPythonObj(InterrogateModuleDef *def) :
  InterfaceMakerPython(def)
{
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
InterfaceMakerPythonObj::
~InterfaceMakerPythonObj() {
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_prototypes
//       Access: Public, Virtual
//  Description: Generates the list of function prototypes
//               corresponding to the functions that will be output in
//               write_functions().
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_prototypes(ostream &out) {
  Functions::iterator fi;
  for (fi = _functions.begin(); fi != _functions.end(); ++fi) {
    Function *func = (*fi);
    write_prototype_for(out, func);
  }

  out << "\n";
  InterfaceMakerPython::write_prototypes(out);
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_functions
//       Access: Public, Virtual
//  Description: Generates the list of functions that are appropriate
//               for this interface.  This function is called *before*
//               write_prototypes(), above.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_functions(ostream &out) {
  Functions::iterator fi;
  for (fi = _functions.begin(); fi != _functions.end(); ++fi) {
    Function *func = (*fi);
    write_function_for(out, func);
  }

  InterfaceMakerPython::write_functions(out);

  // Finally, generate all the make-class-wrapper functions.
  Objects::iterator oi;
  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
    Object *object = (*oi).second;

    write_class_wrapper(out, object);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_module
//       Access: Public, Virtual
//  Description: Generates whatever additional code is required to
//               support a module file.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_module(ostream &out, InterrogateModuleDef *def) {
  InterfaceMakerPython::write_module(out, def);

  out << "static PyMethodDef python_obj_funcs[] = {\n";

  Objects::iterator oi;
  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
    Object *object = (*oi).second;

    Functions::iterator fi;
    for (fi = object->_constructors.begin(); 
         fi != object->_constructors.end(); 
         ++fi) {
      Function *func = (*fi);
      out << "  { \"" << func->_ifunc.get_name() << "\", &" << func->_name 
          << ", METH_VARARGS },\n";
    }
  }  
  out << "  { NULL, NULL }\n"
      << "};\n\n"

      << "#ifdef _WIN32\n"
      << "extern \"C\" __declspec(dllexport) void init" << def->library_name << "();\n"
      << "#else\n"
      << "extern \"C\" void init" << def->library_name << "();\n"
      << "#endif\n\n"
    
      << "void init" << def->library_name << "() {\n"
      << "  Py_InitModule(\"" << def->library_name
      << "\", python_obj_funcs);\n"
      << "}\n\n";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::synthesize_this_parameter
//       Access: Public, Virtual
//  Description: This method should be overridden and redefined to
//               return true for interfaces that require the implicit
//               "this" parameter, if present, to be passed as the
//               first parameter to any wrapper functions.
////////////////////////////////////////////////////////////////////
bool InterfaceMakerPythonObj::
synthesize_this_parameter() {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::get_builder_name
//       Access: Public, Static
//  Description: Returns the name of the InterfaceMaker function
//               generated to define the Python class for the
//               indicated struct type.
////////////////////////////////////////////////////////////////////
string InterfaceMakerPythonObj::
get_builder_name(CPPType *struct_type) {
  return "get_python_class_" + 
    InterrogateBuilder::clean_identifier(struct_type->get_local_name(&parser));
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::get_wrapper_prefix
//       Access: Protected, Virtual
//  Description: Returns the prefix string used to generate wrapper
//               function names.
////////////////////////////////////////////////////////////////////
string InterfaceMakerPythonObj::
get_wrapper_prefix() {
  return "wpo_";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_class_wrapper
//       Access: Private
//  Description: Writes a function that will define the Python class.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_class_wrapper(ostream &out, InterfaceMaker::Object *object) {
  CPPType *struct_type = object->_itype._cpptype;
  if (struct_type == (CPPType *)NULL) {
    return;
  }

  string name = get_builder_name(struct_type);
  string python_name =
    InterrogateBuilder::clean_identifier(struct_type->get_simple_name());

  out << "/*\n"
      << " * Generate unique Python class for "
      << struct_type->get_local_name(&parser) << "\n"
      << " */\n"
      << "PyObject *\n"
      << name << "() {\n"
      << "  static PyObject *wrapper = (PyObject *)NULL;\n"
      << "  static PyMethodDef methods[] = {\n";

  int methods_size = 0;
  int class_methods_size = 0;

  Functions::iterator fi;
  for (fi = object->_methods.begin(); fi != object->_methods.end(); ++fi) {
    Function *func = (*fi);
    if (func->_has_this) {
      out << "  { \"" << func->_ifunc.get_name() << "\", &" << func->_name 
          << ", METH_VARARGS },\n";
      methods_size++;
    }
  }

  out << "  };\n"
      << "  static const int methods_size = " << methods_size << ";\n\n"
      << "  static PyMethodDef class_methods[] = {\n";

  for (fi = object->_methods.begin(); fi != object->_methods.end(); ++fi) {
    Function *func = (*fi);
    if (!func->_has_this) {
      out << "  { \"" << func->_ifunc.get_name() << "\", &" << func->_name 
          << ", METH_VARARGS },\n";
      class_methods_size++;
    }
  }

  out << "  };\n"
      << "  static const int class_methods_size = " << class_methods_size << ";\n\n"
      << "  if (wrapper == (PyObject *)NULL) {\n"
      << "    int i;\n"
      << "    PyObject *bases = PyTuple_New(0);\n"
      << "    PyObject *dict = PyDict_New();\n"
      << "    PyObject *name = PyString_FromString(\""
      << python_name << "\");\n"
      << "    wrapper = PyClass_New(bases, dict, name);\n"
      << "    for (i = 0; i < methods_size; i++) {\n"
      << "      PyObject *function, *method;\n"
      << "      function = PyCFunction_New(&methods[i], (PyObject *)NULL);\n"
      << "      method = PyMethod_New(function, (PyObject *)NULL, wrapper);\n"
      << "      PyDict_SetItemString(dict, methods[i].ml_name, method);\n"
      << "    }\n"
      << "    for (i = 0; i < class_methods_size; i++) {\n"
      << "      PyObject *function;\n"
      << "      function = PyCFunction_New(&class_methods[i], (PyObject *)NULL);\n"
      << "      PyDict_SetItemString(dict, class_methods[i].ml_name, function);\n"
      << "    }\n"
      << "  }\n"
      << "  return wrapper;\n"
      << "}\n\n";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_prototype_for
//       Access: Private
//  Description: Writes the prototype for the indicated function.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_prototype_for(ostream &out, InterfaceMaker::Function *func) {
  out << "static PyObject *"
      << func->_name << "(PyObject *self, PyObject *args);\n";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_function_for
//       Access: Private
//  Description: Writes the definition for a function that will call
//               the indicated C++ function or method.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_function_for(ostream &out, InterfaceMaker::Function *func) {
  Function::Remaps::const_iterator ri;
  out << "/*\n"
      << " * Python object wrapper for\n";
  for (ri = func->_remaps.begin(); ri != func->_remaps.end(); ++ri) {
    FunctionRemap *remap = (*ri);
    out << " * ";
    remap->write_orig_prototype(out, 0);
    out << "\n";
  }
  out << " */\n";

  out << "static PyObject *"
      << func->_name << "(PyObject *, PyObject *args) {\n";

  // Now write out each instance of the overloaded function.
  string expected_params = "Arguments must match one of:";

  for (ri = func->_remaps.begin(); ri != func->_remaps.end(); ++ri) {
    FunctionRemap *remap = (*ri);
    expected_params += "\\n  ";
    write_function_instance(out, 2, func, remap, expected_params);
  }

  // If we get here in the generated code, none of the parameters were
  // valid.  Generate an error exception.  (We don't rely on the error
  // already generated by ParseTuple(), because it only reports the
  // error for one flavor of the function, whereas we might accept
  // multiple flavors for the different overloaded C++ function
  // signatures.

  out << "  PyErr_SetString(PyExc_TypeError, \"" << expected_params << "\");\n"
      << "  return (PyObject *)NULL;\n";

  out << "}\n\n";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::write_function_instance
//       Access: Private
//  Description: Writes out the part of a function that handles a
//               single instance of an overloaded function.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
write_function_instance(ostream &out, int indent_level,
                        InterfaceMaker::Function *func,
                        FunctionRemap *remap, string &expected_params) {
  indent(out, indent_level) << "{\n";
  indent(out, indent_level + 2) << "/* ";
  remap->write_orig_prototype(out, 0);
  out << " */\n\n";
  
  string format_specifiers;
  string parameter_list;
  vector_string pexprs;
  string extra_convert;
  string extra_param_check;
  string extra_cleanup;

  // Make one pass through the parameter list.  We will output a
  // one-line temporary variable definition for each parameter, while
  // simultaneously building the ParseTuple() function call and also
  // the parameter expression list for call_function().

  expected_params += remap->_cppfunc->get_simple_name();
  expected_params += "(";

  int pn;
  for (pn = 0; pn < (int)remap->_parameters.size(); pn++) {
    if (pn != 0) {
      expected_params += ", ";
    }

    indent(out, indent_level + 2);
    CPPType *orig_type = remap->_parameters[pn]._remap->get_orig_type();
    CPPType *type = remap->_parameters[pn]._remap->get_new_type();
    string param_name = remap->get_parameter_name(pn);
    
    // This is the string to convert our local variable to the
    // appropriate C++ type.  Normally this is just a cast.
    string pexpr_string =
      "(" + type->get_local_name(&parser) + ")" + param_name;
    
    if (remap->_parameters[pn]._remap->new_type_is_atomic_string()) {
      if (TypeManager::is_char_pointer(orig_type)) {
        out << "char *" << param_name;
        format_specifiers += "s";
        parameter_list += ", &" + param_name;
        
      } else {
        out << "char *" << param_name
            << "_str; int " << param_name << "_len";
        format_specifiers += "s#";
        parameter_list += ", &" + param_name
          + "_str, &" + param_name + "_len";
        pexpr_string = "basic_string<char>(" +
          param_name + "_str, " +
          param_name + "_len)";
      }
      expected_params += "string";
      
    } else if (TypeManager::is_bool(type)) {
      out << "PyObject *" << param_name;
      format_specifiers += "O";
      parameter_list += ", &" + param_name;
      pexpr_string = "(PyObject_IsTrue(" + param_name + ")!=0)";
      expected_params += "bool";

    } else if (TypeManager::is_unsigned_longlong(type)) {
      out << "PyObject *" << param_name;
      format_specifiers += "O";
      parameter_list += ", &" + param_name;
      extra_convert += " PyObject *" + param_name + "_long = PyNumber_Long(" + param_name + ");";
      extra_param_check += "|| (" + param_name + "_long == NULL)";
      pexpr_string = "PyLong_AsUnsignedLongLong(" + param_name + "_long)";
      extra_cleanup += " Py_XDECREF(" + param_name + "_long);";
      expected_params += "long";

    } else if (TypeManager::is_longlong(type)) {
      out << "PyObject *" << param_name;
      format_specifiers += "O";
      parameter_list += ", &" + param_name;
      extra_convert += " PyObject *" + param_name + "_long = PyNumber_Long(" + param_name + ");";
      extra_param_check += "|| (" + param_name + "_long == NULL)";
      pexpr_string = "PyLong_AsLongLong(" + param_name + "_long)";
      extra_cleanup += " Py_XDECREF(" + param_name + "_long);";
      expected_params += "long";

    } else if (TypeManager::is_integer(type)) {
      out << "int " << param_name;
      format_specifiers += "i";
      parameter_list += ", &" + param_name;
      expected_params += "integer";

    } else if (TypeManager::is_float(type)) {
      out << "double " << param_name;
      format_specifiers += "d";
      parameter_list += ", &" + param_name;
      expected_params += "float";

    } else if (TypeManager::is_char_pointer(type)) {
      out << "char *" << param_name;
      format_specifiers += "s";
      parameter_list += ", &" + param_name;
      expected_params += "string";

    } else if (TypeManager::is_pointer(type)) {
      FunctionWriterPtrFromPython *writer = get_ptr_from_python(type);
      writer->get_pointer_type()->output_instance(out, param_name, &parser);
      format_specifiers += "O&";
      parameter_list += ", &" + writer->get_name() + ", &" + param_name;
      expected_params += writer->get_type()->get_preferred_name();

    } else {
      // Ignore a parameter.
      out << "PyObject *" << param_name;
      format_specifiers += "O";
      parameter_list += ", &" + param_name;
      expected_params += "any";
    }

    if (remap->_parameters[pn]._has_name) {
      expected_params += " " + remap->_parameters[pn]._name;
    }

    out << ";\n";
    pexprs.push_back(pexpr_string);
  }
  expected_params += ")";

  indent(out, indent_level + 2)
    << "if (PyArg_ParseTuple(args, \"" << format_specifiers
    << "\"" << parameter_list << ")) {\n";

  if (!extra_convert.empty()) {
    indent(out, indent_level + 3)
      << extra_convert << "\n";
  }

  if (!extra_param_check.empty()) {
    indent(out, indent_level + 4)
      << "if (" << extra_param_check.substr(3) << ") {\n";
    if (!extra_cleanup.empty()) {
      indent(out, indent_level + 5)
        << extra_cleanup << "\n";
    }
    indent(out, indent_level + 6)
      << "PyErr_SetString(PyExc_TypeError, \"Invalid parameters.\");\n";
    indent(out, indent_level + 6)
      << "return (PyObject *)NULL;\n";
    indent(out, indent_level + 4)
      << "}\n";
  }

  if (track_interpreter) {
    indent(out, indent_level + 4)
      << "in_interpreter = 0;\n";
  }

  if (!remap->_void_return && 
      remap->_return_type->new_type_is_atomic_string()) {
    // Treat strings as a special case.  We don't want to format the
    // return expression.
    string return_expr = 
      remap->call_function(out, indent_level + 4, false, "param0", pexprs);

    CPPType *type = remap->_return_type->get_orig_type();
    indent(out, indent_level + 4);
    type->output_instance(out, "return_value", &parser);
    out << " = " << return_expr << ";\n";

    if (track_interpreter) {
      indent(out, indent_level + 4)
        << "in_interpreter = 1;\n";
    }
    if (!extra_cleanup.empty()) {
      indent(out, indent_level + 3)
        << extra_cleanup << "\n";
    }

    return_expr = manage_return_value(out, indent_level + 4, remap, "return_value");
    test_assert(out, indent_level + 4);
    pack_return_value(out, indent_level + 4, remap, return_expr);

  } else {
    string return_expr = 
      remap->call_function(out, indent_level + 4, true, "param0", pexprs);
    if (return_expr.empty()) {
      if (track_interpreter) {
        indent(out, indent_level + 4)
          << "in_interpreter = 1;\n";
      }
      if (!extra_cleanup.empty()) {
        indent(out, indent_level + 3)
          << extra_cleanup << "\n";
      }
      test_assert(out, indent_level + 4);
      indent(out, indent_level + 4)
        << "return Py_BuildValue(\"\");\n";

    } else {
      CPPType *type = remap->_return_type->get_temporary_type();
      indent(out, indent_level + 4);
      type->output_instance(out, "return_value", &parser);
      out << " = " << return_expr << ";\n";
      if (track_interpreter) {
        indent(out, indent_level + 4)
          << "in_interpreter = 1;\n";
      }
      if (!extra_cleanup.empty()) {
        indent(out, indent_level + 3)
          << extra_cleanup << "\n";
      }

      return_expr = manage_return_value(out, indent_level + 4, remap, "return_value");
      test_assert(out, indent_level + 4);
      pack_return_value(out, indent_level + 4, remap, remap->_return_type->temporary_to_return(return_expr));
    }
  }

  indent(out, indent_level + 2) << "}\n";
  indent(out, indent_level + 2)
    << "PyErr_Clear();\n";  // Clear the error generated by ParseTuple()
  indent(out, indent_level)
    << "}\n";
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::pack_return_value
//       Access: Private
//  Description: Outputs a command to pack the indicated expression,
//               of the return_type type, as a Python return value.
////////////////////////////////////////////////////////////////////
void InterfaceMakerPythonObj::
pack_return_value(ostream &out, int indent_level,
                  FunctionRemap *remap, string return_expr) {
  CPPType *orig_type = remap->_return_type->get_orig_type();
  CPPType *type = remap->_return_type->get_new_type();

  if (remap->_return_type->new_type_is_atomic_string()) {
    if (TypeManager::is_char_pointer(orig_type)) {
      indent(out, indent_level)
        << "return PyString_FromString(" << return_expr << ");\n";

    } else {
      indent(out, indent_level)
        << "return PyString_FromStringAndSize("
        << return_expr << ".data(), " << return_expr << ".length());\n";
    }

  } else if (TypeManager::is_unsigned_longlong(type)) {
    indent(out, indent_level)
      << "return PyLong_FromUnsignedLongLong(" << return_expr << ");\n";

  } else if (TypeManager::is_longlong(type)) {
    indent(out, indent_level)
      << "return PyLong_FromLongLong(" << return_expr << ");\n";

  } else if (TypeManager::is_integer(type)) {
    indent(out, indent_level)
      << "return PyInt_FromLong(" << return_expr << ");\n";

  } else if (TypeManager::is_float(type)) {
    indent(out, indent_level)
      << "return PyFloat_FromDouble(" << return_expr << ");\n";

  } else if (TypeManager::is_char_pointer(type)) {
    indent(out, indent_level)
      << "return PyString_FromString(" << return_expr << ");\n";

  } else if (TypeManager::is_pointer(type)) {
    bool caller_manages = remap->_return_value_needs_management;

    FunctionWriterPtrToPython *writer = get_ptr_to_python(type);
    indent(out, indent_level)
      << "return " << writer->get_name() << "((" 
      << writer->get_pointer_type()->get_local_name(&parser) << ")"
      << return_expr << ", " << caller_manages << ");\n";

  } else {
    // Return None.
    indent(out, indent_level)
      << "return Py_BuildValue(\"\");\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::get_ptr_from_python
//       Access: Private
//  Description: Returns a FunctionWriter pointer suitable for
//               converting from a Python wrapper of the indicated
//               type to the corresponding C++ pointer.
////////////////////////////////////////////////////////////////////
FunctionWriterPtrFromPython *InterfaceMakerPythonObj::
get_ptr_from_python(CPPType *type) {
  PtrConverter::iterator ci;
  ci = _from_python.find(type);
  if (ci != _from_python.end()) {
    // We've previously used this type.
    return (FunctionWriterPtrFromPython *)(*ci).second;
  }

  FunctionWriter *writer = 
    _function_writers.add_writer(new FunctionWriterPtrFromPython(type));
  _from_python.insert(PtrConverter::value_type(type, writer));
  return (FunctionWriterPtrFromPython *)writer;
}

////////////////////////////////////////////////////////////////////
//     Function: InterfaceMakerPythonObj::get_ptr_to_python
//       Access: Private
//  Description: Returns a FunctionWriter pointer suitable for
//               converting from a C++ pointer of the indicated
//               type to the corresponding Python wrapper.
////////////////////////////////////////////////////////////////////
FunctionWriterPtrToPython *InterfaceMakerPythonObj::
get_ptr_to_python(CPPType *type) {
  PtrConverter::iterator ci;
  ci = _to_python.find(type);
  if (ci != _to_python.end()) {
    // We've previously used this type.
    return (FunctionWriterPtrToPython *)(*ci).second;
  }

  FunctionWriter *writer = 
    _function_writers.add_writer(new FunctionWriterPtrToPython(type));
  _to_python.insert(PtrConverter::value_type(type, writer));
  return (FunctionWriterPtrToPython *)writer;
}
