#include <iomanip>
#include <ios>
#include <sstream>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <arbor/version.hpp>

namespace pyarb {

// Returns a python dictionary that python users can use to look up
// which options the Arbor library was configured with at compile time.

pybind11::dict config() {
    pybind11::dict dict;
#ifdef ARB_MPI_ENABLED
    dict[pybind11::str("mpi")]     = pybind11::bool_(true);
#else
    dict[pybind11::str("mpi")]     = pybind11::bool_(false);
#endif
#ifdef ARB_WITH_MPI4PY
    dict[pybind11::str("mpi4py")]  = pybind11::bool_(true);
#else
    dict[pybind11::str("mpi4py")]  = pybind11::bool_(false);
#endif
#ifdef ARB_NVCC_ENABLED
    dict[pybind11::str("gpu")]     = pybind11::str("cuda");
#endif
#ifdef ARB_CUDA_CLANG_ENABLED
    dict[pybind11::str("gpu")]     = pybind11::str("cuda-clang");
#endif
#ifdef ARB_HIP_ENABLED
    dict[pybind11::str("gpu")]     = pybind11::str("hip");
#endif
#ifndef ARB_GPU_ENABLED
    dict[pybind11::str("gpu")]     = pybind11::none();
#endif
#ifdef ARB_VECTORIZE_ENABLED
    dict[pybind11::str("vectorize")] = pybind11::bool_(true);
#else
    dict[pybind11::str("vectorize")] = pybind11::bool_(false);
#endif
#ifdef ARB_PROFILE_ENABLED
    dict[pybind11::str("profiling")] = pybind11::bool_(true);
#else
    dict[pybind11::str("profiling")] = pybind11::bool_(false);
#endif
    dict[pybind11::str("neuroml")] = pybind11::bool_(true);
#ifdef ARB_BUNDLED_ENABLED
    dict[pybind11::str("bundled")] = pybind11::bool_(true);
#else
    dict[pybind11::str("bundled")] = pybind11::bool_(false);
#endif

    dict[pybind11::str("version")]         = pybind11::str(ARB_VERSION);
    dict[pybind11::str("source")]          = pybind11::str(ARB_SOURCE_ID);
    dict[pybind11::str("build_config")]    = pybind11::str(ARB_BUILD_CONFIG);
    dict[pybind11::str("arch")]            = pybind11::str(ARB_ARCH);
    dict[pybind11::str("prefix")]          = pybind11::str(ARB_PREFIX);
    dict[pybind11::str("python_lib_path")] = pybind11::str(ARB_PYTHON_LIB_PATH);
    dict[pybind11::str("binary_path")]     = pybind11::str(ARB_BINARY);
    dict[pybind11::str("lib_path")]        = pybind11::str(ARB_LIB);
    dict[pybind11::str("data_path")]       = pybind11::str(ARB_DATA);
    dict[pybind11::str("CXX")]             = pybind11::str(ARB_CXX_COMPILER);
    {
#define mk_tok(x) #x
#define mk_ver(M, m, p) mk_tok(M) "." mk_tok(m) "." mk_tok(p)
        const char* version = mk_ver(PYBIND11_VERSION_MAJOR,
                                     PYBIND11_VERSION_MINOR,
                                     PYBIND11_VERSION_PATCH);
        dict[pybind11::str("pybind-version")] = pybind11::str(version);
#undef mk_ver
#undef mk_tok
    }
    dict[pybind11::str("timestamp")]       = pybind11::str(std::string(__DATE__) + " " + __TIME__);
    return dict;
}

void print_config(const pybind11::dict &d) {
    std::stringstream s;
    s << "Arbor's configuration:\n";

    for (auto x: d) {
        s << "     "
        << std::left << std::setw(15) << x.first << ": "
        << std::right << std::setw(20) << x.second << "\n";
    }

    pybind11::print(s.str());
}

// Register configuration
void register_config(pybind11::module &m) {

    m.def("config", &config, "Get Arbor's configuration.")
     .def("print_config", [](){print_config(config());}, "Print Arbor's configuration.");
}
} // namespace pyarb
