#ifndef CLASPLAYER_H
#define CLASPLAYER_H

#include "ast.h"
#include "contextrule.h"

#include <clingo/clingocontrol.hh>
#include <string>
#include <climits>
#include <boost/bimap.hpp>
#include <boost/optional.hpp>
#include <list>

namespace xreate {

	typedef unsigned int ScopePacked;

    class CFAGraph {
        friend class ClaspLayer;

    public:
        void addFunctionAnnotations(const std::string& function, const std::vector<Tag>&tags);
        void addScopeAnnotations(const ScopePacked& scope, const std::vector<Expression>&tags);
        void addContextRules(const ScopePacked& scope, const std::vector<Expression>&rules);

        void addCallConnection(const ScopePacked& scopeFrom, const std::string& functionTo);
        void addParentConnection(const ScopePacked& scope, const std::string& functionParent);
        void addParentConnection(const ScopePacked& scope, const ScopePacked& scopeParent);
//        void addScopeRetIdentifier(const ScopePacked& scope, const SymbolPacked& identifier);

    private:
        std::map<ScopePacked, unsigned int> __parentFunctionRelations;
        std::map<ScopePacked, ScopePacked> __parentScopeRelations;
        std::set<std::pair<ScopePacked, unsigned int>> __callRelations;
        boost::bimap<unsigned int, std::string > __nodesFunction;
        std::multimap<unsigned int, Tag> __functionTags;
        std::multimap<ScopePacked, Expression> __scopeTags;
        std::multimap<ScopePacked, ContextRule> __contextRules;

        unsigned int registerNodeFunction(const std::string& fname);
    };



    struct SymbolPacked
    {
        VID identifier;
        ScopePacked scope;
    };

    struct SymbolTransient{
    	std::list<Expression> tags;
    };

    struct SymbolInvalid{};

    typedef boost::variant<SymbolPacked, SymbolTransient, SymbolInvalid> SymbolNode;

    bool operator==(const SymbolPacked& s1, const SymbolPacked& s2);
    bool operator<(const SymbolPacked& s1, const SymbolPacked& s2);

    enum class DFGConnection{
        ALIAS, OPT, PROTO};

    class VisitorAddTag;
    class VisitorAddLink;

    class DFAGraph
    {
        friend class ClaspLayer;
        friend class VisitorAddTag;
        friend class VisitorAddLink;
    public:
        void addAnnotation(SymbolNode& identifier, Expression&& tag);
        void addConnection(const SymbolPacked& identifierTo, const SymbolNode& identifierFrom, DFGConnection link);
        bool isConnected(const SymbolPacked& identifierTo, const SymbolPacked& identifierFrom);

    private:
        typedef unsigned int EdgeId;
        std::vector<std::pair<SymbolPacked, SymbolPacked>> __edges;
        std::multimap<SymbolPacked, EdgeId> __outEdges;
        std::vector<DFGConnection> __data;
        std::multimap<SymbolPacked, Expression> __tags;
    };

    class IQuery    {
    public:
        virtual void init(ClaspLayer* clasp)=0;
        virtual ~IQuery() {}
    };

    enum class QueryId{
    	ContainersQuery,
		ContextQuery,
		PtrvalidQuery
    };

    class ClaspLayer {
    	friend class ContextRule;

    public:
        AST *ast;
        DFAGraph dfaData;
        CFAGraph cfagraph;

        ClaspLayer();
        IQuery* registerQuery(IQuery* query, const QueryId& id);
        IQuery* getQuery(const QueryId& id);
        void addFunctionTags(const std::string &function, const std::vector<Tag> &tags);
        void setCFAData(CFAGraph &&graph);
        void addDFAData(DFAGraph &&graph);
        void addRuleWarning(const RuleWarning &rule);

        void run();

        template<class ...Types>
        static std::tuple<Types...> parse(const Gringo::Value& atom);

