/***************************************************************************
 *   Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name>             *
 *                                                                         *
 *   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                                *
 *                                                                         *
 ***************************************************************************/


#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <Base/Reader.h>
#include <Base/Tools.h>
#include <Base/Writer.h>
#include <CXX/Objects.hxx>

#include "PropertyExpressionEngine.h"
#include "ExpressionVisitors.h"


FC_LOG_LEVEL_INIT("App", true);

using namespace App;
using namespace Base;
using namespace boost;
namespace sp = std::placeholders;

TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyExpressionContainer, App::PropertyXLinkContainer)

static std::set<PropertyExpressionContainer*> _ExprContainers;

PropertyExpressionContainer::PropertyExpressionContainer()
{
    static bool inited;
    if (!inited) {
        inited = true;
        GetApplication().signalRelabelDocument.connect(
            PropertyExpressionContainer::slotRelabelDocument);
        GetApplication().signalRenameDynamicProperty.connect(
            PropertyExpressionContainer::slotRenameDynamicProperty);
    }
    _ExprContainers.insert(this);
}

PropertyExpressionContainer::~PropertyExpressionContainer()
{
    _ExprContainers.erase(this);
}

void PropertyExpressionContainer::slotRelabelDocument(const App::Document& doc)
{
    // For use a private _ExprContainers to track all living
    // PropertyExpressionContainer including those inside undo/redo stack,
    // because document relabel is not undoable/redoable.

    if (doc.getOldLabel() != doc.Label.getValue()) {
        for (auto prop : _ExprContainers) {
            prop->onRelabeledDocument(doc);
        }
    }
}

void PropertyExpressionContainer::slotRenameDynamicProperty(const App::Property& prop, const char* oldName)
{
    for (auto container : _ExprContainers) {
        container->onRenameDynamicProperty(prop, oldName);
    }
}

///////////////////////////////////////////////////////////////////////////////////////

struct PropertyExpressionEngine::Private
{
    // For some reason, MSVC has trouble with vector of scoped_connection if
    // defined in header, hence the private structure here.
    std::vector<boost::signals2::scoped_connection> conns;
    std::unordered_map<std::string, std::vector<ObjectIdentifier>> propMap;
};

///////////////////////////////////////////////////////////////////////////////////////

TYPESYSTEM_SOURCE(App::PropertyExpressionEngine, App::PropertyExpressionContainer)

/**
 * @brief Construct a new PropertyExpressionEngine object.
 */

PropertyExpressionEngine::PropertyExpressionEngine()
    : validator(0)
{}

/**
 * @brief Destroy the PropertyExpressionEngine object.
 */

PropertyExpressionEngine::~PropertyExpressionEngine() = default;

/**
 * @brief Estimate memory size of this property.
 *
 * \fixme Should probably return something else than 0.
 *
 * @return Size of object.
 */

unsigned int PropertyExpressionEngine::getMemSize() const
{
    return 0;
}

Property* PropertyExpressionEngine::Copy() const
{
    PropertyExpressionEngine* engine = new PropertyExpressionEngine();

    for (const auto& it : expressions) {
        ExpressionInfo info;
        if (it.second.expression) {
            info.expression = std::shared_ptr<Expression>(it.second.expression->copy());
        }
        engine->expressions[it.first] = info;
    }

    engine->validator = validator;

    return engine;
}

