// AsapModule.cpp:  The main Asap module.
//
// Copyright (C) 2008 Jakob Schiotz and Center for Individual
// Nanoparticle Functionality, Department of Physics, Technical
// University of Denmark.  Email: schiotz@fysik.dtu.dk
//
// This file is part of Asap version 3.
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// version 3 as published by the Free Software Foundation.  Permission
// to use other versions of the GNU Lesser General Public License may
// granted by Jakob Schiotz or the head of department of the
// Department of Physics, Technical University of Denmark, as
// described in section 14 of the GNU General Public License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// and the GNU Lesser Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.

#include <Python.h>
#define PY_ARRAY_UNIQUE_SYMBOL Asap_Array_API
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

#include "Asap.h"
#include "ExceptionInterface.h"
#include "PotentialInterface.h"
#include "EMTParameterProviderInterface.h"
#include "NeighborLocatorInterface.h"
#include "RDFInterface.h"
#include "ToolsInterface.h"
#include "PTMInterface.h"
#include "DynamicsInterface.h"
#include "OpenMPInterface.h"
#ifdef WITH_OPENKIM
#include "OpenKIMinterface.h"
#endif
#define ALLOCATE_TIMING
#include "Timing.h"
#include "TimingResults.h"
#include "stacktrace.h"
//#define ASAPDEBUG
#include "Debug.h"
#include "AsapModule.h"
#ifdef ASAP_MKL
#include <mkl.h>
#endif // ASAP_MKL

PyDoc_STRVAR(asapmodule__doc__, "C++ extension for Asap.\n");

namespace ASAPSPACE {

#ifdef _OPENMP
AsapErrorBase *AsapGlobalException;
#endif

// Functions declared in the autogenerated version.cpp
string GetVersion();
string GetShortVersion();

static PyObject *PyAsap_GetVersion(PyObject *noself, PyObject *noargs)
{
  string s = GetVersion();
  return PyUnicode_FromString(s.c_str());
}

static PyObject *PyAsap_GetShortVersion(PyObject *noself, PyObject *noargs)
{
  string s = GetShortVersion();
  return PyUnicode_FromString(s.c_str());
}

static PyObject *PyAsap_HeapMallinfo(PyObject *noself, PyObject *noargs)
{
  double heap = heap_mallinfo();
  return PyFloat_FromDouble(heap);
}

// Wrapper function PyAsap_NewNeighborCellLocator_Py.  As it needs to wrap
// different functions depending on whether parallel simulations are supported
// or not, it needs to be defined here.

PyObject *PyAsap_NewNeighborCellLocator_Py(PyObject *noself, PyObject *args,
                                           PyObject *kwargs)
{
#ifdef PARALLEL
  return PyAsap_NewNeighborCellLocator_Parallel(noself, args, kwargs);
#else
  return PyAsap_NewNeighborCellLocator_Serial(noself, args, kwargs);
#endif
}


static PyMethodDef AsapContents[] =
  {
    {"get_version", PyAsap_GetVersion, METH_NOARGS,
     "Get the ASAP version (descriptive string)."},
    {"get_short_version", PyAsap_GetShortVersion, METH_NOARGS,
     "Get the ASAP version (a string containing just the version number)."},
    {"EMTDefaultParameters", PyAsap_EMTDefaultParamProvNew, METH_NOARGS,
     EMTDefaultParamProv_Docstring},
    {"EMTRasmussenParameters", PyAsap_EMTRasmussenParamProvNew,
     METH_NOARGS, EMTRasmussenParamProv_Docstring},
    {"NeighborList", (PyCFunction)PyAsap_NewNeighborList_Py,
     METH_VARARGS|METH_KEYWORDS, PyAsap_NewNeighborList_Docstring},
    {"NeighborCellLocator", (PyCFunction)PyAsap_NewNeighborCellLocator_Py,
     METH_VARARGS|METH_KEYWORDS, PyAsap_NewNeighborCellLocator_Docstring},
    {"FullNeighborList", (PyCFunction)PyAsap_NewFullNeighborList,
     METH_VARARGS|METH_KEYWORDS, PyAsap_NewFullNeighborList_Docstring},
    {"RawRDF", PyAsap_RawRDF, METH_VARARGS,
     "Calculate unnormalized Radial Distribution Function."},
    {"CoordinationNumbers", PyAsap_CoordinationNumbers, METH_VARARGS,
     "Calculate the coordination number of the atoms."},
    {"RestrictedCNA", PyAsap_RestrictedCNA, METH_VARARGS,
     "Restriced CNA analysis: finds FCC, HCP and 'other'"},
    {"PTM_allatoms", (PyCFunction)PyAsap_PTMall, METH_VARARGS | METH_KEYWORDS,
     "Determine the structure of the atoms using Polyhedral Template Matching"},
    {"timing_results", PyAsap_TimingResults, METH_NOARGS,
     "Get timing results, if enabled."},
    {"support_openmp", PyAsap_support_openmp, METH_NOARGS,
     PyAsap_support_openmp_doc},
    {"get_num_procs", PyAsap_get_num_procs, METH_NOARGS,
     PyAsap_get_num_procs_doc},
    {"set_num_threads", (PyCFunction)PyAsap_set_num_threads, METH_VARARGS|METH_KEYWORDS,
     PyAsap_set_num_threads_doc},
    {"heap_mallinfo", (PyCFunction)PyAsap_HeapMallinfo, METH_NOARGS,
     "Return heap size in kB."},
#ifdef PARALLEL
    {"DistributeAtoms", PyAsap_DistributeAtoms, METH_O,
     "Distribute atoms in a parallel simulation."},
#endif
    {NULL, NULL, 0, NULL}
  };


#ifdef STACKTRACE
#ifdef PARALLEL
// Global abort function
void asap_mpi_abort(void) {
    MPI_Abort(MPI_COMM_WORLD, 3);
}

// Global function giving rank in world communicator.  Used when aborting.
void asap_mpi_world_rank(int *rank)
{
    MPI_Comm_rank(MPI_COMM_WORLD, rank);
}
#endif // PARALLEL
#endif // STACKTRACE

} // end namespace

