// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: ffback.cpp 810 2011-09-03 15:21:54Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "ff_heuristic/ffback.h"
#include "system/task.h"
#include "system/system.h"
#include "system/state.h"
#include <iostream>
#include <cassert>

namespace ff {

    using namespace std;

    FFBackHeuristic::FFBackHeuristic(const Task* task, const Options* opts) :
	FFHeuristic(task, opts),
	solution(system)
    {}

    void FFBackHeuristic::init() {
	PtrSet<Location>& reached = system->getReachedLocations();
	for (uint32_t i = 0; i < system->getNrLocations(); i++) {
	    Location* loc = system->getLocations()[i];
	    loc->init();
	    if (reached.isMember(loc)) {
		loc->setActivationLevel(0);
	    }
	}
  	const vector<IntegerConstraint*>& constraints = system->getConstraints();
	for (uint32_t i = 0; i < constraints.size(); i++) {
	    constraints[i]->init();
	}
	const vector<Edge*>& edges = system->getEdges();
	for (uint32_t i = 0; i < edges.size(); i++) {
	    edges[i]->init();
	} 
    }

    void FFBackHeuristic::copyIntegerVars() {
	int32_t level = system->getLevel();
	assert(int32_t(solution.size()) == level);
	solution.appendLevel();
	for (uint32_t i = 0; i < system->getVariables().size(); i++) {
	    const Variable* v = system->getVariables()[i];
	    solution[level].raws.push_back(v->getRaw());
	}
    }

    void FFBackHeuristic::makeTarget(const Goal& goal) {
	for (uint32_t i = 0; i < goal.getLocations().size(); i++) {
	    solution.add(goal.getLocations()[i]);	    
	}
	const vector<IntegerConstraint*>& constraints = goal.getGuard().getConstraints();
	for (uint32_t i = 0; i < constraints.size(); i++) {
	    solution.add(constraints[i]);
	}
    }

    bool FFBackHeuristic::makeTarget(const Edge* edge, int32_t level) {
	if (solution[level].edges[edge->id]) {
	    assert(edge->getActivationLevel() <= level);
	    assert(edge->isSelected());
	    return false;
	} 
	selectedEdges++;
	solution[level].edges[edge->id] = true;	
	if (edge->isSelected()) {
	    return true;
	} 	
	edge->select();
	solution.add(edge->from);
	const vector<IntegerConstraint*>& constraints = edge->getGuard().getConstraints();
	for (uint32_t i = 0; i < constraints.size(); i++) {
	    solution.add(constraints[i]);
	}
	return true;
    }

    void FFBackHeuristic::processEdge(const Edge* edge, int32_t level) {
	if (edge->getType() == Edge::TAU) {
	    makeTarget(edge, level);
	    return;
	}
	for (uint32_t e = 0; e < edge->getSyncEdges().size(); e++) {
	    const Edge* se = edge->getSyncEdges()[e];
	    se = edge->getSyncEdges()[e];
	    if (0 <= se->getActivationLevel() && se->getActivationLevel() <= level) {
		bool counted1 = makeTarget(edge, level);
		bool counted2 = makeTarget(se, level);
		
		if (counted1 && counted2) {
		    selectedEdges--;
		}
		return;
	    }
	}
	// this is bad :-(
	assert(false);
    }
    
    void FFBackHeuristic::resolveLocation(const Location* loc, int32_t level) {
	// TODO: randomize
	for (uint32_t i = 0; i < loc->getIncomingEdges().size(); i++) {	    
	    const Edge* edge = loc->getIncomingEdges()[i];
	    
	    if (edge->getActivationLevel() == level && edge->from->getActivationLevel() <= level) {
		return processEdge(edge, level);		
	    }
	}
	// this should never be reachable
	assert(false);
    }

    bool FFBackHeuristic::checkAssignment(const IntegerAssignment* a, int32_t value, int32_t level) {
	return a->accept(this, value, level);
    }

    bool FFBackHeuristic::checkAssignment(const ConstAssignment* a, int32_t value, int32_t) {
	return a->rhs == value;
    }

    bool FFBackHeuristic::checkAssignment(const VarAssignment* a, int32_t value, int32_t level) {	
	assert(0 < level && level < int32_t(solution.size()));
	if (solution[level-1].raws[a->rhs->getID()] & (1UL << value)) {
	    solution.add(Supporter(a->rhs, value));
	    return true;
	}
	return false;
    }

    bool FFBackHeuristic::checkAssignment(const NullAssignment*, int32_t, int32_t) {
	// there should be no such assignment
	assert(false);
	return false;
    }

    void FFBackHeuristic::resolveSupporter(const Supporter& s, int32_t level) {
	assert(s.var);
	for (uint32_t i = 0; i < s.var->writeEdges.size(); i++) {
	    const Edge* edge = s.var->writeEdges[i];
	    if (0 <= edge->getActivationLevel() && edge->getActivationLevel() < level) {
		const vector<IntegerAssignment*>& assigns = edge->getAssignments();
		for (uint32_t j = 0; j < assigns.size(); j++) {
		    if (s.var->getID() == assigns[j]->getLhs()->getID() && 
			checkAssignment(assigns[j], s.value, level)) {
			processEdge(edge, level-1);
			return;
		    }
		}
	    }
	}
	assert(false);
    }
     
    int32_t FFBackHeuristic::compute(const State* state) {
	solution.clear();
	int32_t maxlevel = FFHeuristic::compute(state);
	if (maxlevel == INT_MAX) {
	    return INT_MAX;
	}
	selectedEdges = 0;
	makeTarget(system->getGoal());       
	
	for (uint32_t k = maxlevel; k > 0; k--) {
	    const Level& level = solution[k];
	    for (uint32_t i = 0; i < level.locations.size(); i++) {
		if (level.locations[i]) {
		    resolveLocation(system->getLocations()[i], k-1);
		}       
	    }
	    
	    for (uint32_t i = 0; i < level.supporters.size(); i++) {
	     	resolveSupporter(level.supporters[i], k);
	    }
	}

	assert(selectedEdges >= maxlevel);

	return selectedEdges;
    }
}
