/***************************************************************************
 *   Copyright (c) 2019 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#ifndef GUI_COMMAND_T_H
#define GUI_COMMAND_T_H

#include <Base/Exception.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <Gui/Command.h>
#include <exception>
#include <type_traits>
#include <boost/format.hpp>

namespace Gui {

class FormatString
{
public:
    static std::string str(const std::string& s) {
        return s;
    }
    static std::string str(const std::stringstream& s) {
        return s.str();
    }
    static std::string str(const std::ostream& s) {
        return dynamic_cast<const std::ostringstream&>(s).str();
    }
    static std::string toStr(boost::format& f) {
        return f.str();
    }

    template<typename T, typename... Args>
    static std::string toStr(boost::format& f, T&& t, Args&&... args) {
        return toStr(f % std::forward<T>(t), std::forward<Args>(args)...);
    }
};

/** @defgroup CommandFuncs Helper functions for running commands through Python interpreter */
//@{

/** Runs a command for accessing document attribute or method
 * This function is an alternative to _FCMD_DOC_CMD
 * @param doc: pointer to a document
 * @param mod: module name, "Gui" or "App"
 * @param cmd: command string, streamable
 *
 * Example:
 * @code{.cpp}
 *      _cmdDocument(Gui::Command::Gui, doc, "Gui", std::stringstream() << "getObject('" << objName << "')");
 * @endcode
 *
 * Translates to command (assuming doc's name is 'DocName', and
 * and objName contains value 'ObjName'):
 * @code{.py}
 *       Gui.getDocument('DocName').getObject('ObjName')
 * @endcode
 */
template<typename T>
void _cmdDocument(Gui::Command::DoCmd_Type cmdType, const App::Document* doc, const std::string& mod, T&& cmd) {
    if (doc && doc->getName()) {
        std::stringstream str;
        str << mod << ".getDocument('" << doc->getName() << "')."
            << FormatString::str(cmd);
        Gui::Command::runCommand(cmdType, str.str().c_str());
    }
}

/** Runs a command for accessing App.Document attribute or method
 * This function is an alternative to FCMD_DOC_CMD
 *
 * @param doc: pointer to a document
 * @param cmd: command string, streamable
 * @sa _cmdDocument()
 *
 * Example:
 * @code{.cpp}
 *      cmdAppDocument(doc, std::stringstream() << "getObject('" << objName << "')");
 * @endcode
 *
 * Translates to command (assuming doc's name is 'DocName', and
 * and objName contains value 'ObjName'):
 * @code{.py}
 *       App.getDocument('DocName').getObject('ObjName')
 * @endcode
 */
template<typename T>
inline void cmdAppDocument(const App::Document* doc, T&& cmd) {
    _cmdDocument(Gui::Command::Doc, doc, "App", std::forward<T>(cmd));
}

/** Runs a command for accessing App.Document attribute or method
 *
 * @param doc: pointer to a document
 * @param cmd: command string, streamable
 * @sa _cmdDocument()
 *
 * Example:
 * @code{.cpp}
 *      cmdGuiDocument(doc, std::stringstream() << "getObject('" << objName << "')");
 * @endcode
 *
 * Translates to command (assuming doc's name is 'DocName', and
 * and objName contains value 'ObjName'):
 * @code{.py}
 *       Gui.getDocument('DocName').getObject('ObjName')
 * @endcode
 */
template<typename T>
inline void cmdGuiDocument(const App::Document* doc, T&& cmd) {
    _cmdDocument(Gui::Command::Gui, doc, "Gui", std::forward<T>(cmd));
}

/** Runs a command for accessing an object's document attribute or method
 * This function is an alternative to _FCMD_OBJ_DOC_CMD
 * @param obj: pointer to a DocumentObject
 * @param mod: module name, "Gui" or "App"
 * @param cmd: command string, streamable
 */
template<typename T>
inline void _cmdDocument(Gui::Command::DoCmd_Type cmdType, const App::DocumentObject* obj, const std::string& mod, T&& cmd) {
    if (obj) _cmdDocument(cmdType, obj->getDocument(), mod, std::forward<T>(cmd));
}

