#ifndef _BOOST_HEADER_
#define _BOOST_HEADER_
#include <armadillo>
#include "lbfgs.h"
#include "matWriter.h"

#include <string>
#include <fstream>
#include <tinyxml2.h>

using namespace std;
using namespace arma;
using namespace tinyxml2;

extern fstream logfile;
struct my_param
{

	double			lamda,
				beta;
	unsigned int	T;

	size_t			maxIter;
	size_t			maxLineseach;

	string			data;
	string			log;
	string			result;
	
	void read(const string &file){
		XMLDocument doc;
	    	doc.LoadFile(file.c_str());
		XMLElement  *config = doc.RootElement();
	    	if(strcasecmp(config->Name(),"config")){
			logfile<<"error:\t"<<"config file format invalid.\n";exit(-1);
	    	}
	    	lamda = atof(config->FirstChildElement("lamda")->GetText());
	    	beta = atof(config->FirstChildElement("beta")->GetText());
	    	T = atoi(config->FirstChildElement("T")->GetText());

		maxIter = atoi(config->FirstChildElement("maxIteror")->GetText());
		maxLineseach = atoi(config->FirstChildElement("maxLinearsearch")->GetText());

		data = config->FirstChildElement("data")->GetText();
		log = config->FirstChildElement("log")->GetText();
		result = config->FirstChildElement("result")->GetText();		
	}
};
extern my_param para;
lbfgsfloatval_t evaluate(
	void *instance,
	const lbfgsfloatval_t *x,
	lbfgsfloatval_t *g,
	const int n,
	const lbfgsfloatval_t step);
int progress(
	void *instance,
	const lbfgsfloatval_t *x,
	const lbfgsfloatval_t *g,
	const lbfgsfloatval_t fx,
	const lbfgsfloatval_t xnorm,
	const lbfgsfloatval_t gnorm,
	const lbfgsfloatval_t step,
	int n,
	int k,
	int ls
	);
class PLweak_learner{
public:
	virtual mat predict(const mat& ,const mat&) = 0;
	virtual mat& train(const mat &,const mat &,const mat&,void* =0)=0;
};
struct INSTANCE_T
{
	const mat *pFeature;
	const mat *pPartial_Target;
	const mat *pTarget;
	vec	*pW;//parameter

