#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/bimap/multiset_of.hpp>
#include <boost/optional.hpp>
#include <boost/scoped_ptr.hpp>
#include <list>

namespace xreate {

    typedef unsigned int ScopePacked;

    struct SymbolPacked {
        VNameId identifier;
        VariableVersion version;
        ScopePacked scope;
        bool categoryTransient;
        
        SymbolPacked(): categoryTransient(false){}
        SymbolPacked(ScopedSymbol i, ScopePacked s, bool isTransient = false): identifier(i.id), version(i.version), scope(s), categoryTransient(isTransient){}
        SymbolPacked(VNameId symbolId, VariableVersion symbolVersion, ScopePacked symbolScope, bool isTransient = false)
            : identifier(symbolId), version(symbolVersion), scope(symbolScope), categoryTransient(isTransient){}
    };

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

    enum class DFGConnection {
        STRONG, WEAK, PROTOTYPE
    };

    class IAnalysisData {
    public:
        void print(std::ostringstream& output) const;
        virtual ~IAnalysisData(){};
    };
    
    class IQuery {
    public:
        virtual void init(ClaspLayer* clasp) = 0;
        virtual ~IQuery() {}
    };

    enum class QueryId {
        ContainersQuery,
        ContextQuery,
        PtrvalidQuery
    };

    namespace analysis{
        class DFAGraph;
        class CFAGraph;
    }
    
    class ClaspLayer {
        friend class ContextRule;

                    //PROVIDERS:
    public:
        boost::scoped_ptr<xreate::analysis::DFAGraph> dataDFA;
        void setDFAData(xreate::analysis::DFAGraph* graph);

        boost::scoped_ptr<xreate::analysis::CFAGraph> dataCFA;
        void setCFAData(xreate::analysis::CFAGraph* graph);

        void addRawScript(std::string&& script);

    private: 
        void involveImports();

                    //QUERIES
    public:
        IQuery* registerQuery(IQuery* query, const QueryId& id);
        IQuery* getQuery(const QueryId& id);

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

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

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

    private:
        std::map<QueryId, IQuery*> __queries;
        std::multimap<std::string, Gringo::Symbol> __model;
        std::map<SymbolPacked, std::string> __indexSymbolNameHints;
        std::unordered_map<const CodeScope*, unsigned int> __indexScopes;
        std::vector<CodeScope*> __registryScopes;

                    //WARNINGS
                    //TODO move to separate provider/query 
    public:
        void addRuleWarning(const RuleWarning &rule);
        unsigned int registerWarning(std::string &&message);

    private:
        std::map<unsigned int, std::string> __warnings;
        void printWarnings(std::ostream& out);

                    //DEFAULT
    public:
        AST *ast;

        ClaspLayer();
        void run();
            
    private:
        std::ostringstream __partTags;
        std::ostringstream __partGeneral;

        bool handleSolution(Gringo::Model const &model);
    };

    template<class typ>
    struct ParseImplAtom {

        static typ get(const Gringo::Symbol& atom) {
            return atom.num();
        }
    };

    template<>
    struct ParseImplAtom<std::string> {
        static std::string get(const Gringo::Symbol& atom) {
            switch (atom.type()) {
                case Gringo::SymbolType::Str: return atom.string().c_str();
                case Gringo::SymbolType::Fun: return atom.name().c_str();
                
                default: break;
            }
                
            assert(false && "Inappropriate symbol type");
        }
    };

    template<>
    struct ParseImplAtom<SymbolPacked> {

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

    template<>
    struct ParseImplAtom<Gringo::Symbol> {

        static Gringo::Symbol get(const Gringo::Symbol& atom) {
            return atom;
        }
    };

    template<>
    struct ParseImplAtom<Expression> {

        static Expression get(const Gringo::Symbol& atom) {
            switch (atom.type()) {
                case Gringo::SymbolType::Num: return Expression(atom.num());
                case Gringo::SymbolType::Str: return Expression(std::string(atom.string().c_str()));
                
                case Gringo::SymbolType::Fun:
                {
                    //ID
                    if (!atom.args().size){
                        return Expression(std::string(atom.name().c_str()));
                    }
                    
                    //FUNC
                    Expression result(Operator::CALL,{Expression(std::string(atom.name().c_str()))});
                    for (const Gringo::Symbol& 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::SymSpan::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::Symbol 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::SymSpan::iterator arg) {
        }
    };

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

        return tup;
    }


}
#endif