        typedef std::multimap<std::string, Gringo::Value>::const_iterator ModelIterator;
        typedef  boost::optional<std::pair<ClaspLayer::ModelIterator, ClaspLayer::ModelIterator>> ModelFragment;
        ModelFragment query(const std::string& atom);

        ScopePacked pack(CodeScope* const scope);
        SymbolPacked pack(const Symbol& symbol, std::string hintSymbolName="");
        Symbol unpack(const SymbolPacked& symbol);
        std::string getHintForPackedSymbol(const SymbolPacked& symbol);
        void addRawScript(std::string&& script);

    private:
        // all query plugins to process clasp data
        std::map<QueryId, IQuery*> __queries;
        std::multimap<std::string, Gringo::Value> __model;
        std::map<unsigned int, std::string> __warnings;
        std::ostringstream __partTags;
        std::ostringstream __partGeneral;
        std::map<SymbolPacked, std::string> __indexSymbolNameHints;

        std::unordered_map<const CodeScope*, unsigned int> __indexScopes;
        std::vector<CodeScope*> __registryScopes;

        void printWarnings(std::ostream& out);
        bool onModel(Gringo::Model const &model);
        static std::list<std::string> compile(const Expression &e);
        static std::list<std::string> compileNeg(const Expression &e);
        unsigned int registerWarning(std::string &&message);
        void involveImports();
        void involveCFAData();
    };

    template<class typ>
    struct ParseImplAtom {
        static typ get(const Gringo::Value& atom)
        {
            return atom.num();
        }
    };

    template<>
    struct ParseImplAtom<std::string> {
        static std::string get(const Gringo::Value& atom)
        {
            return *atom.string();
        }};

    template<>
    struct ParseImplAtom<SymbolPacked> {
        static SymbolPacked get(const Gringo::Value& atom)
        {
            auto result = ClaspLayer::parse<unsigned int, unsigned int>(atom);
            return SymbolPacked{std::get<0>(result), std::get<1>(result)};
        }};

    template<>
    struct ParseImplAtom<Gringo::Value> {
        static Gringo::Value get(const Gringo::Value& atom)
        {
            return atom;
        }};

    template<>
    struct ParseImplAtom<Expression> {
        static Expression get(const Gringo::Value& atom)
        {
            switch (atom.type()){
            	case Gringo::Value::NUM:	return Expression(atom.num());
            	case Gringo::Value::ID: return Expression((std::string(atom.string())));
            	case Gringo::Value::FUNC: {
            		Expression result(Operator::CALL, {Expression(std::string(atom.name()))});
            		for(const Gringo::Value& arg: atom.args()){
            			result.addArg(ParseImplAtom<Expression>::get(arg));
            		}

            		return result;
            	}

            	default: {
            		assert(false);
            	}
         }
    }};

    template<class Tuple, size_t index>
    struct Parse_Impl {
        static void parse(Tuple& tup, Gringo::FWValVec::const_iterator arg)
        {
            const size_t tupleSize = std::tuple_size<Tuple>::value;
            typedef typename std::tuple_element<tupleSize - index, Tuple>::type ElType;

            ElType&  el = std::get<tupleSize - index>(tup);

            Gringo::Value atom = *arg;
            el = ParseImplAtom<ElType>::get(atom);

            Parse_Impl<Tuple, index-1>::parse(tup, ++arg);
        }
    };

    template<class Tuple>
    struct Parse_Impl<Tuple, 0>
    {
        static void parse(Tuple& tup, Gringo::FWValVec::const_iterator arg)
        {}
    };


    template<class ...Types>
    std::tuple<Types...>
    ClaspLayer::parse(const Gringo::Value& atom)
    {
        typedef std::tuple<Types...> Tuple;
        Tuple tup;
        Parse_Impl<Tuple, std::tuple_size<Tuple>::value>::parse(tup, atom.args().begin());

        return tup;
    }


}
#endif
