#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "defs.h"
#include "svm.h"
#include "cover_tree.h"
#include "par_est.h"
#include "per_eval.h"
#include "fast_lsvm.h"
#include "knn.h"


void exit_with_help()
{
	printf(
	"Usage: FaLK-SVM-train [options] input_dataset_file model_file\n"
	"options:\n"
	"-t kernel_type : set type of kernel function (default 2)\n"
	"	0 -- linear: u'*v\n"
	"	1 -- polynomial: (gamma*u'*v + coef0)^degree\n"
	"	2 -- radial basis function: exp(-gamma*|u-v|^2)\n"
	"-L local model selection (default 0)\n"
	"	0 -- no local model selecion \n"
	"	1 -- local model selection on C K and kernel parameters on -M local models (the ranges are specified with -C -G -N)\n"
	"-R relaxation parameter for cover trees (default 1)\n"	
	"-K neighborhood size k\n"
	"-P assignement neighborhood size as fraction of K such that: K'=K*P (default 0.5)\n"
	"-d degree : set degree in kernel function (default 3)\n"
	"-g gamma : set gamma (locally) in kernel function, negative values for estimation based on histogram of distances (default = -0.5 for RBF, default = 1 for polynomial kernels)\n"
	"-r coef0 : set coef0 in kernel function (default 0)\n"
	"-c cost : set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1)\n"
	"-C c values for local grid model selection separated by ':' \n"
	"-G G values for local grid model selection separated by ':' \n"
	"-N K values for local grid model selection separated by ':' \n"
	"-M number of local models used for local model selection \n"
	"-wi weight: set the parameter C of class i to weight*C, for C-SVC (default 1)\n"
	"-b probability_estimates : whether to train local models for probability estimates, 0 or 1 (default 0)\n"
	"-v n: n-fold cross validation mode\n"
	"-S silent mode (default 0)\n"
	);
	exit(1);
}

void parse_command_line(int argc, char **argv, struct svm_parameter *param, struct lkm_parameter *lkm_param, char *train_file_name, char *model_file_name);

int main(int argc, char **argv)
{
	char train_file_name[1024];
	char model_file_name[1024];

	struct problem *train = Malloc(struct problem,1); if( train == NULL ) MallocError("train");
	struct svm_parameter *param = Malloc(struct svm_parameter,1); if( param == NULL ) MallocError("param");
	struct lkm_parameter *lkm_param = Malloc(struct lkm_parameter,1); if( lkm_param == NULL ) MallocError("lkm_param");

	parse_command_line(argc, argv, param, lkm_param, train_file_name, model_file_name );

	read_problem(train_file_name, train);
	
	sFastLSVM *flm = new sFastLSVM(lkm_param,train);

	if( lkm_param->nr_fold > 1 )
		{
		double *res = flm->cross_validation();
		PerEval *perEval = new PerEval(train->y,train->l);
		if( lkm_param->per_meas < 4 ) perEval->calcAll(res,lkm_param->per_meas);
		if( lkm_param->per_meas == 4 ) perEval->calcLift20P(res,true);
		delete perEval;
		free(res);
		}
	else
		{
		flm->train();
		flm->save(model_file_name);
		}

	delete flm;
	
	svm_destroy_param(param);
	free(param);

	free(train->y);
	free(train->x);
	free(train);

	return 0;
}



