//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Tests/Functional/PyStd/Check.cpp
//! @brief     Implements function checkSimulation for Python standard test
//!
//! @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 "BABuild.h"
#include "BATesting.h"
#include "Base/Util/FileSystemUtils.h"
#include "Core/Export/ExportToPython.h"
#include "Core/Simulation/ISimulation.h"
#include "Device/Data/DataUtils.h"
#include "Device/Histo/IntensityDataIOFactory.h"
#include "Device/Histo/SimulationResult.h"
#include <fstream>
#include <iostream>

namespace {

std::unique_ptr<OutputData<double>> domainData(const std::string& test_name,
                                               const ISimulation& direct_simulation)
{
    const std::string output_name =
        BaseUtils::Filesystem::jointPath(BATesting::TestOutDir_PyStd(), test_name);
    const std::string output_path = output_name + ".ref.int.gz";
    std::remove(output_path.c_str());
    std::cout << "- removed old output " << output_path << std::endl;

    // Generate Python script
    const std::string pyscript =
        BaseUtils::Filesystem::jointPath(BATesting::TestOutDir_PyStd(), test_name + ".py");
    std::ofstream f(pyscript);
    f << Py::Export::simulationSaveCode(direct_simulation, output_path);
    f.close();
    std::cout << "- wrote Python script " << pyscript << std::endl;

    // Run Python script
#ifndef _WIN32
    const std::string sys_command = std::string("PYTHONPATH=") + BABuild::buildLibDir() + " "
                                    + std::string("NOSHOW=TRUE") + " " + BABuild::pythonExecutable()
                                    + " -B " + pyscript;
#else
    const std::string sys_command = std::string("set PYTHONPATH=") + BABuild::buildLibDir() + " & "
                                    + std::string("set NOSHOW=TRUE") + " & \""
                                    + BABuild::pythonExecutable() + "\" -B " + pyscript;
#endif
    std::cout << "- system call: " << sys_command << std::endl;
    int err = std::system(sys_command.c_str());
    std::cout << "- system call returned " << err << " (0x" << std::hex << err << ")" << std::endl;
    if (err)
        throw std::runtime_error("Exported Python script did not execute properly");

    auto ret =
        std::unique_ptr<OutputData<double>>(IntensityDataIOFactory::readOutputData(output_path));
    if (!ret)
        throw std::runtime_error("Could not read back simulation output from file " + output_path);
    return ret;
}

} // namespace

//! Run simulation directly (in C+ core) and through Python export, and compare results.

bool checkSimulation(const std::string& name, const ISimulation& direct_simulation,
                     const double limit)
{
    std::cout << "PyStd test: checkSimulation(" << name << ")" << std::endl;

    const std::unique_ptr<OutputData<double>> domain_data = domainData(name, direct_simulation);
    std::cout << "- got domain data" << std::endl;
    const std::unique_ptr<OutputData<double>> ref_data = direct_simulation.result().data();
    std::cout << "- ran simulation" << std::endl;

    return DataUtils::Data::checkRelativeDifference(*domain_data, *ref_data, limit);
}