void PropertyExpressionEngine::hasSetValue()
{
    App::DocumentObject* owner = dynamic_cast<App::DocumentObject*>(getContainer());
    if (!owner || !owner->isAttachedToDocument() || owner->isRestoring()
        || testFlag(LinkDetached)) {
        PropertyExpressionContainer::hasSetValue();
        return;
    }

    std::map<App::DocumentObject*, bool> deps;
    std::vector<std::string> labels;
    unregisterElementReference();
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this);
    for (auto& e : expressions) {
        auto expr = e.second.expression;
        if (expr) {
            expr->getDepObjects(deps, &labels);
            if (!restoring) {
                expr->visit(v);
            }
        }
    }
    registerLabelReferences(std::move(labels));

    updateDeps(std::move(deps));

    if (pimpl) {
        pimpl->conns.clear();
        pimpl->propMap.clear();
    }
    // check if there is any hidden references
    bool hasHidden = false;
    for (auto& v : _Deps) {
        if (v.second) {
            hasHidden = true;
            break;
        }
    }
    if (hasHidden) {
        if (!pimpl) {
            pimpl = std::make_unique<Private>();
        }
        for (auto& e : expressions) {
            auto expr = e.second.expression;
            if (!expr) {
                continue;
            }
            for (auto& dep : expr->getIdentifiers()) {
                if (!dep.second) {
                    continue;
                }
                const ObjectIdentifier& var = dep.first;
                for (auto& vdep : var.getDep(true)) {
                    auto obj = vdep.first;
                    auto objName = obj->getFullName() + ".";
                    for (auto& propName : vdep.second) {
                        std::string key = objName + propName;
                        auto& propDeps = pimpl->propMap[key];
                        if (propDeps.empty()) {
                            // NOLINTBEGIN
                            if (!propName.empty()) {
                                pimpl->conns.emplace_back(obj->signalChanged.connect(
                                    std::bind(&PropertyExpressionEngine::slotChangedProperty,
                                              this,
                                              sp::_1,
                                              sp::_2)));
                            }
                            else {
                                pimpl->conns.emplace_back(obj->signalChanged.connect(
                                    std::bind(&PropertyExpressionEngine::slotChangedObject,
                                              this,
                                              sp::_1,
                                              sp::_2)));
                            }
                            // NOLINTEND
                        }
                        propDeps.push_back(e.first);
                    }
                }
            }
        }
    }

    PropertyExpressionContainer::hasSetValue();
}

void PropertyExpressionEngine::updateHiddenReference(const std::string& key)
{
    if (!pimpl) {
        return;
    }
    auto it = pimpl->propMap.find(key);
    if (it == pimpl->propMap.end()) {
        return;
    }
    for (auto& var : it->second) {
        auto it = expressions.find(var);
        if (it == expressions.end() || it->second.busy) {
            continue;
        }
        Property* myProp = var.getProperty();
        if (!myProp) {
            continue;
        }
        Base::StateLocker guard(it->second.busy);
        App::any value;
        try {
            value = it->second.expression->getValueAsAny();
            if (!isAnyEqual(value, myProp->getPathValue(var))) {
                myProp->setPathValue(var, value);
            }
        }
        catch (Base::Exception& e) {
            e.reportException();
            FC_ERR("Failed to evaluate property binding " << myProp->getFullName()
                                                          << " on change of " << key);
        }
        catch (std::bad_cast&) {
            FC_ERR("Invalid type '" << value.type().name() << "' in property binding "
                                    << myProp->getFullName() << " on change of " << key);
        }
        catch (std::exception& e) {
            FC_ERR(e.what());
            FC_ERR("Failed to evaluate property binding " << myProp->getFullName()
                                                          << " on change of " << key);
        }
    }
}

void PropertyExpressionEngine::slotChangedObject(const App::DocumentObject& obj,
                                                 const App::Property&)
{
    updateHiddenReference(obj.getFullName());
}

void PropertyExpressionEngine::slotChangedProperty(const App::DocumentObject&,
                                                   const App::Property& prop)
{
    updateHiddenReference(prop.getFullName());
}

void PropertyExpressionEngine::Paste(const Property& from)
{
    const PropertyExpressionEngine& fromee = dynamic_cast<const PropertyExpressionEngine&>(from);

    AtomicPropertyChange signaller(*this);

    expressions.clear();
    for (auto& e : fromee.expressions) {
        ExpressionInfo info;
        if (e.second.expression) {
            info.expression = std::shared_ptr<Expression>(e.second.expression->copy());
        }
        expressions[e.first] = info;
        expressionChanged(e.first);
    }
    validator = fromee.validator;
    signaller.tryInvoke();
}

void PropertyExpressionEngine::Save(Base::Writer& writer) const
{
    writer.Stream() << writer.ind() << "<ExpressionEngine count=\"" << expressions.size();
    if (PropertyExpressionContainer::_XLinks.empty()) {
        writer.Stream() << "\">" << std::endl;
        writer.incInd();
    }
    else {
        writer.Stream() << R"(" xlink="1">)" << std::endl;
        writer.incInd();
        PropertyExpressionContainer::Save(writer);
    }
    for (const auto& it : expressions) {
        std::string expression, comment;
        if (it.second.expression) {
            expression = it.second.expression->toString(true);
            comment = it.second.expression->comment;
        }

        writer.Stream() << writer.ind() << "<Expression path=\""
                        << Property::encodeAttribute(it.first.toString()) << "\" expression=\""
                        << Property::encodeAttribute(expression) << "\"";
        if (!comment.empty()) {
            writer.Stream() << " comment=\"" << Property::encodeAttribute(comment) << "\"";
        }
        writer.Stream() << "/>" << std::endl;
    }
    writer.decInd();
    writer.Stream() << writer.ind() << "</ExpressionEngine>" << std::endl;
}

