#include "analysis/dfagraph.h"
#include "analysis/aux.h"

#include <list>


using namespace xreate;
using namespace xreate::analysis;
using namespace std;

namespace xreate {
    namespace analysis {

        void
        DFAGraph::print(std::ostringstream& output) const {
            std::set<SymbolPacked> symbols;

            output << endl << "%\t\tStatic analysis: DFA" << endl;

            std::vector<std::pair<SymbolPacked, SymbolPacked>>::const_iterator i1;
            std::vector<DFGConnection>::const_iterator i2;

            boost::format formatDfaConnection("dfa_connection(%1%, %2%, %3%).");
            for (i1 = this->__edges.begin(), i2 = this->__data.begin(); i1 != this->__edges.end(); ++i1, ++i2) {
                string edgeName;
                switch (*i2) {
                    case DFGConnection::WEAK: edgeName = "weak";
                        break;
                    case DFGConnection::STRONG: edgeName = "strong";
                        break;
                    case DFGConnection::PROTOTYPE: edgeName = "proto";
                        break;
                }

                output << formatDfaConnection
                        % formatSymbol(i1->first)
                        % formatSymbol(i1->second)
                        % edgeName
                        << " %" << this->__clasp->getHintForPackedSymbol(i1->first) << " - " << this->__clasp->getHintForPackedSymbol(i1->second)
                        << endl;

                symbols.insert(i1->first);
                symbols.insert(i1->second);
            }

            boost::format formatDfaDependency("dfa_dependency(%1%, %2%).");
            for (auto i = this->__dependencies.begin(); i != this->__dependencies.end(); ++i) {
                output << formatDfaDependency
                        % formatSymbol(i->first)
                        % formatSymbol(i->second)
                        << " %"
                        << this->__clasp->getHintForPackedSymbol(i->first) << " - "
                        << this->__clasp->getHintForPackedSymbol(i->second)
                        << endl;
            }

            boost::format formatBind("bind(%1%, %2%).");
            for (const pair<SymbolPacked, Expression>& tag : this->__tags) {
                for (string variant : xreate::analysis::compile(tag.second)) {
                    output << formatBind
                            % formatSymbol(tag.first)
                            % (variant)
                            << "%" << this->__clasp->getHintForPackedSymbol(tag.first)
                            << endl;
                }

                symbols.insert(tag.first);
            }

            for (const SymbolPacked& s : symbols) {
                output << "v(" << formatSymbol(s) << ")."
                        << " %" << this->__clasp->getHintForPackedSymbol(s)
                        << endl;
            }
        }

        class VisitorAddTag : public boost::static_visitor<> {
        public:

            void operator()(const SymbolPacked& symbol) {
                __graph->__tags.emplace(symbol, move(__tag));
            }

            void operator()(SymbolAnonymous& symbol) {
                symbol.tags.push_back(move(__tag));
            }

            void operator()(const SymbolInvalid& symbol) {
                assert(false && "Undefined behaviour");
            }

            VisitorAddTag(DFAGraph * const dfagraph, Expression&& tag) :
            __graph(dfagraph), __tag(tag) {
            }

        private:
            DFAGraph * const __graph;
            Expression __tag;
        };

        class VisitorAddLink : public boost::static_visitor<> {
        public:

            void operator()(const SymbolPacked& nodeFrom) {
                if (!__graph->isConnected(__nodeTo, nodeFrom)) {
                    __graph->__edges.emplace_back(__nodeTo, nodeFrom);
                    __graph->__data.push_back(__link);

                    DFAGraph::EdgeId eid = __graph->__edges.size() - 1;
                    __graph->__outEdges.emplace(nodeFrom, eid);
                }
            }

