// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: search_engine.cpp 815 2011-09-10 12:57:27Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "closed_list.h"
#include "heuristic.h"
#include "normalizer.h"
#include "open_list.h"
#include "search_engine.h"
#include "successor_generator.h"
#include "trace.h"
#include "transition.h"

#include "common/message.h"
#include "common/option.h"

#include "system/effect.h"
#include "system/process.h"
#include "system/state.h"
#include "system/system.h"
#include "system/target.h"
#include "system/task.h"
#include "system/variable.h"

#include "ul_heuristic/useless.h"

#include <cassert>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>

using namespace std;

SearchEngine::SearchEngine(const Task* task, const Options* opts) :
    task(task),
    opts(opts),
    goal(NULL),
    open(NULL),
    closed(NULL),
    heur(NULL),
    norm(NULL),
    stat(UNSAT),
    exploredStates(0),
    generatedStates(0)
{}

void SearchEngine::init(OpenList* o, ClosedList* c, Heuristic* h, Normalizer* n) {
    open = o;
    closed = c;
    heur = h;
    norm = n;
}

ostream& SearchEngine::printProgress(ostream& o) const {
    if (!opts->batch_mode && exploredStates % 20000 == 0) {
	o << "Explored states: " << setw(12) << exploredStates 
	     << " open: " << setw(8) << open->size()
	     << " close: " << setw(8) << closed->size() 
	     << "\r";
	o.flush();
    }
    return o;
}


void SearchEngine::prepareExploration(const SuccessorGenerator& succgen, State* initial) {
    assert(open);
    assert(closed);
        
    exploredStates = 0;
    generatedStates = 0;
    stat = UNSAT;
    
    succgen.delay(initial); 
    norm->normalize(initial);
    initial->zone().intern();
    
    if (opts->eval_initial) {
	cout << heur->eval(initial) << endl;
	exit(0);
    } else {
	open->insert(initial, 0, heur->eval(initial));
    }
}

void SearchEngine::explore(State* initial) {
    SuccessorGenerator succgen(task, opts, indep_size);   
    prepareExploration(succgen, initial);       
   
    vector<State*> succs;
    while (!open->empty()) {
	State* state = open->get();  
	
	++exploredStates;
	printProgress(std::cerr);
	if (task->target->isSatBy(state)) {
	    stat = SAT;
	    goal = state;
	    return;
	}
	
	if (closed->insert(state)) {
            continue;
        }	
	succs.clear();
 	succgen.generateSuccessors(state, succs);
	
	generatedStates += succs.size();
	for (uint32_t i = 0; i < succs.size(); i++) {

	    norm->normalize(succs[i]);
	    succs[i]->zone().intern();

	    if (!closed->contains(succs[i])) {
		insert(succs[i]);
	    } else {
	    	State::destroy(succs[i]);
	    }
 	}
    }
}

void SearchEngine::insert(State* succ) {
    open->insert(succ, 0, heur->eval(succ));
}

////////////////////////////////////////////////////////////////////////////////
 
void UTSearchEngine::init(OpenList* o, ClosedList* c, Heuristic* h, Normalizer* n) {
    open = o;
    closed = c;
    heur = h;
    useless = dynamic_cast<Useless*>(heur);
    assert(useless);
    norm = n;
}

void UTSearchEngine::insert(State* succ) {
    bool isuseless;
    useless->compute(succ, isuseless); 
    open->insert(succ, isuseless ? -1 : 0, heur->eval(succ));
}

////////////////////////////////////////////////////////////////////

UTIPSearchEngine::UTIPSearchEngine(const Task* task, const Options* opts) : 
    UTSearchEngine(task, opts),
    interference_level(task->system->builder->getField("interference level"))
{
     assert(interference_level != -1);
}

void UTIPSearchEngine::insert(State* succ) {
    bool isuseless;
    useless->compute(succ, isuseless); 
    int32_t key1 = succ->extra(interference_level);
    if (isuseless && succ->extra(interference_level) == -1) {
	key1 = -1;
    } else if (succ->extra(interference_level) == -1) {
	key1 = 0;
    }
    open->insert(succ, key1, heur->eval(succ));
}

////////////////////////////////////////////////////////////////////

IPSearchEngine::IPSearchEngine(const Task* task, const Options* opts) : 
    SearchEngine(task, opts),
    interference_level(task->system->builder->getField("interference level")) 
{
    assert(interference_level != -1);
}