void parse_command_line(int argc, char **argv, struct svm_parameter *param, struct lkm_parameter *lkm_param, char *train_file_name, char *model_file_name)
{
	int i;

	// default values
	param->svm_type = C_SVC;
	param->kernel_type = RBF;
	param->degree = 3;
	param->gamma = -0.5;	// 1/k
	param->coef0 = 0;
	param->nu = 0.5;
	param->cache_size = 200;
	param->C = 100.0;
	param->eps = 1e-3;
	param->p = 0.1;
	param->shrinking = 1;
	param->probability = 0;
	param->nr_weight = 0;
	param->weight_label = NULL;
	param->weight = NULL;
	lkm_param->nr_fold = -1;
	lkm_param->k = 1000;
	lkm_param->kp = 250;
	lkm_param->base = 1;
	lkm_param->svm_par = param;
	lkm_param->local_model_selection = 0;
	lkm_param->conservative_mr = false;
	lkm_param->opt = 0;
	lkm_param->kp_ratio = 0.5;
	lkm_param->unbalanced = 0;
	lkm_param->loc_prob_bil_th = 0.33;

	lkm_param->n_k_lms = 0;
	lkm_param->per_meas = 0;
	lkm_param->silent = false;



	// parse options
	for(i=1;i<argc;i++)
	{
		if(argv[i][0] != '-') break;

		if(++i>=argc)
			exit_with_help();
		switch(argv[i-1][1])
		{
			case 't':
				param->kernel_type = atoi(argv[i]);
				break;
			case 'T':
				lkm_param->loc_prob_bil_th = atof(argv[i]);
				break;
			case 'L':
				lkm_param->local_model_selection = atoi(argv[i]);
				break;
			case 'R':
				lkm_param->base = atoi(argv[i]);
				break;
			case 'K':
				lkm_param->k = atoi(argv[i]);
				break;			
			case 'P':
				lkm_param->kp_ratio = atof(argv[i]);
				break;
			case 'S':
				lkm_param->silent = (atoi(argv[i]) == 1);
				if( lkm_param->silent ) svm_print_string = &print_null;
				break;
			case 'O':
				lkm_param->opt = atoi(argv[i]);
				break;	
			case 'd':
				param->degree = atoi(argv[i]);
				break;
			case 'g':
				param->gamma = atof(argv[i]);
				break;
			case 'u':
				lkm_param->unbalanced = atoi(argv[i]);
				break;
			case 'r':
				param->coef0 = atof(argv[i]);
				break;
			case 'm':
				param->cache_size = atof(argv[i]);
				break;
			case 'c':
				param->C = atof(argv[i]);
				break;
			case 'e':
				param->eps = atof(argv[i]);
				break;
			case 'p':
				param->p = atof(argv[i]);
				break;
			case 'h':
				param->shrinking = atoi(argv[i]);
				break;
			case 'C':
				{
				int n_val = 1;
				int u = 0;

				for( u = 0; u < (int)strlen(argv[i]); u++ )
					if( argv[i][u] == ':' )
						n_val++;

				lkm_param->c_lms = Malloc(double,n_val);

				u = 0;
				char str[1024];
				unsigned int l = 0;
				int ll = 0;

				while( u < n_val )
					{
					if( argv[i][l] == ':' || l == strlen(argv[i]) )
						{
						str[ll] = '\0';
						lkm_param->c_lms[u++] = atof(str);
						l++;
						ll = 0;
						}
					else str[ll++] = argv[i][l++];
					}
				lkm_param->n_c_lms = n_val;
				break;
				}
			case 'G':
				{
				int n_val = 1;
				int u = 0;

				for( u = 0; u < (int)strlen(argv[i]); u++ )
					if( argv[i][u] == ':' )
						n_val++;

				lkm_param->g_lms = Malloc(double,n_val);

				u = 0;
				char str[1024];
				unsigned int l = 0;
				int ll = 0;

				while( u < n_val )
					{
					if( argv[i][l] == ':' || l == strlen(argv[i]) )
						{
						str[ll] = '\0';
						lkm_param->g_lms[u++] = atof(str);
						l++;
						ll = 0;
						}
					else str[ll++] = argv[i][l++];
					}
				lkm_param->n_g_lms = n_val;
				break;
				}
			case 'N':
				{
				int n_val = 1;
				int u = 0;

				for( u = 0; u < (int)strlen(argv[i]); u++ )
					if( argv[i][u] == ':' )
						n_val++;

				lkm_param->k_lms = Malloc(int,n_val);

				u = 0;
				char str[1024];
				unsigned int l = 0;
				int ll = 0;

				while( u < n_val )
					{
					if( argv[i][l] == ':' || l == strlen(argv[i]) )
						{
						str[ll] = '\0';
						lkm_param->k_lms[u++] = atoi(str);
						l++;
						ll = 0;
						}
					else str[ll++] = argv[i][l++];
					}
				lkm_param->n_k_lms = n_val;
				break;
				}
			case 'M':
				lkm_param->n_loc_models = atoi(argv[i]);
				break;
			case 'E':
				lkm_param->per_meas = atoi(argv[i]);
				break;
			case 'v':
				lkm_param->nr_fold = atoi(argv[i]);
				if(lkm_param->nr_fold < 2)
				{
					fprintf(stderr,"n-fold cross validation: n must >= 2\n");
					exit_with_help();
				}
				break;
			case 'b':
				param->probability = atoi(argv[i]);
				break;
			case 'w':
				++param->nr_weight;
				param->weight_label = (int *)realloc(param->weight_label,sizeof(int)*param->nr_weight);
				param->weight = (double *)realloc(param->weight,sizeof(double)*param->nr_weight);
				param->weight_label[param->nr_weight-1] = atoi(&argv[i-1][2]);
				param->weight[param->nr_weight-1] = atof(argv[i]);
				break;
			default:
				fprintf(stderr,"Unknown option: -%c\n", argv[i-1][1]);
				exit_with_help();
		}
	}

	if( param->kernel_type == POLY && param->gamma < 0.0 ) 
		param->gamma = 1.0;

	lkm_param->kp = (int)(((double)lkm_param->k)*lkm_param->kp_ratio);

	if(i>=argc)
		exit_with_help();

	strcpy(train_file_name, argv[i++]);

	if(lkm_param->nr_fold<2) strcpy(model_file_name,argv[i++]);
}

// read in a problem (in svmlight format)

