/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * Author: pgess <v.melnychenko@xreate.org>
 * File: clasplayer.h
 */ 

#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;
    versions::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, versions::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
};

/** \brief Designated to mark analysis results that can be composed as *logic program* */
class IAnalysisReport {
public:
    /** \brief Composes *logic program* based on analysis data into ASP format and appends to a stream*/
    void print(std::ostringstream& output) const;
    virtual ~IAnalysisReport(){};
};

/** \brief Logic program query interface */
class IQuery {
public:
    virtual void init(ClaspLayer* clasp) = 0;
    virtual ~IQuery() {}
};

enum class QueryId {
    ContainersQuery,
    ContextQuery,
    PtrvalidQuery
};

namespace dfa{
    class DFAGraph;
}

namespace cfa {
    class CFAGraph;
}

class ClaspLayer {
    friend class ContextRule;

                    /**\name Data Providers Management */
///@{
public:   
    /** \brief Provides DFA graph as output from xreate::DFAPass */
    void setDFAData(xreate::dfa::DFAGraph* graph);

    /** \brief Provides CFA graph as output from xreate::CFAPass */
    void setCFAData(xreate::cfa::CFAGraph* graph);

    /** \brief Appends arbitrary string to *logic program 
     */
    void addRawScript(std::string&& script);

    boost::scoped_ptr<xreate::dfa::DFAGraph> dataDFA;
    boost::scoped_ptr<xreate::cfa::CFAGraph> dataCFA;
    
private: 
    /** Includes external text files to a *logic program* */    
    void involveImports();
///@}                

                    /**\name Queries Management */
///@{    
public:
    /** \brief Adds query. See xreate::IQuery  */
    IQuery* registerQuery(IQuery* query, const QueryId& id);
    
    /** \brief Returns particular query. See xreate::IQuery */
    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;

                    /**\name Diagnostic */    
///@{
//TODO diagnostic move over to separate provider/query 
public:
    /** \brief Adds diagnostic rule  */
    void addRuleWarning(const RuleWarning &rule);
    
    /** \brief Registers diagnostic messages  */
    unsigned int registerWarning(std::string &&message);

private:
    std::map<unsigned int, std::string> __warnings;
    void printWarnings(std::ostream& out);
///@}
    
///@{
public:
    ClaspLayer();
    
    /** \brief Executes reasoning */
    void run();
///@}
    AST *ast;
    
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);
};

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

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

template<>
struct ParseImplAtom<std::list<Gringo::Symbol>>{
    static std::list<Gringo::Symbol> get(const Gringo::Symbol& atom);
};

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

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;
}
} //end of xreate namespace
#endif
