// -*- Mode: C++; tab-width: 2; -*-
// vi: set ts=2:
//

#include <BALL/FORMAT/commandlineParser.h>
#include <BALL/QSAR/registry.h>
#include <BALL/QSAR/featureSelection.h>
#include <BALL/QSAR/configIO.h>
#include <fstream>
#include "version.h"

using namespace BALL::QSAR;
using namespace BALL;
using namespace std;


void startFeatureSelection(FeatureSelectionConfiguration& conf, QSARData* q, String* data_filename);


void startFeatureSelection(ifstream& in, QSARData* q, String* data_filename)
{
	FeatureSelectionConfiguration conf = ConfigIO::readFeatureSelectionConfiguration(&in);
	if(conf.done) return; // stop processing this section

	startFeatureSelection(conf, q, data_filename);
}


void startFeatureSelection(FeatureSelectionConfiguration& conf, QSARData* q, String* data_filename)
{
	bool created_data_object=0;
	if(q==NULL || data_filename==NULL || conf.data_file!=*data_filename)
	{
		if(q==NULL)
		{
			q = new QSARData;
			created_data_object=1;
		}
		q->readFromFile(conf.data_file);
		if(data_filename) *data_filename = conf.data_file;
	}
	else
	{
		Log.level(2)<<"[FeatureSelector debug-info:] QSARData object for file "<<conf.data_file<<" already in memory; not reading it again."<<endl;
	}

	Registry reg;
	Model* m;
	String model_type;

	ifstream model_input(conf.model.c_str()); // read model-abbreviation
	if(!model_input)
	{
		Log.error()<<"Error: Model-file '"<<conf.model<<"' does not exist!!"<<endl;
		return;
	}
	getline(model_input,model_type);
	getline(model_input,model_type);
	model_type = model_type.getField(0,"\t");
	model_input.close();

	RegistryEntry* entry = reg.getEntry(model_type);
	if(!entry->kernel)
	{
		m = (*entry->create)(*q);
	}
	else
	{
		// parameters irrelevant; will be overwritten by those read from file
		m = (*entry->createKernel1)(*q,1,1, -1);
	}

	if(conf.statistic>0)
	{
		Log.level(3)<<"  using "<<conf.statistic_name<<" to assess qualitiy of the model ... "<<endl;
		m->model_val->selectStat(conf.statistic);
	}

	m->readFromFile(conf.model.c_str());
	FeatureSelection fs(*m);
	if(conf.quality_increase_cutoff!=-1)
	{
		fs.setQualityIncreaseCutoff(conf.quality_increase_cutoff);
	}
	if(conf.remove_correlated || conf.feat_type==0)
	{
		fs.removeHighlyCorrelatedFeatures(conf.cor_threshold);
	}
	if(conf.feat_type==1)
	{
		fs.forwardSelection(conf.k_fold,conf.opt);
	}
	else if(conf.feat_type==2)
	{
		fs.backwardSelection(conf.k_fold,conf.opt);
	}
	else if(conf.feat_type==3)
	{
		fs.stepwiseSelection(conf.k_fold,conf.opt);
	}
	else if(conf.feat_type==4)
	{
		fs.removeLowResponseCorrelation(conf.cor_threshold);
	}
	else if(conf.feat_type==6)
	{
		fs.twinScan(conf.k_fold,conf.opt);
	}
	if(conf.opt_model_after_fs)
	{
		m->optimizeParameters(conf.opt_k_fold);
	}
	KernelModel* km = dynamic_cast<KernelModel*>(m);
	if(km && conf.opt_kernel_after_fs)
	{
		/// search locally around current kernel parameters
		try
		{
			// specifing start-values for grid search now obsolete; grid search will automatically search locally around current kernel parameter(s)
			km->kernel->gridSearch(conf.grid_search_stepwidth, conf.grid_search_steps,conf.grid_search_recursions,conf.opt_k_fold,conf.opt/*,start_par1,start_par2*/);
		}
		catch(BALL::Exception::GeneralException e)
		{
			Log.error()<<e.getName()<<" : "<<e.getMessage()<<endl;
			return;
		}
	}

	m->readTrainingData();
	m->train();
	m->saveToFile(conf.output);

	if(created_data_object) delete q;
	delete m;
}


#ifndef EXT_MAIN
int main(int argc, char* argv[])
{
	CommandlineParser par("FeatureSelector","run feature-selection on a QSAR model", VERSION, String(__DATE__), "QuEasy (QSAR)");
	par.registerMandatoryInputFile("i","input mod-file");
	par.registerMandatoryInputFile("dat","data-file");
	par.registerMandatoryOutputFile("o","output mod-file");
	par.registerMandatoryStringParameter("type","feature-selection type");

	String man = "FeatureSelector runs a feature-selection for a given QSAR model.\n\nThe type of feature-selection to be done is specified by '-type'. Input of this tool is a data file as generated by InputReader containing the training data for feature-selection and a QSAR model file as generated by ModelCreator (or this tool itself). Note that you can apply several feature-selection methods in succession by using the output of one call of this tool as input for the next call.\nModel- and kernel-parameters (if any) will be automatically optimized by cross-validation after applying the desired feature-selection.\n\nOutput of this tool is a model-file that can be used by other QuEasy tools (e.g. Validator).";
	list<String> restr;
	restr.push_back("remove_correlated");
	restr.push_back("forward_selection");
	restr.push_back("backward_selection");
	restr.push_back("stepwise_selection");
	restr.push_back("twinscan");
	restr.push_back("removeLowResponseCorrelation");
	par.setParameterRestrictions("type", restr);
	par.setToolManual(man);
	par.setSupportedFormats("i","mod");
	par.setSupportedFormats("dat","dat");
	par.setSupportedFormats("o","mod");
	par.parse(argc,argv);

	Registry reg;
	FeatureSelectionConfiguration conf;
	conf.model = par.get("i");
	conf.data_file = par.get("dat");
	conf.output = par.get("o");
	conf.opt_model_after_fs = true;
	conf.opt_kernel_after_fs = true;
	conf.opt = false;
	conf.k_fold = reg.default_k;
	conf.opt_k_fold = reg.default_k;
	conf.grid_search_recursions = reg.default_gridsearch_recursion;
	conf.grid_search_stepwidth = reg.default_gridsearch_stepwidth;
	conf.grid_search_steps = reg.default_gridsearch_steps;
	conf.cor_threshold = 0.95;

	String type = par.get("type");
	if(type=="remove_correlated")
	{
		conf.feat_type = 0;
	}
	else if(type=="forward_selection")
	{
		conf.feat_type = 1;
	}
	else if(type=="backward_selection")
	{
		conf.feat_type = 2;
	}
	else if(type=="stepwise_selection")
	{
		conf.feat_type = 3;
	}
	else if(type=="twinscan")
	{
		conf.feat_type = 6;
	}
	else if(type=="removeLowResponseCorrelation")
	{
		conf.feat_type = 4;
	}
	else
	{
		cerr << "Feature-selection type '"<<type<<"' unknown, possible choices are: remove_correlated, forward_selection, stepwise_selection, twinscan, removeLowResponseCorrelation"<<endl;
		exit(1);
	}

	startFeatureSelection(conf,0,0);
}
#endif