/** Runs a command for accessing an object's App::Document attribute or method
 * This function is an alternative to FCMD_OBJ_DOC_CMD
 * @param obj: pointer to a DocumentObject
 * @param cmd: command string, streamable
 */
template<typename T>
inline void cmdAppDocument(const App::DocumentObject* obj, T&& cmd) {
    _cmdDocument(Gui::Command::Doc, obj, "App", std::forward<T>(cmd));
}

/** Runs a command for accessing an object's Gui::Document attribute or method
 * This function is an alternative to FCMD_VOBJ_DOC_CMD
 * @param obj: pointer to a DocumentObject
 * @param cmd: command string, streamable
 */
template<typename T>
inline void cmdGuiDocument(const App::DocumentObject* obj, T&& cmd) {
    _cmdDocument(Gui::Command::Gui, obj, "Gui", std::forward<T>(cmd));
}

/** Runs a command for accessing a document/view object's attribute or method
 * This function is an alternative to _FCMD_OBJ_CMD
 * @param cmdType: Command type
 * @param obj: pointer to a DocumentObject
 * @param mod: module name, "Gui" or "App"
 * @param cmd: command string, streamable
 *
 * Example:
 * @code{.cpp}
 *      _cmdObject(Command::Gui,obj,"Gui", "Visibility = " << (visible?"True":"False"));
 * @endcode
 *
 * Translates to command (assuming obj's document name is 'DocName', obj's name
 * is 'ObjName', and visible is true):
 * @code{.py}
 *       Gui.getDocument('DocName').getObject('ObjName').Visibility = True
 * @endcode
 */
template<typename T>
void _cmdObject(Gui::Command::DoCmd_Type cmdType, const App::DocumentObject* obj, const std::string& mod, T&& cmd) {
    if (obj && obj->getNameInDocument()) {
        std::ostringstream str;
        str << mod << ".getDocument('" << obj->getDocument()->getName() << "')"
                      ".getObject('" << obj->getNameInDocument() << "')."
                   << FormatString::str(cmd);
        Gui::Command::runCommand(cmdType, str.str().c_str());
    }
}

/** Runs a command for accessing an document object's attribute or method
 * This function is an alternative to FCMD_OBJ_CMD
 * @param obj: pointer to a DocumentObject
 * @param cmd: command string, streamable
 * @sa _cmdObject()
 */
template<typename T>
inline void cmdAppObject(const App::DocumentObject* obj, T&& cmd) {
    _cmdObject(Gui::Command::Doc, obj, "App", std::forward<T>(cmd));
}

/** Runs a command for accessing an view object's attribute or method
 * This function is an alternative to FCMD_VOBJ_CMD
 * @param obj: pointer to a DocumentObject
 * @param cmd: command string, streamable
 * @sa _cmdObject()
 */
template<typename T>
inline void cmdGuiObject(const App::DocumentObject* obj, T&& cmd) {
    _cmdObject(Gui::Command::Gui, obj, "Gui", std::forward<T>(cmd));
}

/// Hides an object
inline void cmdAppObjectHide(const App::DocumentObject* obj) {
    cmdAppObject(obj, "Visibility = False");
}

/// Shows an object
inline void cmdAppObjectShow(const App::DocumentObject* obj) {
    cmdAppObject(obj, "Visibility = True");
}

/** Runs a command to start editing a give object
 * This function is an alternative to FCMD_SET_EDIT
 * @param obj: pointer to a DocumentObject
 *
 * Unlike other helper functions, this one editing the object using the current
 * active document, instead of the object's owner document. This allows
 * in-place editing an object, which may be brought in through linking to an
 * external group.
 */
inline void cmdSetEdit(const App::DocumentObject* obj) {
    if (obj && obj->getNameInDocument()) {
        Gui::Command::doCommand(Gui::Command::Gui,
            "Gui.ActiveDocument.setEdit(App.getDocument('%s').getObject('%s'))",
            obj->getDocument()->getName(), obj->getNameInDocument());
    }
}

