// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: option.cpp 839 2011-10-12 19:49:17Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "common/option.h"
#include "common/message.h"
#include <iostream>
#include <iomanip>
#include <inttypes.h>
#include <boost/program_options.hpp>

using namespace std;
namespace po = boost::program_options;

// this function is used by boost::program_options
template<class T>
static inline bool operator>>(std::istream& o, T& type) {
    int32_t dummy;
    o >> dummy;
    type = T(dummy);
    return true;
}

void Options::parseCmdLine(int argc, char** argv) {
    po::positional_options_description pd;
    // do not concat the following adds, this may cause compiler confusions
    pd.add("ta-file", 1);
    pd.add("q-file", 1);
    
    po::options_description generic("Generic options");
    generic.add_options()
	("help", "produce help message")  
	("quiet,q", "be quiet")
	("debug,d", "produce debug output")
	;
    
    po::options_description ceopts("Context-enhanced directed model checking options");
    ceopts.add_options()
	("ce", "use context-enhanced dmc")
	("ce-with-enabled,e", "use context-enhanced dmc with enabledness requirement")
	("closure", "use context-enhanced dmc to safely prune states")
	;

    po::options_description icbopts("Iterative Context Bounding options");
    icbopts.add_options()
	("icb", po::value<icb_opt>(&icb)->default_value(icb),
	 "    0 \tdo not use iterative context bounding\n"
	 "    1 \tuse process context\n"
	 "    2 \tuse interference context\n"
	 "    3 \tuse single context\n"
	 "    4 \tuse all context")
	("ce-dist", po::value<int32_t>(&ce_dist)->default_value(ce_dist),
	 "    sets the interference distance (use in combination with --icb=2)")
	;
    
    po::options_description misc("Miscellaneous options");
    misc.add_options()
	("new-syntax,n", "use Uppaal's new syntax")
	("no-memtime", "do not print memory and runtime statistics")
	("batch-mode,b", "print results in batch mode")
	("dump-state-space", po::value<string>(&state_space_filename), 
	 "dump the entire reachable state space")
	("eval-initial", "heuristically evaluate initial state and exit")
	;

    po::options_description traceopts("Trace options");
    traceopts.add_options()
	("trace,t", po::value<trace_opt>(&trace)->default_value(trace),
	 "for a found trace there are the following output functions:\n"
	 "    0 \tdo not print trace\n"
	 "    1 \tonly print trace statistics\n"
	 "    2 \tprint trace\n"
         "    3 \tprint Uppaal trace")
	;

    po::options_description searchopts("Search order");
    searchopts.add_options()
	("order,o", po::value<search_opt>(&search)->default_value(search),
	 "    0 \tbreadth first search\n"
	 "    1 \tdepth first search\n"
	 "    2 \tgreedy search\n"
	 "    3 \tA* search\n"
	 "    4 \tUT search")
	;

    po::options_description heuristics("Heuristic options");
    heuristics.add_options()
	("heur,h", po::value<heuristic_opt>(&heuristic)->default_value(heuristic),
	 "to guide the search quickly towards error states, choose one of the following heuristics:\n"
	 "    0 \tno heuristic\n"
	 "    1 \tdu (graph distance sum)\n"
	 "    2 \tdl (graph distance max)\n"
	 "    3 \thl heuristic\n"
	 "    4 \tpdb heuristic\n"
	 "    5 \thu heuristic\n"
	 "    6 \th-add heuristic\n")
	("pdb", po::value<vector<string> >(&pdb_filenames)->composing(),
	 "white space separated list of PDB files (-h4)")
	;
    
    heuristics.add_options()
	("cache,c", po::value<cache_opt>(&cache)->default_value(cache),
	 "to avoid the recomputation of heuristic values, choose one of the following caches:\n"
	 "    0 \tdo not cache heuristic values\n"
	 "    1 \tbitstate cache heuristic values (based on the discrete part of states only)\n"
	 "    2 \tonly use discrete part for caching\n"
	 "    3 \tuse whole state for caching") 
	;

    po::options_description hidden("Hidden options");
    hidden.add_options()
	("ta-file", po::value<string>(&ta_file), ".xta, .ta, .xml file. Description of the timed automata system")
	("q-file", po::value<string>(&q_file), "the query file (.q file)") 
	; 

    po::options_description cmdline_options;
    po::options_description visible;
    visible.
	add(searchopts).
	add(heuristics).
	add(ceopts).
	add(icbopts).
	add(traceopts).
	add(generic).
	add(misc);    
    cmdline_options.add(visible).add(hidden);
        
    po::variables_map vm;
    if (argc == 1) {
	error() << "Not enough arguments. Try '" << argv[0] << " --help' for more information." << endl;
    }     
    try {
	po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(pd).run(), vm);
    } catch (po::error& e) {
	error() << e.what() << endl << "Try '" << argv[0] << " --help' for more information." << endl;
    }
    po::notify(vm);
    if (vm.count("help")) {
	cout << "Usage: " << argv[0] << " [options] <system> <query>" << endl
	     << "where <system> is a timed automata system file and" << endl
	     << "<query> is a query file. Both files must be provided" << endl
	     << "in the Uppaal input language" << endl 
	     << endl
	     << visible << endl;
	exit(EXIT_SUCCESS);
    }
    
    if (vm.count("no-memtime")) {
	memtime = false; 
    }	
    if (vm.count("batch-mode")) {
	batch_mode = true;
	trace = STATISTIC_TRACE;
    }
    if (vm.count("quiet")) {
	quiet = true;	
    }
    if (vm.count("new-syntax")) {
	new_syntax = true;
    }
    if (vm.count("eval-initial")) {
	eval_initial = true;
    }
    if (vm.count("debug")) {
	debug_mode = true;
    }
    if (vm.count("ce-with-enabled")) {
	ce = true;
	ce_with_enabled = true;
    }
    if (vm.count("ce")) {
	ce = true;
    }
    if (vm.count("closure")) {
	ce = true;
	ce_with_enabled = false;
	ce_closure = true;
    }
    if (vm.count("dump-state-space")) {
	dump_state_space = true;
    }
}

