/* 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: transcendlayer.h
 */ 

#ifndef transcendLAYER_H
#define transcendLAYER_H

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

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

namespace xreate {

typedef unsigned int ScopePacked;
const ScopePacked SCOPE_ABSTRACT_GLOBAL = std::numeric_limits<ScopePacked>::max();

struct SymbolPacked {
    SymbolPacked(){}
    SymbolPacked(ScopedSymbol i, ScopePacked s): identifier(i.id), version(i.version), scope(s){}
    SymbolPacked(VNameId symbolId, versions::VariableVersion symbolVersion, ScopePacked symbolScope)
        : identifier(symbolId), version(symbolVersion), scope(symbolScope){}
    
    VNameId identifier;
    versions::VariableVersion version;
    ScopePacked scope;
};


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

struct SymbolAnonymous {
    unsigned int id;
    bool flagIsUsed = false;
};

bool operator==(const SymbolAnonymous& s1, const SymbolAnonymous& s2);

typedef boost::variant<SymbolAnonymous, SymbolPacked> SymbolNode;

//DEBT use SymbolGeneralized communicating with Analysis rather than Symbol
typedef boost::variant<SymbolAnonymous, Symbol> SymbolGeneralized;

template<>
struct AttachmentsId<SymbolGeneralized>{
    static unsigned int getId(const SymbolGeneralized& symbol);
};
    
} namespace std {
  
    template<>
    struct hash<xreate::SymbolNode> {
        std::size_t operator()(xreate::SymbolNode const& s) const noexcept;
    };

    template<>
    struct hash<xreate::SymbolGeneralized> {
        std::size_t operator()(xreate::SymbolGeneralized const& s) const noexcept;
    };
}

namespace xreate { 

enum class DFGConnection {
    STRONG, WEAK, PROTOTYPE
};

/** \brief Supplies \ref TranscendLayer with results of an analysis*/
class IAnalysisReport {
public:
    /** \brief Composes a logic program to represent the analysis data in ASP format, and appends to a stream*/
    virtual void print(std::ostringstream& output) const = 0;
    virtual ~IAnalysisReport(){};
};

/** \brief Transcend solutions querying interface */
class IQuery {
public:
  virtual void init(TranscendLayer* transcend) = 0;
  virtual ~IQuery() {}
};

enum class QueryId {
  ContainersQuery,
  PolymorphQuery,
  LatexQuery
};

namespace dfa{
  class DFAGraph;
}

namespace cfa {
  class CFAGraph;
}

typedef std::multimap<std::string, Gringo::Symbol> StaticModel;
typedef StaticModel::const_iterator StaticModelIterator;

class TranscendLayer {
friend class ContextRule;

                    /**\name Data Providers Management */
///@{
public:   
    void registerReport(IAnalysisReport* report);
    void printReports();
    void deleteReports();
    
    /** \brief Appends arbitrary string to a logic program
     */
    void addRawScript(std::string&& script);

private: 
    std::list<IAnalysisReport*> __reports;
    
    /** Includes external text files to a *logic program* */    
    void involveImports();
///@}                

                    /**\name Queries Management */
///@{    
public:
    /** \brief Registers a query. See xreate::IQuery  */
    IQuery* registerQuery(IQuery* query, const QueryId& id);
    
    /** \brief Returns a particular query. See xreate::IQuery */
    IQuery* getQuery(const QueryId& id);
    
    template<class ...Types>
    static std::tuple<Types...> parse(const Gringo::Symbol& atom);
    StaticModel query(const std::string& atom) const;
    size_t getScopesCount() const;
    SymbolPacked pack(const Symbol& symbol, std::string hintSymbolName = "");
    ScopePacked pack(const CodeScope * const scope);
    Symbol unpack(const SymbolPacked& symbol) const;
    SymbolNode pack(const SymbolGeneralized& symbol, const std::string& hintSymbolName);
    SymbolGeneralized unpack(const SymbolNode& symbol) const;
    std::string getHintForPackedSymbol(const SymbolPacked& symbol);
///@}
    
private:
    std::map<QueryId, IQuery*> __queries;
    std::map<SymbolPacked, std::string> __indexSymbolNameHints;
    std::unordered_map<const CodeScope*, unsigned int> __indexScopes;
    std::vector<const CodeScope*> __registryScopes;

                    /**\name Diagnostic */    
///@{
//TODO diagnostic move over to separate provider/query 
public:
    /** \brief Registers diagnostic rules  */
    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:
    TranscendLayer();
    
    /** \brief Executes reasoning */
    void run();
///@}
    AST *ast;
    
protected:
    virtual bool processSolution(Gringo::Model const &model);
    
private:
    StaticModel __model;
    std::ostringstream __partTags;
    std::ostringstream __partGeneral;
};

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

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

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<SymbolNode> {
    static SymbolNode get(const Gringo::Symbol& atom);
};

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

template<class ItemType>
struct ParseImplAtom<std::list<ItemType>>{
    static std::list<ItemType> 
    get(const Gringo::Symbol& atom){
        bool flagIsList = (atom.type() == Gringo::SymbolType::Fun) && atom.name().empty();
        std::list<ItemType> result;

        if(!flagIsList) {
            //treat as degenerate case: list with a single element
            result.push_back(ParseImplAtom<ItemType>::get(atom));
            return result;
        }
        
        for (const Gringo::Symbol& arg: atom.args()) {
            result.push_back(ParseImplAtom<ItemType>::get(arg));
        }

        return result;
    }
};

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...>
TranscendLayer::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