// Module definition for Python 3
#ifdef ASAP_PY3
static int asap_traverse(PyObject *m, visitproc visit, void *arg)
{
  //struct asap_module_state *state = GETMODULESTATE(m);  
  //Py_VISIT(xxxx);
  return 0;
}

static int asap_clear(PyObject *m)
{
  //struct asap_module_state *state = GETMODULESTATE(m);  
  //Py_CLEAR(state->Asap_String_Stress);
  return 0;
}

static struct PyModuleDef asapmoduledef = {
  PyModuleDef_HEAD_INIT,
#ifdef PARALLEL
  "_asap",
#else
  "_asap_p3",
#endif
  asapmodule__doc__,
#ifdef _OPENMP
  -1,      // Module uses global data - only one interpreter is possible.
#else // _OPENMP
  0,       // Module does not have any module-global data i C++.
#endif // _OPENMP
  AsapContents,
  NULL,
  asap_traverse,
  asap_clear,
  NULL
};

#endif // ASAP_PY3

PyObject *AsapInitModule(void)
{
  DEBUGPRINT;

  // Initialize the NumPy package.  Return NULL on failure.
  import_array1(NULL);
  DEBUGPRINT;

#ifdef ASAP_PY3
  PyObject *m = PyModule_Create(&asapmoduledef);
#else // ASAP_PY3
  PyObject *m = Py_InitModule3(
#ifdef PARALLEL
			       "_asap",
#else // PARALLEL
			       "_asap_p2",
#endif // PARALLEL
			       AsapContents,
			       asapmodule__doc__);
#endif // ASAP_PY3
  if (m == NULL)
    return NULL;

  DEBUGPRINT;
#ifdef _OPENMP
  AsapGlobalException = NULL;
#endif
  
#ifdef TIMING
  Timing_init();
#endif
  if (PyAsap_InitExceptionInterface(m) < 0)
    return NULL;
  if (PyAsap_InitPotentialInterface(m) < 0)
    return NULL;
  if (PyAsap_InitEMTParameterProviderInterface(m) < 0)
    return NULL;
  if (PyAsap_InitNeighborLocatorInterface(m) < 0)
    return NULL;
  if (PyAsap_InitDynamicsInterface(m) < 0)
    return NULL;
  if (PyAsap_InitToolsInterface(m) < 0)
    return NULL;
#ifdef WITH_OPENKIM
  if (PyAsap_InitOpenKIMInterface(m) < 0)
    return NULL;
#endif
#ifdef PARALLEL
  if (PyAsap_InitMpiInterface(m) < 0)
    return NULL;
  if (PyAsap_InitParallelPotentialInterface(m) < 0)
    return NULL;
#endif

  DEBUGPRINT;
  PyAsap_InitPTMmodule();

#ifdef STACKTRACE
#ifdef PARALLEL
  int rank;
  asap_mpi_world_rank(&rank);
  Asap_setSignalHandlers(rank, asap_mpi_abort);
#else  //PARALLEL
  Asap_setSignalHandlers(-1, 0);
#endif //PARALLEL
#endif //STACKTRACE

  // Create the verbose variable
  PyModule_AddIntConstant(m, "verbose", 0);

#ifdef ASAP_MKL
  assert(BUFLEN % (ASAP_MKL_ALIGN/sizeof(double)) == 0);  // Otherwise we break alignment
  // Set the precision of MKL to low accuracy
  // meaning that the last two bits of the mantissa may be
  // incorrect, and that denormalized numbers are zero.
  // The first cannot possibly influence a simulations,
  // and denormalized numbers should never occur.
  vmlSetMode(VML_LA | VML_FTZDAZ_ON | VML_ERRMODE_DEFAULT);
#endif //ASAP_MKL
  
  DEBUGPRINT;
  return m;
}

