/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "action_bindings.h"
#include "core_utils.h"
#include "qt_bindings.h"
#include "docstrings.h"

#include <Action.h>
#include <Component.h>
#include <ActionWidget.h>
#include <HotPlugAction.h>
#include <PythonHotPlugAction.h>
#include <PythonManager.h>
#include <Property.h>

#include <algorithm>  // for std::find_if

namespace camitk {

// helper class for exposing Action protected functions
// see https://pybind11.readthedocs.io/en/stable/advanced/classes.html#binding-protected-member-functions
class ActionPublicist : public Action {
public:
    // inherited members with different access modifier
    using Action::refreshApplication;
};

} // namespace camitk

void add_action_bindings(py::module_& m) {

    // --------------- Action ---------------
    // Instantiate action using py::dynamic_attr() allocate a __dict__ slot for dynamic properties
    // This means Action in Python can add new attributes dynamically.
    // see https://pybind11.readthedocs.io/en/stable/classes.html#dynamic-attributes
    py::class_<camitk::Action> action(m, "Action", py::dynamic_attr(), DOC(camitk_Action));

    action.def("getName", &camitk::Action::getName,
               DOC(camitk_Action_getName));

    action.def("getTargets", &camitk::Action::getTargets,
               py::return_value_policy::reference,
               DOC(camitk_Action_getTargets));

    // reuse actions in pipelines
    action.def("setInputComponent", [](camitk::Action & self, camitk::Component & c) {
        self.setInputComponent(&c);
        self.getWidget(); // to activate targetDefined
    },
    R"doc(Assigns the given Component as input to this Action. Note that the Action's widget is also initialized upon this call.)doc");

    action.def("getOutputComponent", [](camitk::Action & self) -> camitk::Component* {
        auto it = std::find_if(self.getOutputComponents().begin(), self.getOutputComponents().end(), [](const camitk::Component * c) {
            return c->isTopLevel();
        });
        if (it != self.getOutputComponents().end()) {
            return (*it);
        }
        else {
            return nullptr;
        }
    },
    py::return_value_policy::reference,
    R"doc(Returns the top-level output Component of this Action, or None if there is no output.)doc");

    // Note: the explicit Property copy constructor must be called in order to
    // take ownership of the Property inside C++
    action.def("addParameter",
    [](camitk::Action & self, camitk::Property & p) {
        camitk::Property* prop = new camitk::Property(p);
        self.addParameter(prop);
    },
    DOC(camitk_Action_addParameter));

    action.def("getParameterValue", &camitk::Action::getParameterValue,
               DOC(camitk_Action_getParameterValue));

    action.def("setParameterValue", &camitk::Action::setParameterValue,
               DOC(camitk_Action_setParameterValue));

    action.def("refreshApplication", &camitk::ActionPublicist::refreshApplication,
               DOC(camitk_Action_refreshApplication));

    action.def("getProperty", &camitk::Action::getProperty, py::return_value_policy::reference,
               DOC(camitk_Action_getProperty));

    action.def("saveState",
    [](camitk::Action * self) {
        camitk::PythonManager::backupPythonState(self);
    },
    R"doc(Saves the current state of this Action, including its parameters. This will backup the current state of the __dict__ of the python object associated with the action)doc");

    // TODO Should this be added in the C++ Action class as well?
    action.def("updateWidget",
    [](camitk::Action & self) {
        camitk::ActionWidget* defaultActionWidget = dynamic_cast<camitk::ActionWidget*>(self.getWidget());
        if (defaultActionWidget != nullptr) {
            // this is a default action widget, make sure the widget has updated information
            defaultActionWidget->update();
        }
    },
    R"doc(Updates the Action's default widget, if any.)doc");

    // TODO Should the method name in the C++ Action be updated as well?
    action.def("setApplyButtonText", &camitk::Action::setDefaultWidgetApplyButtonText,
               DOC(camitk_Action_setDefaultWidgetApplyButtonText));

    // apply status enum
    // TODO change to enum_native when updating binding to pybind3
    // see https://pybind11.readthedocs.io/en/stable/classes.html#enumerations-and-internal-types
    py::enum_<camitk::Action::ApplyStatus> applyStatus(action, "ApplyStatus", DOC(camitk_Action_ApplyStatus));

    applyStatus.value("SUCCESS", camitk::Action::SUCCESS);
    applyStatus.value("ERROR", camitk::Action::ERROR);
    applyStatus.value("WARNING", camitk::Action::WARNING);
    applyStatus.value("ABORTED", camitk::Action::ABORTED);
    applyStatus.value("TRIGGERED", camitk::Action::TRIGGERED);
    applyStatus.export_values();

    // --------------- PythonHotPlugActionAction ---------------
    py::class_<camitk::HotPlugAction, camitk::Action>(m, "HotPlugAction",
            DOC(camitk_HotPlugAction));

    py::class_<camitk::PythonHotPlugAction, camitk::HotPlugAction>(m, "PythonHotPlugAction",
            R"doc(An Action that is implemented using a Python CamiTK script.)doc");

}