void PropertyExpressionEngine::Restore(Base::XMLReader& reader)
{
    reader.readElement("ExpressionEngine");
    int count = reader.getAttribute<double>("count");

    if (reader.hasAttribute("xlink") && reader.getAttribute<bool>("xlink")) {
        PropertyExpressionContainer::Restore(reader);
    }

    restoredExpressions = std::make_unique<std::vector<RestoredExpression>>();
    restoredExpressions->reserve(count);
    for (int i = 0; i < count; ++i) {

        reader.readElement("Expression");
        restoredExpressions->emplace_back();
        auto& info = restoredExpressions->back();
        info.path = reader.getAttribute<const char*>("path");
        info.expr = reader.getAttribute<const char*>("expression");
        if (reader.hasAttribute("comment")) {
            info.comment = reader.getAttribute<const char*>("comment");
        }
    }

    reader.readEndElement("ExpressionEngine");
}

/**
 * @brief Update graph structure with given path and expression.
 * @param path Path
 * @param expression Expression to query for dependencies
 * @param nodes Map with nodes of graph, including dependencies of 'expression'
 * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies.
 * @param edges Edges in graph
 */

void PropertyExpressionEngine::buildGraphStructures(
    const ObjectIdentifier& path,
    const std::shared_ptr<Expression> expression,
    boost::unordered_map<ObjectIdentifier, int>& nodes,
    boost::unordered_map<int, ObjectIdentifier>& revNodes,
    std::vector<Edge>& edges) const
{
    /* Insert target property into nodes structure */
    if (nodes.find(path) == nodes.end()) {
        int s = nodes.size();

        revNodes[s] = path;
        nodes[path] = s;
    }
    else {
        revNodes[nodes[path]] = path;
    }

    /* Insert dependencies into nodes structure */
    ExpressionDeps deps;
    if (expression) {
        deps = expression->getDeps();
    }

    for (auto& dep : deps) {
        for (auto& info : dep.second) {
            if (info.first.empty()) {
                continue;
            }
            for (auto& oid : info.second) {
                ObjectIdentifier cPath(oid.canonicalPath());
                if (nodes.find(cPath) == nodes.end()) {
                    int s = nodes.size();
                    nodes[cPath] = s;
                }
                edges.emplace_back(nodes[path], nodes[cPath]);
            }
        }
    }
}

/**
 * @brief Create a canonical object identifier of the given object \a p.
 * @param p ObjectIndentifier
 * @return New ObjectIdentifier
 */

ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& oid) const
{
    DocumentObject* docObj = freecad_cast<DocumentObject*>(getContainer());

    // Am I owned by a DocumentObject?
    if (!docObj) {
        throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject.");
    }

    int ptype;
    Property* prop = oid.getProperty(&ptype);

    // oid pointing to a property...?
    if (!prop) {
        throw Base::RuntimeError(oid.resolveErrorString().c_str());
    }

    if (ptype || prop->getContainer() != getContainer()) {
        return oid;
    }

    // In case someone calls this with p pointing to a PropertyExpressionEngine for some reason
    if (prop->isDerivedFrom(PropertyExpressionEngine::classTypeId)) {
        return oid;
    }

    // Dispatch call to actual canonicalPath implementation
    return oid.canonicalPath();
}

/**
 * @brief Number of expressions managed by this object.
 * @return Number of expressions.
 */

size_t PropertyExpressionEngine::numExpressions() const
{
    return expressions.size();
}

void PropertyExpressionEngine::afterRestore()
{
    DocumentObject* docObj = freecad_cast<DocumentObject*>(getContainer());
    if (restoredExpressions && docObj) {
        Base::FlagToggler<bool> flag(restoring);
        AtomicPropertyChange signaller(*this);

        PropertyExpressionContainer::afterRestore();
        ObjectIdentifier::DocumentMapper mapper(this->_DocMap);

        for (auto& info : *restoredExpressions) {
            ObjectIdentifier path = ObjectIdentifier::parse(docObj, info.path);
            if (!info.expr.empty()) {
                std::shared_ptr<Expression> expression(
                    Expression::parse(docObj, info.expr.c_str()));
                if (expression) {
                    expression->comment = std::move(info.comment);
                }
                setValue(path, expression);
            }
        }
        signaller.tryInvoke();
    }
    restoredExpressions.reset();
}

