/*
 * 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/.
 * 
 * \file   latereasoning.h
 * \brief  Late reasoning support
 * 
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created on June 2, 2018, 1:08 PM
 */

#ifndef LATEREASONING_H
#define LATEREASONING_H

#include "transcendlayer.h"

namespace xreate {
    struct LateBindingT;

    template<>
    struct AttachmentsDict<LateBindingT>{
        typedef Expression Data;
        static const unsigned int key = 12;
    };
}

namespace xreate{ namespace latereasoning{

struct LateAnnotation{
    std::list<std::pair<std::list<Expression>, Gringo::Symbol>> guardedSymbols;
    boost::optional<Gringo::Symbol> select(const std::list<Expression>& keys) const;
};

struct LateAnnotationsGroup{
    std::unordered_map<SymbolNode, LateAnnotation> annotations;
    std::unordered_map<SymbolNode, std::list<SymbolPacked>> bindings;
};

//DEBT implement querying as a view/joining iterators without actual data copying
//DEBT how to implement late model RESET(invalidate all child models)
template<class Parent>
class LateReasoningTranscendDecorator: public Parent{
public:
    StaticModel queryLate(const std::string& alias, const SymbolNode& symbol) const{
        StaticModel result;
        if(!__modelLate.count(alias)) return StaticModel();

        const LateAnnotationsGroup& group = __modelLate.at(alias);
        assert(group.bindings.count(symbol));
        const std::list<SymbolPacked>& bindings = group.bindings.at(symbol);
        std::list<Expression>&& keys = findKeys(bindings);

        const LateAnnotation& annLate = group.annotations.at(symbol);
        auto ann = annLate.select(keys);
        if(ann){
            result.emplace(std::make_pair(alias, *ann));
        }

        return result;
    }
    
protected:    
    virtual bool processSolution(Gringo::Model const &model) override{
        const std::string& atomLateStatement = "late";

        for(const Gringo::Symbol& atom: model.atoms(clingo_show_type_atoms)){
            std::string atomName(atom.name().c_str());

            SymbolPacked aaaa;
            if(atomName == atomLateStatement){
                //late atom's format: (Symbol, (tuple of keys), (tuple of values), late-annotation)
                auto atomLate = TranscendLayer::parse<SymbolPacked, std::list<SymbolPacked>, std::list<Expression>, Gringo::Symbol>(atom);
                const std::string& atomAlias = std::get<3>(atomLate).name().c_str();
                addLateAtom(atomAlias, std::get<0>(atomLate),
                            std::get<3>(atomLate), std::get<1>(atomLate),
                            std::get<2>(atomLate));
            }
        }
        
        return Parent::processSolution(model);
    }

private:
    std::map<std::string, LateAnnotationsGroup> __modelLate;
    
    void addLateAtom(const std::string& alias, const SymbolNode& symbol,
                const Gringo::Symbol& atom, const std::list<SymbolPacked>& guardKeys,
                const std::list<Expression>& guardBindings){
        LateAnnotationsGroup& group = __modelLate[alias];
        if(!group.bindings.count(symbol)){
            group.bindings.emplace(symbol, guardKeys);
        }

        LateAnnotation& annotation = group.annotations[symbol];
        annotation.guardedSymbols.push_back(std::make_pair(guardBindings, atom));
    }

    std::list<Expression> findKeys(const std::list<SymbolPacked>& keys) const{
        std::list<Expression> result;
        std::transform(keys.begin(), keys.end(), std::inserter(result, result.end()), [this](const SymbolPacked & key){
            return Attachments::get<LateBindingT>(Parent::unpack(key));
        });

        return result;
    }
};

}} // end of xreate::latereasoning

#endif /* LATEREASONING_H */

