//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sim/Fitting/PyFittingCallbacks.cpp
//! @brief     Implements family of PyFittingCallbacks classes.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sim/Fitting/PyFittingCallbacks.h"
#include "Sim/Simulation/ISimulation.h"
#include <stdexcept>

SimulationWrapper::SimulationWrapper(const SimulationWrapper& other)
{
    cSimulationFn = other.cSimulationFn;
    pySimulationFn = other.pySimulationFn;
    pySimulate = other.pySimulate;
    pyFree = other.pyFree;
}

void SimulationWrapper::check() const
{
    if (!(cSimulationFn || pySimulationFn))
        throw std::runtime_error("SimulationWrapper: "
                                 "simulation-builder function is not available");

    if (cSimulationFn && pySimulationFn)
        throw std::runtime_error("SimulationWrapper: Either C++ or a Python simulation-builder "
                                 "function must be given, not both");

    if (pySimulationFn && !(pySimulate && pyFree))
        throw std::runtime_error(
            "SimulationWrapper: Python simulation-builder "
            "function needs extra functions to execute a Python simulation, "
            "convert a Python simulation, and free the memory on Python side.");
}

Datafield SimulationWrapper::simulate(const mumufit::Parameters& prm)
{
    check();

    // discard any artifacts from a previous simulation
    discard();

    if (pySimulationFn) {
        // Python function
        return m_executePySimulation(prm);
    }

    // C++ function
    return m_executeSimulation(prm);
}

void SimulationWrapper::discard()
{
    if (pySimulationFn) { // Python case
#ifdef BORNAGAIN_PYTHON
        // NOTE: The generated ISimulation instance and its Python-wrapper is managed
        // by the Python interpreter.
        simulation.release();
        pyFree(m_pythonObject);
#endif // BORNAGAIN_PYTHON
        m_pythonObject = nullptr;
    } else { // C++ case
        simulation.reset();
    }
}

SimulationWrapper::~SimulationWrapper()
{
    discard();
}

Datafield SimulationWrapper::m_executePySimulation(const mumufit::Parameters& params)
{
#ifndef BORNAGAIN_PYTHON
    throw std::runtime_error("SimulationWrapper: Calling a Python simulation-builder"
                             "function needs interoperation with a Python interpreter");
#endif // BORNAGAIN_PYTHON

    // call a Python function with signature:
    // fn(Parameters:Python-dict) -> Python-Object which wraps an ISimulation instance
    ISimulation* sim;
    pySimulate(pySimulationFn, params, sim, m_pythonObject);
    simulation.reset(sim);
    return simulation->simulate();
}

Datafield SimulationWrapper::m_executeSimulation(const mumufit::Parameters& params)
{
    // call a C++ function with signature:
    // fn(Parameters) -> ISimulation instance
    simulation.reset(cSimulationFn(params).release());
    return simulation->simulate();
}