void PropertyExpressionEngine::onContainerRestored()
{
    Base::FlagToggler<bool> flag(restoring);
    unregisterElementReference();
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this);
    for (auto& e : expressions) {
        auto expr = e.second.expression;
        if (expr) {
            expr->visit(v);
        }
    }
}

/**
 * @brief Get expression for \a path.
 * @param path ObjectIndentifier to query for.
 * @return Expression for \a path, or empty boost::any if not found.
 */

const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentifier& path) const
{
    // Get a canonical path
    ObjectIdentifier usePath(canonicalPath(path));

    auto i = expressions.find(usePath);
    if (i != expressions.end()) {
        return i->second;
    }

    return boost::any();
}

/**
 * @brief Set expression with optional comment for \a path.
 * @param path Path to update
 * @param expr New expression
 * @param comment Optional comment.
 */

void PropertyExpressionEngine::setValue(const ObjectIdentifier& path,
                                        std::shared_ptr<Expression> expr)
{
    ObjectIdentifier usePath(canonicalPath(path));
    const Property* prop = usePath.getProperty();

    // Try to access value; it should trigger an exception if it is not supported, or if the path is
    // invalid
    prop->getPathValue(usePath);

    // Check if the current expression equals the new one and do nothing if so to reduce unneeded
    // computations
    auto it = expressions.find(usePath);
    if (it != expressions.end()
        && (expr == it->second.expression
            || (expr && it->second.expression && expr->isSame(*it->second.expression)))) {
        return;
    }

    if (expr) {
        std::string error = validateExpression(usePath, expr);
        if (!error.empty()) {
            throw Base::RuntimeError(error.c_str());
        }
        AtomicPropertyChange signaller(*this);
        expressions[usePath] = ExpressionInfo(expr);
        expressionChanged(usePath);
        signaller.tryInvoke();
    }
    else if (it != expressions.end()) {
        AtomicPropertyChange signaller(*this);
        expressions.erase(it);
        expressionChanged(usePath);
        signaller.tryInvoke();
    }
}

/**
 * @brief The cycle_detector struct is used by the boost graph routines to detect cycles in the
 * graph.
 */

struct cycle_detector: public boost::dfs_visitor<>
{
    cycle_detector(bool& has_cycle, int& src)
        : _has_cycle(has_cycle)
        , _src(src)
    {}

    template<class Edge, class Graph>
    void back_edge(Edge e, Graph& g)
    {
        _has_cycle = true;
        _src = source(e, g);
    }

protected:
    bool& _has_cycle;
    int& _src;
};

/**
 * @brief Build a graph of all expressions in \a exprs.
 * @param exprs Expressions to use in graph
 * @param revNodes Map from int[nodeid] to ObjectIndentifer.
 * @param g Graph to update. May contain additional nodes than in revNodes, because of outside
 * dependencies.
 */

void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs,
                                          boost::unordered_map<int, ObjectIdentifier>& revNodes,
                                          DiGraph& g,
                                          ExecuteOption option) const
{
    boost::unordered_map<ObjectIdentifier, int> nodes;
    std::vector<Edge> edges;

    // Build data structure for graph
    for (const auto& expr : exprs) {
        if (option != ExecuteAll) {
            auto prop = expr.first.getProperty();
            if (!prop) {
                throw Base::RuntimeError("Path does not resolve to a property.");
            }
            bool is_output =
                prop->testStatus(App::Property::Output) || (prop->getType() & App::Prop_Output);
            if ((is_output && option == ExecuteNonOutput)
                || (!is_output && option == ExecuteOutput)) {
                continue;
            }
            if (option == ExecuteOnRestore && !prop->testStatus(Property::Transient)
                && !(prop->getType() & Prop_Transient)
                && !prop->testStatus(Property::EvalOnRestore)) {
                continue;
            }
        }
        buildGraphStructures(expr.first, expr.second.expression, nodes, revNodes, edges);
    }

    // Create graph
    g = DiGraph(nodes.size());

    // Add edges to graph
    for (const auto& edge : edges) {
        add_edge(edge.first, edge.second, g);
    }

    // Check for cycles
    bool has_cycle = false;
    int src = -1;
    cycle_detector vis(has_cycle, src);
    depth_first_search(g, visitor(vis));

    if (has_cycle) {
        std::string s = revNodes[src].toString() + " reference creates a cyclic dependency.";

        throw Base::RuntimeError(s.c_str());
    }
}

/**
 * The code below builds a graph for all expressions in the engine, and
 * finds any circular dependencies. It also computes the internal evaluation
 * order, in case properties depends on each other.
 */