/** Runs a command for accessing a document object's attribute or method
 * This function is an alternative to FCMD_OBJ_CMD2
 * @param cmd: command string, supporting printf like formatter
 * @param obj: pointer to a DocumentObject
 *
 * Example:
 * @code{.cpp}
 *      cmdAppObjectArgs(obj, "Visibility = %s", visible ? "True" : "False");
 * @endcode
 *
 * Translates to command (assuming obj's document name is 'DocName', obj's name
 * is 'ObjName', and visible is true):
 * @code{.py}
 *       App.getDocument('DocName').getObject('ObjName').Visibility = True
 * @endcode
 */
template<typename...Args>
void cmdAppObjectArgs(const App::DocumentObject* obj, const std::string& cmd, Args&&... args) {
    std::string _cmd;
    try {
        boost::format fmt(cmd);
        _cmd = FormatString::toStr(fmt, std::forward<Args>(args)...);
        Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').getObject('%s').%s",
            obj->getDocument()->getName(), obj->getNameInDocument(), _cmd.c_str());
    }
    catch (const std::exception& e) {
        Base::Console().Error("%s: %s\n", e.what(), cmd.c_str());
    }
    catch (const Base::Exception&) {
        Base::Console().Error("App.getDocument('%s').getObject('%s').%s\n",
            obj->getDocument()->getName(), obj->getNameInDocument(), _cmd.c_str());
        throw;
    }
}

/** Runs a command for accessing a view object's attribute or method
 * This function is an alternative to FCMD_VOBJ_CMD2
 * @param cmd: command string, supporting printf like formatter
 * @param obj: pointer to a DocumentObject
 * @sa cmdAppObjectArgs()
 */
template<typename...Args>
void cmdGuiObjectArgs(const App::DocumentObject* obj, const std::string& cmd, Args&&... args) {
    std::string _cmd;
    try {
        boost::format fmt(cmd);
        _cmd = FormatString::toStr(fmt, std::forward<Args>(args)...);
        Gui::Command::doCommand(Gui::Command::Gui,"Gui.getDocument('%s').getObject('%s').%s",
            obj->getDocument()->getName(), obj->getNameInDocument(), _cmd.c_str());
    }
    catch (const std::exception& e) {
        Base::Console().Error("%s: %s\n", e.what(), cmd.c_str());
    }
    catch (const Base::Exception&) {
        Base::Console().Error("Gui.getDocument('%s').getObject('%s').%s\n",
            obj->getDocument()->getName(), obj->getNameInDocument(), _cmd.c_str());
        throw;
    }
}

/** Runs a command
 * @param cmdType: command type
 * @param cmd: command string, supporting printf like formatter
 *
 * Example:
 * @code{.cpp}
 *      doCommandT(Gui::Command::Gui, "Gui.getDocument(%s).getObject(%s).Visibility = %s", "DocName", "ObjName", visible?"True":"False");
 * @endcode
 *
 * Translates to command (assuming obj's document name is 'DocName', obj's name
 * is 'ObjName', and visible is true):
 * @code{.py}
 *       Gui.getDocument('DocName').getObject('ObjName').Visibility = True
 * @endcode
 */
template<typename...Args>
void doCommandT(Gui::Command::DoCmd_Type cmdType, const std::string& cmd, Args&&... args) {
    std::string _cmd;
    try {
        boost::format fmt(cmd);
        _cmd = FormatString::toStr(fmt, std::forward<Args>(args)...);
        Gui::Command::doCommand(cmdType,"%s", _cmd.c_str());
    }
    catch (const std::exception& e) {
        Base::Console().Error("%s: %s\n", e.what(), cmd.c_str());
    }
    catch (const Base::Exception&) {
        Base::Console().Error("%s\n", _cmd.c_str());
        throw;
    }
}

/** Copy visual attributes from a source to a target object
 */
template<typename...Args>
void copyVisualT(Args&&... args) {
    // file and line number is useless here. Check C++'s source location function once available
    Gui::Command::_copyVisual(__FILE__, __LINE__, std::forward<Args>(args)...);
}

//@}

};

#endif // GUI_COMMAND_T_H