void IPSearchEngine::insert(State* succ) {
    assert(succ->extra(interference_level) != 0 && succ->extra(interference_level) != INT_MAX);
    int32_t ip_val = succ->extra(interference_level) == -1 ? 0: succ->extra(interference_level);
    open->insert(succ, ip_val, heur->eval(succ));
}

////////////////////////////////////////////////////////////////////////////////

ostream& SearchEngine::report(ostream& o) const {    
    cerr << endl;
    cerr.flush();
    cout.flush();
    o << endl                
      << left << setw(30) << "Total explored states:  " << right << setw(9) << exploredStates << endl
      << left << setw(30) << "Total generated states: " << right << setw(9) << generatedStates << endl;
    printTrace(o);
    switch (stat) {
    case SAT:     o << left << setw(30) << "Property: " << right << setw(9) << "satisfied" << endl; break;
    case UNSAT:   o << left << setw(30) << "Property: " << right << setw(9) << "not satisfied" << endl; break;
    default:      o << left << setw(30) << "Property: " << right << setw(9) << "unknown" << endl; break;
    }
    debug() << *open << endl;
    return o;
}

void SearchEngine::generateTrace(Trace& trace, const State* goal) const {
    trace.prepend(goal);
    while (goal->predecessor) {
     	goal = goal->predecessor;
	trace.prepend(goal);
    }
} 

ostream& SearchEngine::printTrace(ostream& o) const {
    if (opts->trace == NO_TRACE || goal == NULL) {
	return o;
    }
    Trace trace(task->system);
    generateTrace(trace, goal);
    
    switch (opts->trace) {
    case STATISTIC_TRACE: 
	return o << left << setw(30) << "Trace length: " << right << setw(9) << trace.length() << endl;
    case SHOW_TRACE:     return o << trace << endl;
    case SYMBOLIC_TRACE: return trace.dump(o);
    default:             return o << endl;
    }
}

//////////////////////////////////////////////////////////////////////

GraphvizEngine::GraphvizEngine(const Task* task, const Options* opts) : 
    SearchEngine(task, opts), 
    diff_states(0),
    id(task->system->builder->getField("id")) 
{}

void GraphvizEngine::label(State* state, FILE* file) {
    state->extra(id) = diff_states++;
    fprintf(file, "S %d", state->extra(id));
    state->serialize(file);
    fprintf(file, "\n");
    if (state->extra(id) == 0) {
	fprintf(file, "I %d\n", state->extra(id)); 
    }	  
}

void GraphvizEngine::explore(State* initial) {
    SuccessorGenerator succgen(task, opts, indep_size);   

    exploredStates = 0;
    generatedStates = 0;
    stat = UNSAT;

    succgen.delay(initial); 
    
    if (opts->eval_initial) {
	cout << heur->eval(initial) << endl;
	exit(0);
    } else {

	norm->normalize(initial);
	initial->zone().intern();
	open->insert(initial, 0, heur->eval(initial));
    }


    vector<State*> succs;
    FILE* file = fopen(opts->state_space_filename.c_str(), "w");

    while (!open->empty()) {
	State* state = open->get();  
	++exploredStates;
	printProgress(std::cerr);
	
#ifndef NDEBUG	
	FILE* foo = fopen("foo", "w");
	state->serialize(foo);
	fclose(foo);
	ifstream in("foo"); 
	State* res = task->system->builder->newState(in);
	assert(*res == *state);
	State::destroy(res);
#endif
	
	const State* witness = closed->insert(state);	
	
	if (witness) {
	    assert(state->predecessor);	    
	    fprintf(file, "T %d %d\n", state->predecessor->extra(id), witness->extra(id));
            continue;
        } else {
	    label(state, file);
	    if (state->predecessor) {
		fprintf(file, "T %d %d\n", state->predecessor->extra(id), state->extra(id));
	    }
	}
	if (task->target->isSatBy(state)) {
	    if (goal == NULL) {
		stat = SAT;
		goal = state;
	    }
	    fprintf(file, "G %d\n", state->extra(id));
	    continue;
	}
	succs.clear();
 	succgen.generateSuccessors(state, succs);
	if (succs.empty()) {
	    fprintf(file, "D %d\n", state->extra(id));
	}
	generatedStates += succs.size();
	for (uint32_t i = 0; i < succs.size(); i++) {

	    norm->normalize(succs[i]);
	    succs[i]->zone().intern();

	    const State* witness = closed->contains(succs[i]);
	    if (!witness) {
		insert(succs[i]);		
	    } else {
	    	fprintf(file, "T %d %d\n", state->extra(id), witness->extra(id));
	    	State::destroy(succs[i]);
	    }
 	}
    }
    fclose(file);
}
    
