/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "ecflow/simulator/DefsAnalyserVisitor.hpp"

#include "ecflow/core/Indentor.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/ExprAst.hpp"
#include "ecflow/node/Family.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/Task.hpp"
#include "ecflow/simulator/AstAnalyserVisitor.hpp"

using namespace std;

namespace ecf {

///////////////////////////////////////////////////////////////////////////////
DefsAnalyserVisitor::DefsAnalyserVisitor() = default;

void DefsAnalyserVisitor::visitDefs(Defs* d) {
    for (suite_ptr s : d->suiteVec()) {
        s->acceptVisitTraversor(*this);
    }
}

void DefsAnalyserVisitor::visitSuite(Suite* s) {
    visitNodeContainer(s);
}
void DefsAnalyserVisitor::visitFamily(Family* f) {
    visitNodeContainer(f);
}

void DefsAnalyserVisitor::visitNodeContainer(NodeContainer* nc) {
    std::set<Node*> dependentNodes;
    analyse(nc, dependentNodes);

    for (node_ptr t : nc->nodeVec()) {
        t->acceptVisitTraversor(*this);
    }
}

void DefsAnalyserVisitor::visitTask(Task* t) {
    std::set<Node*> dependentNodes;
    analyse(t, dependentNodes);
}

void DefsAnalyserVisitor::analyse(Node* node, std::set<Node*>& dependentNodes, bool dependent) {
    // ***************************************************************
    // Do a depth first search to find the root cause of the blockage
    // ***************************************************************
    // #ifdef DEBUG
    //	if (dependent)  {
    //		ss_ << "DefsAnalyserVisitor::analyse " << node->debugType() << Str::COLON() << node->absNodePath();
    //		ss_ << " state(" << NState::toString(node->state()) << ")\n";
    //	}
    // #endif
    if (analysedNodes_.find(node) != analysedNodes_.end())
        return;
    analysedNodes_.insert(node);
    if (node->state() == NState::COMPLETE)
        return;

    if (node->state() == NState::QUEUED) {
        std::vector<std::string> theReasonWhy;
        node->why(theReasonWhy);
        for (const auto& i : theReasonWhy) {
            Indentor::indent(ss_) << "Reason: " << i << "\n";
        }
    }

    /// Note a complete expression that does not evaluate, does *NOT* hold the node
    /// It merly sets node to complete.
    if (node->completeAst() && !node->evaluateComplete()) {
        // Follow nodes referenced in the complete expressions
        analyseExpressions(node, dependentNodes, false, dependent);

        // follow child nodes
        auto* nc = dynamic_cast<NodeContainer*>(node);
        if (nc) {
            for (node_ptr t : nc->nodeVec()) {
                t->acceptVisitTraversor(*this);
            }
        }
    }

    if (node->triggerAst() && !node->evaluateTrigger()) {
        // Follow nodes referenced in the trigger expressions
        analyseExpressions(node, dependentNodes, true, dependent);

        // follow child nodes
        auto* nc = dynamic_cast<NodeContainer*>(node);
        if (nc) {
            for (node_ptr t : nc->nodeVec()) {
                t->acceptVisitTraversor(*this);
            }
        }
    }
}

void DefsAnalyserVisitor::analyseExpressions(Node* node,
                                             std::set<Node*>& dependentNodes,
                                             bool trigger,
                                             bool dependent) {
    Indentor in;
    Indentor::indent(ss_);
    if (dependent)
        ss_ << "DEPENDENT ";
    if (trigger) {
        ss_ << node->debugNodePath() << " holding on trigger expression '" << node->triggerExpression() << "'\n";
    }
    else {
        ss_ << node->debugNodePath() << " holding on complete expression '" << node->completeExpression() << "'\n";
    }

    AstAnalyserVisitor astVisitor;
    if (trigger) {
        node->triggerAst()->accept(astVisitor);
        ss_ << *node->triggerAst();
    }
    else {
        node->completeAst()->accept(astVisitor);
        ss_ << *node->completeAst();
    }

    // Warn about NULL node references in the trigger expressions
    for (const string& nodePath : astVisitor.dependentNodePaths()) {
        Indentor in2;
        Indentor::indent(ss_) << "'" << nodePath << "' is not defined in the expression\n";
    }

    // **** NOTE: Currently for COMPLETE expression will only follow trigger expressions
    for (Node* triggerNode : astVisitor.dependentNodes()) {

        Indentor in2;
        Indentor::indent(ss_) << "EXPRESSION NODE " << triggerNode->debugNodePath();
        ss_ << " state(" << NState::toString(triggerNode->state()) << ")";
        if (triggerNode->triggerAst())
            ss_ << " trigger(evaluation = " << triggerNode->evaluateTrigger() << "))";
        if (analysedNodes_.find(triggerNode) != analysedNodes_.end())
            ss_ << " analysed ";
        if (dependentNodes.find(triggerNode) != dependentNodes.end())
            ss_ << " ** ";
        ss_ << "\n";

        if (dependentNodes.find(triggerNode) != dependentNodes.end()) {
            // possible deadlock make sure
            if (triggerNode->triggerAst()) {
                AstAnalyserVisitor visitor;
                triggerNode->triggerAst()->accept(visitor);

                //				cerr << "Node = " << node->absNodePath() << "\n";
                //				cerr << "triggerNode = " << triggerNode->absNodePath() << "\n";
                //				for(Node* n:visitor.dependentNodes() ) {
                //					cerr << "triggerNode Node dependents = " << n->absNodePath() <<
                //"\n";
                //				}

                if (visitor.dependentNodes().find(node) != visitor.dependentNodes().end()) {
                    Indentor in3;
                    Indentor::indent(ss_) << "Deadlock detected between:\n";
                    Indentor in4;
                    Indentor::indent(ss_) << node->debugNodePath() << "\n";
                    Indentor::indent(ss_) << triggerNode->debugNodePath() << "\n";
                }
            }
            continue;
        }
        dependentNodes.insert(triggerNode);
        analyse(triggerNode, dependentNodes, true);
    }
}

} // namespace ecf
