#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: FkNN [options] training_dataset_file [testing_dataset_file output_label_file]\n"
	"options:\n"
	"-K neighborhood size k\n"
	"-C conservative variant for the majority rule , 0 or 1 (default 0)\n"
	"-R relaxation parameter for cover trees (default 1)\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) [EQUIVALENT TO LINEAR KERNEL!]\n"
	"-d degree : set degree in kernel function (default 3)\n"
	"-r coef0 : set coef0 in kernel function (defaul 1)\n"
	"-s knn strategy : (defaul 0 = majority rule)\n"
	"-g gamma : set gamma in kernel function, negative values for estimation based on histogram of distances (default = -0.5 for RBF, default = 1 for polynomial kernels)\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 *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);
	if( lkm_param->nr_fold < 1 ) read_problem(test_file_name, test);

	KNN *knn = new KNN(lkm_param,train);

	double *res = NULL;

	if( lkm_param->nr_fold > 1 )
		{
		res = Malloc(double,train->l);
		knn->cross_validation(lkm_param->nr_fold,res);
		}
	else
		{
		knn->train();
		if( lkm_param->k == 1 )	res = knn->predict(test);
		else res = knn->predict(test,lkm_param->k);

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

	PerEval *perEval = NULL;
	if( lkm_param->nr_fold > 1 ) perEval = new PerEval(train->y,train->l);
	else perEval = new PerEval(test->y,test->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);
	delete knn;
	
	if( lkm_param->nr_fold < 1 ) free(test->y);
	if( lkm_param->nr_fold < 1 ) free(test->x);
	if( lkm_param->nr_fold < 1 ) 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 = 0;
	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->per_meas = 0;
	lkm_param->knn_strategy = 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 'S':
				lkm_param->silent = (atoi(argv[i]) == 1);
				if( lkm_param->silent ) svm_print_string = &print_null;
				break;
			case 'R':
				lkm_param->base = atoi(argv[i]);
				break;
			case 'C':
				lkm_param->conservative_mr = atoi(argv[i])>0 ? true : false;
				break;
			case 't':
				param->kernel_type = atoi(argv[i]); 
				break;
			case 's':
				lkm_param->knn_strategy = atoi(argv[i]); 
				break;
			case 'E':
				lkm_param->per_meas = atoi(argv[i]);
				break;
			case 'd':
				param->degree = atoi(argv[i]);
				break;
			case 'r':
				param->coef0 = atof(argv[i]);
				break;
			case 'g':
				param->gamma = atof(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;
			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++]);
		}
	
}