	const mat *pTeFt;
	const mat *pTeLbl;
};
//弱分类器 softmax
class softmax :public PLweak_learner{

public:
	mat predict(const mat& test_data,const mat& model){		
		mat d = exp(test_data*model);
		//归一化
		for (size_t ins = 0; ins < test_data.n_rows; ins++)
			d.row(ins) = d.row(ins) / sum(d.row(ins));/*logfile << "预测结果：\n" << model << endl; logfile.flush();*/
		return d;
	}
	mat& train(const mat &train_data, const mat &train_PL_labels, const mat &train_label, void*parameter = 0){
		size_t n_ins = train_data.n_rows;
		size_t n_lbl = train_PL_labels.n_cols;
		size_t n_ft = train_data.n_cols;
		size_t n_var = n_ft*n_lbl;
		lbfgsfloatval_t fx;
		lbfgsfloatval_t *x = lbfgs_malloc(n_var);
		lbfgs_parameter_t param;
		
		INSTANCE_T	instance = {&train_data,&train_PL_labels,&train_label,(vec*)parameter};
		mat	&model = *new mat();
		int code;
		if (x == NULL){
			printf("ERROR: Failed to allocate a memory block for variables.\n");
			exit(-1);
		}
		srand(time(0));
		for (size_t i = 0; i < n_var; i++)
			x[i] = 1;// rand() % 100 / 100.0;

		/* Initialize the parameters for the L-BFGS optimization. */
		lbfgs_parameter_init(&param);
		param.max_linesearch = para.maxLineseach;
		param.max_iterations = para.maxIter;
		code = lbfgs(n_var, x, &fx, evaluate, progress, &instance, &param);
		*const_cast<double**>(&model.mem) = x;
		*const_cast<uword*>(&model.n_rows) = n_ft; *const_cast<uword*>(&model.n_cols) = n_lbl; *const_cast<uword*>(&model.n_elem) = n_ft*n_lbl;		
		printf("lbfgs exit with code%d\n",code);
		return model;
	}
};
class Boost{
	PLweak_learner* learner;
	vector<mat*>	models;
	vector<double>	alphas;
public:
	Boost(string const &name="softmax"){
		switch (name[0])
		{
		case 's':case 'S':
			learner = new softmax;
			break;
		default:
			break;
		}
	}
	void boosting(const mat& train_data,const mat &train_PL_labels,const mat &train_labels,int T)
	{
		size_t n_ins = train_data.n_rows;
		size_t n_lbl = train_PL_labels.n_cols;
		size_t n_ft = train_data.n_cols;
		vector<mat*> models(T);
		vector<double>alphas(T);
		mat		label_distr = train_PL_labels;
		mat		h;
		vec		D(n_ins);
		//initial
		D.fill(1.0/n_ins);
		for (size_t t = 0; t < T; t++){
			models[t] = &learner->train(train_data, label_distr, train_labels, &D);
			h = learner->predict(train_data, *models[t]);		
			vec		diff(n_ins);
			uvec	max_label(n_ins,fill::zeros);
			for (size_t ins = 0; ins < n_ins; ins++)
			{
				double	max_cand = 0, max_nocand = 0;
				for (size_t k = 0; k < n_lbl; k++){
					if (train_PL_labels(ins, k)){//候选标记集合		
						if (h(ins, k) > max_cand){
						max_cand = h(ins, k);
						max_label[ins] = k;
						}
					}else  
					if (h(ins, k) > max_nocand)//非候选标记集合
						max_nocand = h(ins, k);		
				}							
				diff(ins) = max_cand - max_nocand;			
			}
			double r = dot(D,diff);
			alphas[t] = 1 / 2.0 * log((1+r)/(1-r));
			//update weight
			for (size_t ins=0; ins < n_ins; ++ins)
				D(ins) = D(ins)*exp(-alphas[t]*diff(ins));
			D = D / sum(D);//归一化
			//update q
			for (size_t ins = 0; ins < n_ins; ins++){
				//logfile << label_distr(ins, max_label(ins)) << " * " << exp(alphas[t]) << "\n";
				if (train_PL_labels(ins, max_label(ins)))
					label_distr(ins, max_label(ins)) = label_distr(ins, max_label(ins))*exp(para.beta);//使得h预测的标记是真实标记的置信度增加
				else
					label_distr(ins, max_label(ins)) = label_distr(ins, max_label(ins))*exp(-para.beta);
				label_distr.row(ins) = label_distr.row(ins) / sum(label_distr.row(ins));//归一化
			}
			printf("the %d weak learner finished\n",t);
			printf("  r: %f",r);
			printf("  alpha:%f\n",alphas[t]);			
		}
		this->models.swap(models);
		this->alphas.swap(alphas);
		if(train_labels.n_cols<=0)return;
		//transductive
		int 	trans_nums=0;
		for (int i=0;i<n_ins;++i){
			int max_lbl=0;
			for (int j=0; j < n_lbl;++j){
				if (label_distr(i,j)>label_distr(i,max_lbl))max_lbl=j;
			}
			if (train_labels(i,max_lbl))trans_nums++;
		}
		logfile << "\ntransductive correct rate:\t"<<trans_nums/double(n_ins)<<endl;
		
	}
	pair<uvec,double> predict(const mat& test_data,const mat& test_label,bool flag=false)
	{
		size_t	T = models.size();
		size_t	n_ins = test_data.n_rows;
		size_t	n_lbl = test_label.n_cols;
		size_t	n_count = 0;
		vec	rates(T, fill::zeros);
		mat	ensemble_h(n_ins,n_lbl,fill::zeros);
		uvec	result(n_ins);
		for (size_t t = 0; t < T; t++){
			mat tmp = alphas[t] * learner->predict(test_data, *models[t]);
			ensemble_h = ensemble_h + tmp;
			if(flag)
				for (size_t ins = 0; ins < n_ins; ins++){		
					arma::uword	lbl = 0;
					tmp.row(ins).max(lbl);
					if (test_label(ins, lbl) == 1){//predict correct			
						rates[t] = rates[t] + 1;
					}
				}
		}
		if (flag)
			logfile <<"weak learner correct rate:\n"<< rates/n_ins;
		for (size_t ins = 0; ins < n_ins; ins++){		
			arma::uword	lbl = 0;
			ensemble_h.row(ins).max(lbl);
			result[ins] = lbl;
			if (test_label(ins, lbl) == 1){//predict correct			
				n_count = n_count + 1;
			}
		}
		return make_pair(result,n_count/double(n_ins));
	}
	double transductive(const mat& data,const mat& pl_labels,const mat& label)
	{
		size_t	T = models.size();
		size_t	n_ins = data.n_rows;
		size_t	n_lbl = label.n_cols;
		size_t	n_count = 0;
		mat	ensemble_h(n_ins,n_lbl,fill::zeros);

		for (size_t t = 0; t < T; t++){
			mat tmp = alphas[t] * learner->predict(data, *models[t]);
			ensemble_h = ensemble_h + tmp;
		}
		for (size_t ins = 0; ins < n_ins; ins++){
			int	max_lbl=0;
			double	max_cond=0;
			for (size_t lbl=0;lbl<n_lbl;++lbl)
				if (pl_labels(ins,lbl)&&
				ensemble_h(ins,lbl)>max_cond){
					max_lbl=lbl;max_cond=ensemble_h(ins,lbl);
				}
			if(1 == label(ins,max_lbl))n_count++;
		}
		return n_count/double(n_ins);
	}
};

#endif
