/*
  Copyright 2019 Equinor ASA.

  This file is part of the Open Porous Media project (OPM).

  OPM is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  OPM is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with OPM.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <memory>

#ifdef EMBEDDED_PYTHON
#include "../../Python/PyRunModule.hpp"
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
#endif


#include <opm/common/utility/String.hpp>
#include <opm/input/eclipse/Python/Python.hpp>
#include <opm/input/eclipse/Schedule/Action/PyAction.hpp>
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
#include <opm/input/eclipse/Schedule/Action/State.hpp>
#include <opm/input/eclipse/Schedule/Schedule.hpp>


namespace Opm {
namespace Action {

bool PyAction::valid_keyword(const std::string& keyword) {
    static std::unordered_set<std::string> pyaction_allowed_list = {
        "BOX",
        "COMPSEGS",
        "FIELD",
        "ENDBOX", "EXIT",
        //INCLUDE is allowed as well, but is handled differently by the Parser and thus does not need to be in this list
        "GCONINJE", "GCONPROD", "GCONSUMP", "GEFAC", "GRUPTREE",
        "METRIC", "MULTX", "MULTX-", "MULTY", "MULTY-", "MULTZ", "MULTZ-",
        "NEXT", "NEXTSTEP",
        "WCONINJE", "WCONPROD", "WECON", "WEFAC", "WELOPEN", "WELPI", "WELTARG", "WGRUPCON",
        "WELSEGS", "WELSPECS", "WSEGVALV", "WLIST", "WPIMULT", "WTEST", "WTMULT"
    };
    return pyaction_allowed_list.find(keyword) != pyaction_allowed_list.end();
}

PyAction::RunCount PyAction::from_string(std::string run_count) {
    run_count = uppercase(run_count);

    if (run_count == "SINGLE")
        return RunCount::single;

    if (run_count == "UNLIMITED")
        return RunCount::unlimited;

    if (run_count == "FIRST_TRUE")
        return RunCount::first_true;

    throw std::invalid_argument("RunCount string: " + run_count + " not recognized ");
}

PyAction PyAction::serializationTestObject()
{
    PyAction result;

    result.m_name = "name";
    result.m_run_count = RunCount::first_true;
    result.m_active = false;
    result.module_file = "no.such.file.py";

    return result;
}

bool PyAction::ready(const State& state) const {
    if (this->m_run_count == RunCount::unlimited)
        return true;

    auto last_result = state.python_result(this->m_name);
    if (!last_result.has_value())
        return true;

    if (this->m_run_count == RunCount::first_true && last_result.value() == false)
        return true;

    return false;
}


const std::string& PyAction::name() const {
    return this->m_name;
}


bool PyAction::operator==(const PyAction& other) const {
    return this->m_name == other.m_name &&
        this->m_run_count == other.m_run_count &&
        this->m_active == other.m_active &&
        this->module_file == other.module_file;
}


#ifndef EMBEDDED_PYTHON

bool PyAction::run(EclipseState&, Schedule&, std::size_t, SummaryState&,
                   const std::function<void(const std::string&, const std::vector<std::string>&)>&,
                   const std::unordered_map<std::string, double>&) const
{
    return false;
}

PyAction::PyAction(std::shared_ptr<const Python>, const std::string& name, RunCount run_count, const std::string& fname) :
    m_name(name),
    m_run_count(run_count),
    module_file(fname)
{}

#else

PyAction::PyAction(std::shared_ptr<const Python> python, const std::string& name, RunCount run_count, const std::string& fname) :
    run_module( std::make_shared<Opm::PyRunModule>(python, fname)),
    m_name(name),
    m_run_count(run_count),
    module_file(fname)
{
}


bool PyAction::run(EclipseState& ecl_state,
                   Schedule& schedule,
                   std::size_t report_step,
                   SummaryState& st,
                   const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback,
                   const std::unordered_map<std::string, double>& target_wellpi) const
{
    /*
      For PyAction instances which have been constructed the 'normal' way
      through the four argument constructor the run_module member variable has
      already been correctly initialized, however if this instance lives on a
      rank != 0 process and has been created through deserialization it was
      created without access to a Python handle and we must import the module
      now.
     */
    if (!this->run_module)
        this->run_module = std::make_shared<Opm::PyRunModule>(schedule.python(), this->module_file);

    return this->run_module->run(ecl_state, schedule, report_step, st, actionx_callback, target_wellpi);
}



#endif

}
}
