//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Tests/Suite/Py/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/PathUtil.h"
#include "Device/Data/Datafield.h"
#include "Device/Histo/DiffUtil.h"
#include "Device/Histo/SimulationResult.h"
#include "Device/IO/IOFactory.h"
#include "Sim/Export/ExportToPython.h"
#include "Sim/Simulation/ISimulation.h"
#include <filesystem>
#include <fstream>
#include <iostream>

namespace fs = std::filesystem;

namespace {

std::vector<double> domainData(const std::string& test_name, const ISimulation& direct_simulation)
{
    const auto dir = fs::path(BATesting::TestOutDir()) / fs::path("Suite") / fs::path("Py");
    Base::Path::createDirectories(dir.string());

    const std::string output_path = (dir / fs::path(test_name + ".ref.int.gz")).string();
    std::remove(output_path.c_str());
    std::cout << "- removed old output " << output_path << std::endl;

    // Generate Python script
    const std::string pyscript = (dir / fs::path(test_name + ".py")).string();
    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 result = std::unique_ptr<Datafield>(IO::readData2D(output_path));
    if (!result)
        throw std::runtime_error("Could not read back simulation output from file " + output_path);
    return result->flatVector();
}

} // namespace

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

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

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

    return DiffUtil::checkRelativeDifference(domain_data, ref_data, limit);
}