std::vector<App::ObjectIdentifier>
PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option)
{
    std::vector<App::ObjectIdentifier> evaluationOrder;
    boost::unordered_map<int, ObjectIdentifier> revNodes;
    DiGraph g;

    buildGraph(expressions, revNodes, g, option);

    /* Compute evaluation order for expressions */
    std::vector<int> c;
    topological_sort(g, std::back_inserter(c));

    for (int i : c) {
        // we return the evaluation order for our properties, not the dependencies
        // the topo sort will contain node ids for both our props and their deps
        if (revNodes.find(i) != revNodes.end()) {
            evaluationOrder.push_back(revNodes[i]);
        }
    }

    return evaluationOrder;
}

/**
 * @brief Compute and update values of all registered expressions.
 * @return StdReturn on success.
 */

DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption option,
                                                                 bool* touched)
{
    DocumentObject* docObj = freecad_cast<DocumentObject*>(getContainer());

    if (!docObj) {
        throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject.");
    }

    if (running) {
        return DocumentObject::StdReturn;
    }

    if (option == ExecuteOnRestore) {
        bool found = false;
        for (auto& e : expressions) {
            auto prop = e.first.getProperty();
            if (!prop) {
                continue;
            }
            if (prop->testStatus(App::Property::Transient)
                || (prop->getType() & App::Prop_Transient)
                || prop->testStatus(App::Property::EvalOnRestore)) {
                found = true;
                break;
            }
        }
        if (!found) {
            return DocumentObject::StdReturn;
        }
    }

    /* Resetter class, to ensure that the "running" variable gets set to false, even if
     * an exception is thrown.
     */

    class resetter
    {
    public:
        explicit resetter(bool& b)
            : _b(b)
        {
            _b = true;
        }
        ~resetter()
        {
            _b = false;
        }

    private:
        bool& _b;
    };

    resetter r(running);

    // Compute evaluation order
    std::vector<App::ObjectIdentifier> evaluationOrder = computeEvaluationOrder(option);
    std::vector<ObjectIdentifier>::const_iterator it = evaluationOrder.begin();

#ifdef FC_PROPERTYEXPRESSIONENGINE_LOG
    std::clog << "Computing expressions for " << getName() << std::endl;
#endif

    /* Evaluate the expressions, and update properties */
    for (; it != evaluationOrder.end(); ++it) {

        // Get property to update
        Property* prop = it->getProperty();

        if (!prop) {
            throw Base::RuntimeError("Path does not resolve to a property.");
        }

        DocumentObject* parent = freecad_cast<DocumentObject*>(prop->getContainer());

        /* Make sure property belongs to the same container as this PropertyExpressionEngine */
        if (parent != docObj) {
            throw Base::RuntimeError("Invalid property owner.");
        }

        /* Set value of property */
        App::any value;
        try {
            // Evaluate expression
            std::shared_ptr<App::Expression> expression = expressions[*it].expression;
            if (expression) {
                value = expression->getValueAsAny();

                // Enable value comparison for all expression bindings to reduce
                // unnecessary touch and recompute.
                //
                // This optimization is necessary for some hidden reference to
                // work because it introduce dependency loop. The repeativtive
                // recompute can be stopped if the expression evaluates the same
                // value.
                //
                // In the future, we can generalize the optimization to all
                // property modification, i.e. do not touch unless value change
                //
                // if (option == ExecuteOnRestore && prop->testStatus(Property::EvalOnRestore))
                {
                    if (isAnyEqual(value, prop->getPathValue(*it))) {
                        continue;
                    }
                    if (touched) {
                        *touched = true;
                    }
                }
                prop->setPathValue(*it, value);
            }
        }
        catch (Base::Exception& e) {
            std::ostringstream ss;
            ss << e.what() << std::endl << "in property binding '" << prop->getFullName() << "'";
            e.setMessage(ss.str());
            throw;
        }
        catch (std::bad_cast&) {
            std::ostringstream ss;
            ss << "Invalid type '" << value.type().name() << "'";
            ss << "\nin property binding '" << prop->getFullName() << "'";
            throw Base::TypeError(ss.str().c_str());
        }
        catch (std::exception& e) {
            std::ostringstream ss;
            ss << e.what() << "\nin property binding '" << prop->getFullName() << "'";
            throw Base::RuntimeError(ss.str().c_str());
        }
    }
    return DocumentObject::StdReturn;
}

