// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: mcta.cpp 843 2011-10-13 21:52:49Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "system/parser.h"
#include "system/system.h"
#include "system/target.h"
#include "system/task.h"

#include "search/cache.h"
#include "search/closed_list.h"
#include "search/context.h"
#include "search/normalizer.h"
#include "search/open_list.h"
#include "search/search_engine.h"

#include "ff_heuristic/ffadd.h"
#include "ff_heuristic/ffback.h"
#include "ff_heuristic/ffheur.h"
#include "gd_heuristic/gd.h"
#include "pdb_heuristic/pdb_heuristic.h"
#include "ul_heuristic/ffuheur.h"
#include "ul_heuristic/gdu.h"

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

#include <cassert>
#include <iomanip>
#include <iostream>
#include <string>

using namespace std;

Heuristic* newHeuristic(const Task* task, const Options* options) {
    if (options->search == UT_SEARCH) {
	switch (options->heuristic) {
	case DU_HEURISTIC:         return new GDSUMUselessHeuristic(task, options);	
	case DL_HEURISTIC:         return new GDMAXUselessHeuristic(task, options);	
	case HL_HEURISTIC:         return new ff::FFUselessHeuristic(task, options);
	case HU_HEURISTIC:         return new ff::FFBackUselessHeuristic(task, options);
	case H_ADD_HEURISTIC:      return new ff::FFAddUselessHeuristic(task, options); 
	default:                       
	    error() << "UT search option not supported with selected heuristic!" << endl;
	    return NULL;
	} 	
    } else {
	switch (options->heuristic) {
	case NULL_HEURISTIC:           return new NullHeuristic(task, options);
	case DU_HEURISTIC:             return new GDSUMHeuristic(task, options);
	case DL_HEURISTIC:             return new GDMAXHeuristic(task, options);	
	case HL_HEURISTIC:             return new ff::FFHeuristic(task, options);
	case PDB_HEURISTIC:            return new PDBHeuristic(task, options); 
	case HU_HEURISTIC:             return new ff::FFBackHeuristic(task, options);	
	case H_ADD_HEURISTIC:          return new ff::FFAddHeuristic(task, options); 
	default:                       
	    error() << "No heuristic selected!" << endl;
	    return new NullHeuristic(task, options);
	}
    }
}

Cache* newCache(const Options* options) {
    // TODO: the initial size of the cache should also become a
    // cmdline option
    const uint32_t size = 256000;
    switch (options->cache) {
    case BITSTATE_CACHE:    return new BitstateCache(size);
    case DISCRETE_CACHE:    return new DiscretePartCache(size);
    case WHOLE_STATE_CACHE: return new WholeStateCache(size);
    default: return new NoCache(size);
    }
}


OpenList* newOpenList(const Options* options) {
    OpenList* open = NULL;
    switch (options->search) {
    case DFS_SEARCH: 
	open = new StackList; 
	break;
    case A_STAR_SEARCH:
    case GREEDY_SEARCH: 
	open = new PriorityQueue;
	break;
    case UT_SEARCH:
	if (options->ce) {
	    return new UTIPQueue;
	} else {
	    return new UTQueue;
	}
    case BFS_SEARCH: 
	open = new QueueList;
	break;
    default: 
	error() << "no open list initialized!" << endl;
    }
    
    if (options->ce) {
	return new MultiQueue(open);
    }
    return open;
}

SearchEngine* newSearchEngine(const Options* options, const Task* task) {
    SearchEngine* searchEngine = NULL;
    OpenList* open = newOpenList(options);

    Heuristic* heuristic = NULL;
    Normalizer* normalizer = new LocalLUNormalizer(task->system);

    if (options->ce || options->icb == ICB_INTERFERENCE) {
	task->system->builder->addField("interference level");
    }
        
    if (options->icb != ICB_NO) {
	task->system->builder->addField("context switches");	
	searchEngine = new ContextSearchEngine(task, options);
    } else if (options->ce && options->search == UT_SEARCH) {
	searchEngine = new UTIPSearchEngine(task, options);
    } else if (options->ce) {
	searchEngine = new IPSearchEngine(task, options);
    } else if (options->search == UT_SEARCH) {
	searchEngine = new UTSearchEngine(task, options);
    } else if (options->dump_state_space) {
	task->system->builder->addField("id");
	searchEngine = new GraphvizEngine(task, options);
    } else {   
	searchEngine = new SearchEngine(task, options);
    }
    
    switch (options->search) {
    case GREEDY_SEARCH:
    case UT_SEARCH:
	heuristic = newHeuristic(task, options);
	heuristic->cache = newCache(options);
	break;
    case A_STAR_SEARCH:
	task->system->builder->addField("depth");
	heuristic = newHeuristic(task, options);
	heuristic = new AStarHeuristic(task, options, heuristic);
	heuristic->cache = newCache(options);
	break;
    case DFS_SEARCH:
    case BFS_SEARCH: 
	heuristic = new NullHeuristic(task, options);
	break;
    default:
	error() << "no heuristic initialized!" << endl;
	break;
    }

    ClosedList* closed = NULL;
    
    if (options->search == A_STAR_SEARCH && !options->dump_state_space) {
	closed = new AStarClosedList(task->system->builder->getField("depth"));
    } else {
	closed = new ClosedList;
    }
    
    searchEngine->init(open, closed, heuristic, normalizer);
    return searchEngine;
}


int main(int argc, char** argv) {
    Options* options = new Options(argc, argv);
    
    if (options->memtime) {       
	MemStat::getMemStat();
    }

    debug().setSilent(!options->debug_mode);

    if (options->batch_mode) {
	options->trace = STATISTIC_TRACE;
    }
        
    if (!options->quiet && !options->batch_mode) {
	cout << "Mcta - version 2011.10, Copyright (C)" << endl
	     << "Sebastian Kupferschmid and Martin Wehrle" << endl << endl
	     << "Mcta is free software and distributed under" << endl
	     << "the terms of the GNU General Public License" << endl
	     << "as published by the Free Software Foundation." << endl << endl;
    } 
    cout << *options << endl;
     
    Task* task = SystemParser::parser()(options->ta_file, options->q_file, !options->new_syntax);      
    SearchEngine* engine = newSearchEngine(options, task);
    
    engine->explore(task->system->createInitial());
    engine->report(cout);
}

