/* 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 ASTSitePacked {
  union {
    ASTSite site;
    SymbolPacked symbol;
  };
  enum { SYMBOL, ANON } tag;

  ASTSitePacked(){}
  explicit ASTSitePacked(const ASTSite& data): site(data), tag(ASTSitePacked::ANON) {}
  explicit ASTSitePacked(const SymbolPacked& data): symbol(data), tag(ASTSitePacked::SYMBOL){}
  std::string str() const;
};

//bool operator==(const Site& s1, const Site& s2);

//template<>
//struct AttachmentsId<Site>{
//    static unsigned int getId(const Site& symbol);
//};
    
} namespace std {
  
//    template<>
//    struct hash<xreate::SymbolNode> {
//        std::size_t operator()(xreate::SymbolNode const& s) const noexcept;
//    };
//
//    template<>
//    struct hash<xreate::Site> {
//        std::size_t operator()(xreate::Site 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,
  DemandQuery
};

namespace dfa{
  class DFAGraph;
}

namespace cfa {
  class CFAGraph;
}

template<typename Tuple> struct schemeT{};
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 appendImport();
///@}                

                    /**\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);

    template<class ...Types>
    static std::tuple<Types...> parse(schemeT<std::tuple<Types...>>, const Gringo::Symbol& atom);
    template<class Ret>
    static Ret parseAtom(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 Site& symbol, const std::string& hintSymbolName);
//    Site unpack(const SymbolNode& symbol) const;
    std::string getHintForPackedSymbol(const SymbolPacked& symbol);
    ASTSitePacked pack(const ASTSite& s);
    ASTSite unpack(const ASTSitePacked& s);
///@}
    
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<Gringo::Symbol> {
    static Gringo::Symbol get(const Gringo::Symbol& atom);
};

template<>
struct ParseImplAtom<ASTSitePacked> {
  static ASTSitePacked 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;
}

template<class ...Types>
std::tuple<Types...>
TranscendLayer::parse(schemeT<std::tuple<Types...>>, const Gringo::Symbol& atom){
  return parse<Types...>(atom);
}

template<class Ret>
Ret
TranscendLayer::parseAtom(const Gringo::Symbol& atom){
  return ParseImplAtom<Ret>::get(atom);
}


} //end of xreate namespace
#endif