/**
 * @brief Find paths to document object.
 * @param obj Document object
 * @param paths Object identifier
 */

void PropertyExpressionEngine::getPathsToDocumentObject(
    DocumentObject* obj,
    std::vector<App::ObjectIdentifier>& paths) const
{
    DocumentObject* owner = freecad_cast<DocumentObject*>(getContainer());

    if (!owner || owner == obj) {
        return;
    }

    for (auto& v : expressions) {
        if (!v.second.expression) {
            continue;
        }
        const auto& deps = v.second.expression->getDeps();
        auto it = deps.find(obj);
        if (it == deps.end()) {
            continue;
        }
        for (auto& dep : it->second) {
            paths.insert(paths.end(), dep.second.begin(), dep.second.end());
        }
    }
}

/**
 * @brief Determine whether any dependencies of any of the registered expressions have been touched.
 * @return True if at least on dependency has been touched.
 */

bool PropertyExpressionEngine::depsAreTouched() const
{
    for (auto& v : _Deps) {
        // v.second indicates if it is a hidden reference
        if (!v.second && v.first->isTouched()) {
            return true;
        }
    }
    return false;
}

/**
 * @brief Validate the given path and expression.
 * @param path Object Identifier for expression.
 * @param expr Expression tree.
 * @return Empty string on success, error message on failure.
 */

std::string
PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path,
                                             std::shared_ptr<const Expression> expr) const
{
    std::string error;
    ObjectIdentifier usePath(canonicalPath(path));

    if (validator) {
        error = validator(usePath, expr);
        if (!error.empty()) {
            return error;
        }
    }

    // Get document object
    DocumentObject* pathDocObj = usePath.getDocumentObject();
    assert(pathDocObj);

    auto inList = pathDocObj->getInListEx(true);
    for (auto& v : expr->getDepObjects()) {
        auto docObj = v.first;
        if (!v.second && inList.contains(docObj)) {
            std::stringstream ss;
            ss << "cyclic reference to " << docObj->getFullName();
            return ss.str();
        }
    }

    // Check for internal document object dependencies

    // Copy current expressions
    ExpressionMap newExpressions = expressions;

    // Add expression in question
    std::shared_ptr<Expression> exprClone(expr->copy());
    newExpressions[usePath].expression = exprClone;

    // Build graph; an exception will be thrown if it is not a DAG
    try {
        boost::unordered_map<int, ObjectIdentifier> revNodes;
        DiGraph g;

        buildGraph(newExpressions, revNodes, g);
    }
    catch (const Base::Exception& e) {
        return e.what();
    }

    return {};
}

/**
 * @brief Rename paths based on \a paths.
 * @param paths Map with current and new object identifier.
 */

void PropertyExpressionEngine::renameExpressions(
    const std::map<ObjectIdentifier, ObjectIdentifier>& paths)
{
    ExpressionMap newExpressions;
    std::map<ObjectIdentifier, ObjectIdentifier> canonicalPaths;

    /* ensure input map uses canonical paths */
    for (const auto& path : paths) {
        canonicalPaths[canonicalPath(path.first)] = path.second;
    }

    for (auto i = expressions.begin(); i != expressions.end(); ++i) {
        auto j = canonicalPaths.find(i->first);

        // Renamed now?
        if (j != canonicalPaths.end()) {
            newExpressions[j->second] = i->second;
        }
        else {
            newExpressions[i->first] = i->second;
        }
    }

    aboutToSetValue();
    expressions = newExpressions;
    for (auto i = expressions.begin(); i != expressions.end(); ++i) {
        expressionChanged(i->first);
    }

    hasSetValue();
}

/**
 * @brief Rename object identifiers in the registered expressions.
 * @param paths Map with current and new object identifiers.
 */

void PropertyExpressionEngine::renameObjectIdentifiers(
    const std::map<ObjectIdentifier, ObjectIdentifier>& paths)
{
    for (const auto& it : expressions) {
        RenameObjectIdentifierExpressionVisitor<PropertyExpressionEngine> v(*this, paths, it.first);
        it.second.expression->visit(v);
    }
}

PyObject* PropertyExpressionEngine::getPyObject()
{
    Py::List list;
    for (const auto& it : expressions) {
        Py::Tuple tuple(2);
        tuple.setItem(0, Py::String(it.first.toString()));
        auto expr = it.second.expression;
        tuple.setItem(1, expr ? Py::String(expr->toString()) : Py::None());
        list.append(tuple);
    }
    return Py::new_reference_to(list);
}

