/*!
TODO: include README here.
*/

#![feature(maybe_uninit_array_assume_init)]
#![feature(iterator_try_collect)]
#![feature(once_cell_try)]
// We use unicode. We would like to use much more of it than Rust allows.
// Live with it. Embrace it.
#![allow(uncommon_codepoints)]
#![allow(mixed_script_confusables)]
#![allow(confusable_idents)]

use alg_tools::error::DynResult;
use log::debug;
use measures::python::pymod as pymod_measures;
use pointsource_algs::common_main;
use pyo3::append_to_inittab;
use pyo3::ffi::c_str;
use pyo3::prelude::*;
use std::ffi::CStr;

mod dolfinx_access;
mod experiments;
mod python_access;

use experiments::Experiments;
use python_access::pymod_pointsource_pde;

macro_rules! pymods {
    [$($modname:expr),*] => {&[$(
        (c_str!(concat!("pointsource_pde.", $modname)),
            c_str!(concat!("src/", $modname, ".py")),
                c_str!(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/", $modname, ".py")))),
    )*]};
}

const PY_MODULES: &[(&CStr, &CStr, &CStr)] = pymods![
    "dolfinx_extras",
    "compose",
    "measure",
    "quadratic_dataterm",
    "convection_diffusion",
    "laser_sampling",
    "full_sampling"
];

/// The entry point for the program.
pub fn main() -> DynResult<()> {
    // Initialise logging
    colog::init();

    debug!("Appending Rust-side Python modules to inittab");

    // Register the C++ module in Pythong. CURRENTLY NOT USED.
    // Can't use DynResult above and ? here to get traces from Python.
    dolfinx_access::register_python_ffi().unwrap();
    append_to_inittab!(pymod_measures);
    append_to_inittab!(pymod_pointsource_pde);

    debug!("Initialising python");

    // Initialise Python.
    Python::initialize();

    debug!("Loading Python modules from embedded source files");

    // Load modules
    Python::attach(|py| {
        // Verify that there's no attempt to use MPI.
        py.run(
            c_str!(
                r#"
import mpi4py

mpi4py.rc.initialize = False  # do not initialize MPI automatically
mpi4py.rc.thread_level = "multiple"
mpi4py.rc.threads = True

from mpi4py import MPI

MPI.Init()
size = MPI.COMM_SELF.Get_size()
if size != 1:
    raise RuntimeError("MPI_COMM_SELF size must be 1, but is %d" % size)
size = MPI.COMM_WORLD.Get_size()
if size != 1:
    raise RuntimeError("MPI_COMM_WORLD size must be 1, but is %d" % size)
"#
            ),
            None,
            None,
        )?;

        // FIXME: this is weird, besides registering in [`dolfinx_access::register_python_ffí`],
        // we need to import our module into Python, once we have the GIL or otherwise
        // nanobind::type<Function_f64>() will SIGSEGV.
        let _dolfinx_access = PyModule::import(py, "dolfinx_access")?;

        //let _pointsource_pde = python_access::register_module(py);

        for &(modname, filename, filecontents) in PY_MODULES {
            debug!("… Loading {}", modname.to_string_lossy());
            PyModule::from_code(py, filecontents, filename, modname)?;
        }

        Ok::<_, PyErr>(())
    })?;

    debug!("Entering pointsource_algs::common_main");

    common_main::<Experiments>()
}

/*
fn py_main<'py>(py: Python<'py>, access_points: &PythonAccessPoints) -> PyResult<()> {
    let main = PyModule::import(py, "__main__")?;

    //let fp = u_py.getattr("_V")?;
    let mut u: DolfinxPyFunction_f64<2, 2, 1> = main.getattr("u")?.extract()?;

    let res = u.minimise(0.0, 0);
    println!("min : {res:?}");

    let resx = u.maximise(0.0, 0);
    println!("max : {resx:?}");

    let res2 = u.apply(Loc([0.5, 0.5]));
    println!("apply : {res2:?}");

    let res3 = u.differential(Loc([0.5, 0.5]));
    println!("diff : {res3:?}");

    // let dolfinx_fem = PyModule::import(py, "dolfinx.fem")?;
    // let d_function = dolfinx_fem.getattr("Function")?;
    // let d_function_new = d_function.getattr("__new__")?;
    // let f = d_function_new.call1((d_function, fp))?;

    let mut u10 = &u * 10.0;
    let obj = u10.into_pyobject(py)?;

    let locals = PyDict::new(py);
    locals.set_item("obj", obj)?;
    py.run(
        c_str!(
            "\
import numpy as np
print(\"10 * min array \", np.min(obj.x.array))
print(\"10 * max array \", np.max(obj.x.array))"
        ),
        None,
        Some(&locals),
    )?;

    Ok(())
}
*/

// fn get_dolfinx_function_type_object(py: Python<'_>) -> PyResult<*mut pyo3::ffi::PyTypeObject> {
//     // Import the module and get the class
//     let fem = py.import("dolfinx.fem.function")?;
//     let func_class: Bound<'_, pyo3::types::PyType> = fem.getattr("Function")?.downcast_into()?;
//     // Convert to raw pointer
//     Ok(func_class.as_type_ptr() as *mut pyo3::ffi::PyTypeObject)
// }
