#ifndef CLASPLAYER_H
#define CLASPLAYER_H

#include <ast.h>

#include <gringo/control.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 addFunctionNodeTags(const std::string& function, const std::vector<Tag>&tags);
        void addScopeNodeTags(const ScopePacked& scope, const std::vector<Expression>&tags);
        void addLink(const ScopePacked& scopeFrom, const std::string& functionTo);
        //void print(std::ostream &cout) const;

    private:
        std::map<ScopePacked, unsigned int> __relations;
        boost::bimap<unsigned int, std::string > __nodesFunction;
        std::multimap<unsigned int, Tag> __functionTags;
        std::multimap<ScopePacked, Expression> __scopeTags;

        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 addTag(SymbolNode& node, Expression&& tag);
        void addLink(const SymbolPacked& nodeTo, const SymbolNode& nodeFrom, DFGConnection link);
        bool linkExists(const SymbolPacked& node1, const SymbolPacked& node2);

    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() {}
    };

    class ClaspLayer {

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

        ClaspLayer();
        void registerdQuery(IQuery* query);
        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>> ModelRange;
        ModelRange query(const std::string& atom);

        ScopePacked pack(CodeScope* scope);
        SymbolPacked pack(const Symbol& symbol, std::string hintSymbolName="");
        Symbol unpack(const SymbolPacked& symbol);
        void addRawScript(std::string&& script);
    private:
        // all query plugins to process clasp data
        std::list<IQuery*> __queries;
        std::multimap<std::string, Gringo::Value> __model;
        std::map<unsigned int, std::string> __warnings;
        std::ostringstream __partTags;
        std::ostringstream __partGeneral;

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

        void printWarnings(std::ostream& out);
        bool onModel(Gringo::Model const &model);
        std::list<std::string> compile(const Expression &e) const;
        std::list<std::string> compileNeg(const Expression &e) const;
        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<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