namespace output {

    // Note: These functions are in a separate namespace, because
    // program_options would otherwise use them to print the usage
    // screen."

    static inline std::ostream& operator<<(std::ostream& o, heuristic_opt opt) {
	switch (opt) {
	case NULL_HEURISTIC:             return o << "null";
	case DU_HEURISTIC:               return o << "du (graph distance sum)";
	case DL_HEURISTIC:               return o << "dl (graph distance max)";
	case HL_HEURISTIC:               return o << "hl heuristic";
	case PDB_HEURISTIC:              return o << "pdb heuristic";
	case HU_HEURISTIC:               return o << "hu heuristic";
	case H_ADD_HEURISTIC:            return o << "h-add heuristic";
	default:                         return o << "NO SUCH HEURISTIC";
	}
    }

    static inline std::ostream& operator<<(std::ostream& o, cache_opt opt) {
	switch (opt) {
	case NO_CACHE :         return o << "no";
	case BITSTATE_CACHE:    return o << "bitstate";
	case DISCRETE_CACHE:    return o << "discrete part only";
	case WHOLE_STATE_CACHE: return o << "whole state";
	default:                return o << "NO SUCH OPTION";
	}
    }

    static inline std::ostream& operator<<(std::ostream& o, icb_opt opt) {
	switch (opt) {
	case ICB_NO:           return o << "none";
	case ICB_PROC:         return o << "process context";
	case ICB_INTERFERENCE: return o << "interference context";
	case ICB_SINGLE:       return o << "single context";
	case ICB_ALL:          return o << "all context";
	default:               return o << "NO SUCH OPTION";
	}
    }
    
    static inline std::ostream& operator<<(std::ostream& o, search_opt opt) {
	switch (opt) {
	case BFS_SEARCH:    return o << "breadth first";
	case DFS_SEARCH:    return o << "depth first";
	case GREEDY_SEARCH: return o << "greedy";
	case A_STAR_SEARCH: return o << "A*";
	case UT_SEARCH:     return o << "UT";
	default:            return o << "NO SUCH OPTION";
	}
    }
    
    static inline std::ostream& operator<<(std::ostream& o, trace_opt opt) {
	switch (opt) {
	case NO_TRACE:        return o << "no";
	case STATISTIC_TRACE: return o << "statistic";
	case SHOW_TRACE:      return o << "normal";
	case SYMBOLIC_TRACE:  return o << "symbolic";
	default:              return o << "NO SUCH OPTION";
	} 
    }
}

std::ostream& operator<<(std::ostream& o, const Options& opts) {
    using namespace output;
    const uint32_t width1 = 20;
    const uint32_t width2 = 12;
    
    if (opts.batch_mode) {
	o << "Input files:" << endl
	  << setw(width1) << left << "  System file:" << setw(width2) << opts.ta_file << endl
	  << setw(width1) << left << "  Query file:" << setw(width2) << opts.q_file << endl;
    }

    o << "Selected Options:" << endl;
    if (opts.icb != ICB_NO) {
	o << setw(width1) << left << "  ICB:" << setw(width2) << opts.icb;
	if (opts.icb == ICB_INTERFERENCE) {
	    o << ", interference distance: " << opts.ce_dist << endl;
	} else {
	    o << endl;
	}
    }

    o << setw(width1) << left << "  Search order:" << setw(width2) << opts.search << endl;
    if (opts.ce) {
	o << setw(width1) << left << "  CE:" << setw(width2);
	if (opts.ce_closure) {
	    o << setw(width2) << "complete closure" << endl;
	} else if (opts.ce_with_enabled) {
	    o << setw(width2) << "eager + all relevant enabled" << endl;
	} else {
	    o << setw(width2) << "eager" << endl;
	}
    }
    if (opts.search != DFS_SEARCH && opts.search != BFS_SEARCH) {
	o << setw(width1) << left << "  Cache:"        << setw(width2) << opts.cache << endl; 
	o << setw(width1) << left << "  Heuristic:"    << setw(width2) << opts.heuristic << endl;
    }
    return o;
}