void PropertyExpressionEngine::setPyObject(PyObject*)
{
    throw Base::RuntimeError("Property is read-only");
}

/* The policy implemented in the following function is to auto erase binding in
 * case linked object is gone. I think it is better to cause error and get
 * user's attention
 *
void PropertyExpressionEngine::breakLink(App::DocumentObject *obj, bool clear) {
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
    if(!owner)
        return;
    if(_Deps.count(obj)==0 && (!clear || obj!=owner || _Deps.empty()))
        return;
    AtomicPropertyChange signaler(*this);
    for(auto it=expressions.begin(),itNext=it;it!=expressions.end();it=itNext) {
        ++itNext;
        const auto &deps = it->second.expression->getDepObjects();
        if(clear) {
            // here means we are breaking all expression, except those that has
            // no depdenecy or self dependency
            if(deps.empty() || (deps.size()==1 && *deps.begin()==owner))
                continue;
        }else if(!deps.count(obj))
            continue;
        auto path = it->first;
        expressions.erase(it);
        expressionChanged(path);
    }
}
*/

bool PropertyExpressionEngine::adjustLink(const std::set<DocumentObject*>& inList)
{
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
    if (!owner) {
        return false;
    }
    bool found = false;
    for (auto& v : _Deps) {
        if (inList.contains(v.first)) {
            found = true;
            break;
        }
    }
    if (!found) {
        return false;
    }

    AtomicPropertyChange signaler(*this);
    for (auto& v : expressions) {
        try {
            if (v.second.expression && v.second.expression->adjustLinks(inList)) {
                expressionChanged(v.first);
            }
        }
        catch (Base::Exception& e) {
            std::ostringstream ss;
            ss << "Failed to adjust link for " << owner->getFullName() << " in expression "
               << v.second.expression->toString() << ": " << e.what();
            throw Base::RuntimeError(ss.str());
        }
    }
    return true;
}

void PropertyExpressionEngine::updateElementReference(DocumentObject* feature,
                                                      bool reverse,
                                                      bool notify)
{
    (void)notify;
    if (!feature) {
        unregisterElementReference();
    }
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this, feature, reverse);
    for (auto& e : expressions) {
        if (e.second.expression) {
            e.second.expression->visit(v);
            if (v.changed()) {
                expressionChanged(e.first);
                v.reset();
            }
        }
    }
    if (feature && v.changed()) {
        auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
        if (owner) {
            owner->onUpdateElementReference(this);
        }
    }
}

bool PropertyExpressionEngine::referenceChanged() const
{
    return false;
}

Property* PropertyExpressionEngine::CopyOnImportExternal(
    const std::map<std::string, std::string>& nameMap) const
{
    std::unique_ptr<PropertyExpressionEngine> engine;
    for (auto it = expressions.begin(); it != expressions.end(); ++it) {
#ifdef BOOST_NO_CXX11_SMART_PTR
        std::shared_ptr<Expression> expr(it->second.expression->importSubNames(nameMap).release());
#else
        std::shared_ptr<Expression> expr(it->second.expression->importSubNames(nameMap));
#endif
        if (!expr && !engine) {
            continue;
        }
        if (!engine) {
            engine = std::make_unique<PropertyExpressionEngine>();
            for (auto it2 = expressions.begin(); it2 != it; ++it2) {
                engine->expressions[it2->first] =
                    ExpressionInfo(std::shared_ptr<Expression>(it2->second.expression->copy()));
            }
        }
        else if (!expr) {
            expr = it->second.expression;
        }
        engine->expressions[it->first] = ExpressionInfo(expr);
    }
    if (!engine) {
        return nullptr;
    }
    engine->validator = validator;
    return engine.release();
}

Property* PropertyExpressionEngine::CopyOnLabelChange(App::DocumentObject* obj,
                                                      const std::string& ref,
                                                      const char* newLabel) const
{
    std::unique_ptr<PropertyExpressionEngine> engine;
    for (auto it = expressions.begin(); it != expressions.end(); ++it) {
#ifdef BOOST_NO_CXX11_SMART_PTR
        std::shared_ptr<Expression> expr(
            it->second.expression->updateLabelReference(obj, ref, newLabel).release());
#else
        std::shared_ptr<Expression> expr(
            it->second.expression->updateLabelReference(obj, ref, newLabel));
#endif
        if (!expr && !engine) {
            continue;
        }
        if (!engine) {
            engine = std::make_unique<PropertyExpressionEngine>();
            for (auto it2 = expressions.begin(); it2 != it; ++it2) {
                ExpressionInfo info;
                if (it2->second.expression) {
                    info.expression = std::shared_ptr<Expression>(it2->second.expression->copy());
                }
                engine->expressions[it2->first] = info;
            }
        }
        else if (!expr) {
            expr = it->second.expression;
        }
        engine->expressions[it->first] = ExpressionInfo(expr);
    }
    if (!engine) {
        return nullptr;
    }
    engine->validator = validator;
    return engine.release();
}

