/*
 * adhoc.cpp
 *
 *  Created on: Dec 1, 2015
 *      Author: pgess
 */

#include <query/context.h>
#include <boost/range/iterator_range_core.hpp>
#include <boost/iterator/transform_iterator.hpp>

using namespace std;
namespace xreate {

const Domain domainEmpty;
const Decisions decisionsEmpty;
const FunctionDemand functionDemandEmpty;

ContextQuery::ContextQuery(){}


const Domain&
ContextQuery::getContext(const ScopePacked& scopeId) const{
	if (!__modelContext.count(scopeId)){
		return domainEmpty;
	}

	return __modelContext.at(scopeId);
}

const Domain&
ContextQuery::getContext(CodeScope* const scope) const{
	return getContext(clasp->pack(scope));
}

//DEBT compatibility of forced context with context resolution for interpretation
void
ContextQuery::forceContext(const ScopePacked& scopeId, std::list<Expression> context){
	// TODO remove forced context of the same class/type only, possibly
        //remove any previous forced context for this scopeId
	//__modelForcedContext.erase(scopeId);

	//TASK restore forceContext
		//std::multimap<ScopePacked, Expression> __modelForcedContext;
		/*
	std::transform(context.begin(), context.end(), inserter(__modelForcedContext, __modelForcedContext.end()),
			[scopeId](const Expression& context){return make_pair(scopeId, context);});
		*/
}

void
ContextQuery::init(ClaspLayer* clasp){
	const std::string& atomBinding = Config::get("clasp.bindings.scope");

	this->clasp = clasp;
	ClaspLayer::ModelFragment query = clasp->query(atomBinding);

        //static context
	if (query){
            map<ScopePacked, vector<Expression>> dictContext;

            for (auto i = query->first; i!=query->second; ++i){
                    ScopePacked idScope;
                    Expression context;
                    std::string link;
                    tie(idScope, context, link) = ClaspLayer::parse<ScopePacked, Expression, string>(i->second);
                    if (link == "strong") {
                        dictContext[idScope].push_back(context);
                    }
            }

            for (map<ScopePacked, vector<Expression>>::value_type& entry: dictContext){
                    __modelContext.insert(move(entry));
            }
	}

	prepareFunctionDemandModel();
        prepareDecisionModels();
}



void
ContextQuery::prepareFunctionDemandModel(){
	const std::string& atomFunctionDemand = Config::get("clasp.bindings.function_demand");

	ClaspLayer::ModelFragment query = clasp->query(atomFunctionDemand);
	if (query)
	for (auto i = query->first; i!=query->second; ++i){
		string function;
		Expression topic;
		tie(function, topic) = ClaspLayer::parse<string, Expression>(i->second);

		FunctionDemand& demand = __modelFunctionDemand[function];
		demand.left.insert(make_pair(demand.left.size(), topic));
	}
}

void
ContextQuery::prepareDecisionModels(){
    const std::string& atomDecision = Config::get("clasp.bindings.scope_decision");
    const std::string& atomDependentDecision = Config::get("clasp.context.decisions.dependent");

    std::multimap<Expression, Expression> modelDomains;


    ClaspLayer::ModelFragment query = clasp->query(atomDecision);
    if (query){
        for (auto i = query->first; i!=query->second; ++i){
            ScopePacked scopeId;
            Expression topic;
            Expression decision;

            std::tie(scopeId, topic, decision) = ClaspLayer::parse<ScopePacked, Expression, Expression>(i->second);

            if (decision.getValueString() == atomDependentDecision)  {
                assert(decision.operands.size() == 2);

                const Expression& decisionGuard = decision.operands[1];
                const Expression& decisionValue = decision.operands[0];

                __modelDependentDecisions[scopeId][topic].emplace(decisionGuard, decisionValue);
                modelDomains.emplace(topic, decisionValue);

            } else {
                Decisions& decisionsOfScope = __modelStaticDecisions[scopeId];
                assert(decisionsOfScope.emplace(topic, decision).second && "Possibly more than one decision");
                modelDomains.emplace(topic, decision);
            }
	}
    }


    //populate topic domains:
    auto adapter = [](const std::pair<const Expression&,const Expression>& p){ return p.second; };

    auto iBegin = modelDomains.begin();
    while (iBegin!=modelDomains.end()){
            const Expression topic = iBegin->first;
            auto iEnd = modelDomains.upper_bound(topic);
            auto iBeginAdapted = boost::make_transform_iterator(iBegin,adapter);
            auto iEndAdapted = boost::make_transform_iterator(iEnd,adapter);
            Domain dom(iBeginAdapted, iEndAdapted);
            __modelTopicDomains.emplace(topic, move(dom));
            iBegin = iEnd;
    }
}

const FunctionDemand&
ContextQuery::getFunctionDemand(const std::string& name) const {
	if (__modelFunctionDemand.count(name)){
		return __modelFunctionDemand.at(name);
	}

	return functionDemandEmpty;
}

const Decisions&
ContextQuery::getFinalDecisions(const ScopePacked& scopeId) const{
	if (__modelStaticDecisions.count(scopeId)){
		return __modelStaticDecisions.at(scopeId);
	}

	return decisionsEmpty;
}

const Domain&
ContextQuery::getTopicDomain(const Expression& topic) const{
    if (__modelTopicDomains.count(topic)){
        return __modelTopicDomains.at(topic);
    }

    return  domainEmpty;
}

const DependentDecision&
ContextQuery::getDependentDecision(ScopePacked scope, const Expression& topic) const{
    auto itDecisionsAllTopics = __modelDependentDecisions.find(scope);
    if (itDecisionsAllTopics != __modelDependentDecisions.end()){
        auto itDecisions = itDecisionsAllTopics->second.find(topic);

        if (itDecisions != itDecisionsAllTopics->second.end()){
            return itDecisions->second;
        }
    }

    return decisionsEmpty;
}

//	const std::string& atomLateBinding = Config::get("clasp.bindings.function_uncertain");
//	query = clasp->query(atomLateBinding);
//
//	std::map<std::string, std::vector<Expression>> dictFunctionDomain;
//	if (query){
//		for (auto i = query->first; i!=query->second; ++i){
//			string nameFunction;
//			Expression context;
//			tie(nameFunction, context) = ClaspLayer::parse<ScopePacked, Expression>(i->second);
//			dictFunctionDomain.at(nameFunction).push_back(context);
//		}
//
//		for(auto& entry: dictFunctionDomain){
//			__modelFunctionDomain.emplace(entry.first, move(entry.second));
//		}
//	}

} /* namespace xreate */
