#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 "knn.h"
#include "per_eval.h"


void exit_with_help()
{
	printf(
	"Usage: FkNNSVM [options] training_dataset_file [testing_dataset_file output_label_file]\n"
	"options:\n"
	"-R relaxation parameter for cover trees (default 1)\n"
	"-K neighborhood size k\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"
	"-c cost : set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1)\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 \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 *test_file_name, char *output_file_name);

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

	struct problem *train = Malloc(struct problem,1); if( train == NULL ) MallocError("train");
	struct problem *test = Malloc(struct problem,1); if( train == NULL ) MallocError("test");
	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, test_file_name, output_file_name);

	read_problem(train_file_name, train);
	read_problem(test_file_name, test);

	CoverTree *coverTree = new CoverTree(param,lkm_param->base,false,0,lkm_param->silent);
	coverTree->setKs(lkm_param->k,lkm_param->kp);
	coverTree->addPoints(train);

	double *pred = Malloc(double,test->l);

	double gv = param->gamma;
	ParEst *parEst = new ParEst();

	for( int i = 0; i < test->l; i++ )
		{
		if( !lkm_param->silent ) printf("Testing the %d-th point:\t",i);
		struct problem *pr = coverTree->knn_p(test->x[i],lkm_param->k,true);
		if( gv <= 0.0 && param->kernel_type == RBF )
			{
			param->gamma = parEst->estG_quant(pr,-gv);
			if( !lkm_param->silent ) printf("est gamma [%g --> %g]\t",-gv,param->gamma);
			}
		struct svm_model *m = svm_train(pr,param);
		pred[i] = svm_predict(m,test->x[i]);
		if( !lkm_param->silent ) printf("\n");
		svm_destroy_model(m);
		free(pr->x);
		free(pr->y);
		free(pr);
		}

	delete parEst;
	delete coverTree;

	PerEval *perEval = NULL;
	perEval = new PerEval(test->y,test->l);
	perEval->calcAll(pred,0);
	delete perEval;

	FILE *out = fopen(output_file_name,"w");
	
	for( int i = 0; i < test->l; i++ )
		fprintf(out,"%g\n",pred[i]);

	fclose(out);

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

	svm_destroy_param(param);
	free(param);
	
 	return 0;
}



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

	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 = 100;
	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 = 0;
	lkm_param->k = 1;
	lkm_param->kp = 1;
	lkm_param->base = 1;
	lkm_param->svm_par = param;
	lkm_param->nr_strategy = 0;
	lkm_param->local_model_selection = 0;
	lkm_param->conservative_mr = false;
	lkm_param->nr_fold = 0;
	lkm_param->loc_prob_bil_th = 0.33;
	lkm_param->opt = 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 'K':
				lkm_param->k = atoi(argv[i]);
				break;
			case 'R':
				lkm_param->base = atoi(argv[i]);
				break;
			case 'S':
				lkm_param->silent = (atoi(argv[i]) == 1);
				if( lkm_param->silent ) svm_print_string = &print_null;
				break;
			case 't':
				param->kernel_type = atoi(argv[i]);
				break;
			case 'c':
				param->C = atof(argv[i]);
				break;
			case 'd':
				param->degree = atoi(argv[i]);
				break;
			case 'g':
				param->gamma = atof(argv[i]);
				break;
			case 'r':
				param->coef0 = 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;

	if(i>=argc)
		exit_with_help();

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

	if( lkm_param->nr_fold < 2 ) 
		{	
		if(i>=argc)
			{
			printf("\n\nTesting input file missing!!!\n\n");
			exit_with_help();
			}
		strcpy(test_file_name, argv[i++]);
		
		if(i>=argc)
			{
			printf("\n\nOutput label file missing!!!\n\n");
			exit_with_help();
			}

		strcpy(output_file_name,argv[i++]);
		}
}