            void operator()(const SymbolAnonymous& symbolFrom) {
                switch (__link) {
                    case DFGConnection::WEAK:
                    {
                        //virtual symbol to hold transient annotations
                        SymbolPacked symbPivot = __graph->createAnonymousSymbol(symbolFrom.scope);

                        __graph->addConnection(symbPivot, symbolFrom, DFGConnection::STRONG);
                        __graph->addConnection(__nodeTo, symbPivot, DFGConnection::WEAK);
                        break;
                    }

                    case DFGConnection::STRONG:
                    {
                        for (const Expression& tag : symbolFrom.tags) {
                            __graph->__tags.emplace(__nodeTo, tag);
                        }
                        break;
                    }

                    default:
                        assert(false && "Undefined behavior");
                }
            }

            void operator()(const SymbolInvalid&) {
                if (__link == DFGConnection::STRONG) return;
                if (__link == DFGConnection::WEAK) return;

                assert(false && "Undefined behavior");
            }

            VisitorAddLink(DFAGraph * const dfagraph, const SymbolPacked& nodeTo, DFGConnection link) :
            __graph(dfagraph), __nodeTo(nodeTo), __link(link) {
            }

        private:
            DFAGraph * const __graph;
            SymbolPacked __nodeTo;
            DFGConnection __link;
        };

        class VisitorGetDependencyConnection : public boost::static_visitor<list<SymbolPacked>>
        {
            public:

            list<SymbolPacked>
                    operator()(const SymbolPacked & nodeFrom) {
                return
                {
                    nodeFrom
                };
            }

            list<SymbolPacked>
                    operator()(const SymbolAnonymous & nodeFrom) {
                return nodeFrom.dependencies;
            }

            list<SymbolPacked>
                    operator()(const SymbolInvalid&) {
                assert(false && "Undefined behavior");
            }

            VisitorGetDependencyConnection(DFAGraph * const g) : graph(g) {
            }
            DFAGraph * const graph;
        };

        class VisitorSetDependencyConnection : public boost::static_visitor<> {
        public:

            void operator()(SymbolPacked& nodeTo) {
                VisitorGetDependencyConnection visitorGetDepenencies(graph);
                auto deps = boost::apply_visitor(visitorGetDepenencies, nodeFrom);

                for (const SymbolPacked& dep : deps) {
                    graph->__dependencies.emplace(nodeTo, dep);
                }
            }

            void operator()(SymbolAnonymous& nodeTo) {
                VisitorGetDependencyConnection visitorGetDepenencies(graph);
                auto deps = boost::apply_visitor(visitorGetDepenencies, nodeFrom);

                for (const SymbolPacked& dep : deps) {
                    nodeTo.dependencies.push_back(dep);
                }
            }

            void operator()(SymbolInvalid&) {
                assert(false && "Undefined behavior");
            }

            VisitorSetDependencyConnection(DFAGraph * const g, SymbolNode s) : graph(g), nodeFrom(s) {
            }
            DFAGraph * const graph;
            SymbolNode nodeFrom;
        };

        bool
        DFAGraph::isConnected(const SymbolPacked& identifierTo, const SymbolPacked& identifierFrom) {
            auto range = __outEdges.equal_range(identifierFrom);

            for (std::multimap<SymbolPacked, EdgeId>::iterator edge = range.first; edge != range.second; ++edge) {
                if (__edges[edge->second].second == identifierTo)
                    return true;
            }

            return false;
        }

        void
        DFAGraph::addConnection(const SymbolPacked& nodeTo, const SymbolNode& nodeFrom, DFGConnection link) {
            VisitorAddLink visitor(this, nodeTo, link);
            boost::apply_visitor(visitor, nodeFrom);
        }

        void
        DFAGraph::addDependencyConnection(SymbolNode& identifierTo, SymbolNode& identifierFrom) {
            VisitorSetDependencyConnection visitor(this, identifierFrom);
            boost::apply_visitor(visitor, identifierTo);
        }

        void
        DFAGraph::addAnnotation(SymbolNode& node, Expression&& tag) {
            VisitorAddTag visitor(this, move(tag));
            boost::apply_visitor(visitor, node);
        }

        SymbolPacked
        DFAGraph::createAnonymousSymbol(const ScopePacked& scope) {
            return SymbolPacked(ScopedSymbol{__countAnonymousSymbols++, 0}, scope, true);
        }

    }
}