Property* PropertyExpressionEngine::CopyOnLinkReplace(const App::DocumentObject* parent,
                                                      App::DocumentObject* oldObj,
                                                      App::DocumentObject* newObj) const
{
    std::unique_ptr<PropertyExpressionEngine> engine;
    for (auto it = expressions.begin(); it != expressions.end(); ++it) {
#ifdef BOOST_NO_CXX11_SMART_PTR
        std::shared_ptr<Expression> expr(
            it->second.expression->replaceObject(parent, oldObj, newObj).release());
#else
        std::shared_ptr<Expression> expr(
            it->second.expression->replaceObject(parent, oldObj, newObj));
#endif
        if (!expr && !engine) {
            continue;
        }
        if (!engine) {
            engine = std::make_unique<PropertyExpressionEngine>();
            for (auto it2 = expressions.begin(); it2 != it; ++it2) {
                ExpressionInfo info;
                if (it2->second.expression) {
                    info.expression = std::shared_ptr<Expression>(it2->second.expression->copy());
                }
                engine->expressions[it2->first] = info;
            }
        }
        else if (!expr) {
            expr = it->second.expression;
        }
        engine->expressions[it->first] = ExpressionInfo(expr);
    }
    if (!engine) {
        return nullptr;
    }
    engine->validator = validator;
    return engine.release();
}

std::map<App::ObjectIdentifier, const App::Expression*>
PropertyExpressionEngine::getExpressions() const
{
    std::map<App::ObjectIdentifier, const Expression*> res;
    for (auto& v : expressions) {
        res[v.first] = v.second.expression.get();
    }
    return res;
}

void PropertyExpressionEngine::setExpressions(
    std::map<App::ObjectIdentifier, App::ExpressionPtr>&& exprs)
{
    AtomicPropertyChange signaller(*this);
#ifdef BOOST_NO_CXX11_SMART_PTR
    for (auto& v : exprs) {
        setValue(v.first, std::shared_ptr<Expression>(v.second.release()));
    }
#else
    for (auto& v : exprs) {
        setValue(v.first, std::move(v.second));
    }
#endif
}

void PropertyExpressionEngine::onRelabeledDocument(const App::Document& doc)
{
    RelabelDocumentExpressionVisitor v(doc);
    for (auto& e : expressions) {
        if (e.second.expression) {
            e.second.expression->visit(v);
        }
    }
}

void PropertyExpressionEngine::onRenameDynamicProperty(const App::Property& prop, const char* oldName)
{
    ObjectIdentifier oldNameId = ObjectIdentifier(prop.getContainer(), std::string(oldName));
    ObjectIdentifier newNameId = ObjectIdentifier(prop);
    const std::map<ObjectIdentifier, ObjectIdentifier> paths = {
        {oldNameId, newNameId},
    };

    renameObjectIdentifiers(paths);
}

void PropertyExpressionEngine::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
                                          App::DocumentObject* obj,
                                          const char* subname,
                                          bool all) const
{
    Expression::DepOption option =
        all ? Expression::DepOption::DepAll : Expression::DepOption::DepNormal;

    App::SubObjectT objT(obj, subname);
    auto sobj = objT.getSubObject();
    auto subElement = objT.getOldElementName();

    for (auto& [expressionId, expressionInfo] : expressions) {
        const auto& deps = expressionInfo.expression->getDeps(option);
        auto it = deps.find(obj);
        if (it == deps.end()) {
            continue;
        }
        auto [docObj, map] = *it;
        for (auto& [key, paths] : map) {
            if (!subname) {
                identifiers.push_back(expressionId);
                break;
            }
            if (std::ranges::any_of(paths, [subname, obj, sobj, &subElement](const auto& path) {
                    if (path.getSubObjectName() == subname) {
                        return true;
                    }

                    App::SubObjectT sobjT(obj, path.getSubObjectName().c_str());
                    return (sobjT.getSubObject() == sobj
                            && sobjT.getOldElementName() == subElement);
                })) {
                identifiers.push_back(expressionId);
            }
        }
    }
}
