#ifndef CLASPLAYER_H
#define CLASPLAYER_H
#include <string>
#include <gringo/control.hh>
#include <ast.h>
#include <QStringList>
#include <climits>

namespace xreate {

    class CFGraph {
        friend class ClaspLayer;

    public:
        void addNode(unsigned int function, std::string &&tag);
        void addLink(unsigned int nodeFrom, unsigned int nodeTo);
        bool existsNode(unsigned int function) const;
        void print(std::ostream &cout) const;

    private:
        std::map<unsigned int, unsigned int> __relations;
        std::map<unsigned int, std::string> __nodes;
    };

    enum class DFGConnection{
        STRONG, OPT, PROTO};


    struct SymbolPacked
    {
        VID identifier;
        unsigned int scope;

        bool isValid() const;
    };

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

    const SymbolPacked SYMBOL_INVALID{UINT_MAX, UINT_MAX};

    class DFGraph
    {
        friend class ClaspLayer;
    public:
        void addTag(const SymbolPacked& node, Expression&& tag);
        void addLink(const SymbolPacked& nodeTo, const SymbolPacked& nodeFrom, DFGConnection link);
        bool linkExists(const SymbolPacked& node1, const SymbolPacked& node2);

        SymbolPacked pack(const Symbol& symbol, std::string hintSymbolName="");
        Symbol unpack(const SymbolPacked& symbol);
    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;

        std::unordered_map<const CodeScope*, unsigned int> __hash;
        std::vector<CodeScope*> __hashedScopes;
    };

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

    class ClaspLayer {

    public:
        AST *ast;
        DFGraph dfgData;

        ClaspLayer();
        void registerdQuery(IQuery* query);
        void addFunctionTags(const std::string &function, const std::vector<Tag> &tags);
        void addCFAData(CFGraph &&graph);
        void addDFAData(DFGraph &&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;
        std::pair<ModelIterator, ModelIterator> query(const std::string& atom);
    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;

        void printWarnings(std::ostream& out);
        bool onModel(Gringo::Model const &model);
        QStringList compile(const Expression &e) const;
        QStringList compileNeg(const Expression &e) const;
        unsigned int registerWarning(std::string &&message);
        void addImports();
    };

    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
