#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

#include "mex.h"

#ifdef MX_API_VER
#if MX_API_VER < 0x07030000
typedef int mwIndex;
#endif 
#endif 
#ifndef max
#define max(x,y) (((x)>(y))?(x):(y))
#endif
#ifndef min
#define min(x,y) (((x)<(y))?(x):(y))
#endif

void exit_with_help()
{
	mexPrintf(
	"Usage: [label_vector, instance_matrix] = libsvmread('filename');\n"
	);
}

static void fake_answer(int nlhs, mxArray *plhs[])
{
	int i;
	for(i=0;i<nlhs;i++)
		plhs[i] = mxCreateDoubleMatrix(0, 0, mxREAL);
}

static char *line;
static int max_line_len;

static char* readline(FILE *input)
{
	int len;
	
	if(fgets(line,max_line_len,input) == NULL)
		return NULL;

	while(strrchr(line,'\n') == NULL)
	{
		max_line_len *= 2;
		line = (char *) realloc(line, max_line_len);
		len = (int) strlen(line);
		if(fgets(line+len,max_line_len-len,input) == NULL)
			break;
	}
	return line;
}

// read in a problem (in libsvm format)
void read_problem(const char *filename, int nlhs, mxArray *plhs[])
{
	int max_index, min_index, inst_max_index, i;
	long elements, k;
	FILE *fp = fopen(filename,"r");
	int l = 0;
	char *endptr;
	mwIndex *ir, *jc;
	double *labels, *samples;
	
	if(fp == NULL)
	{
		mexPrintf("can't open input file %s\n",filename);
		fake_answer(nlhs, plhs);
		return;
	}

	max_line_len = 1024;
	line = (char *) malloc(max_line_len*sizeof(char));

	max_index = 0;
	min_index = 1; // our index starts from 1
	elements = 0;
	while(readline(fp) != NULL)
	{
		char *idx, *val;
		// features
		int index = 0;

		inst_max_index = -1; // strtol gives 0 if wrong format, and precomputed kernel has <index> start from 0
		strtok(line," \t"); // label
		while (1)
		{
			idx = strtok(NULL,":"); // index:value
			val = strtok(NULL," \t");
			if(val == NULL)
				break;

			errno = 0;
			index = (int) strtol(idx,&endptr,10);
			if(endptr == idx || errno != 0 || *endptr != '\0' || index <= inst_max_index)
			{
				mexPrintf("Wrong input format at line %d\n",l+1);
				fake_answer(nlhs, plhs);
				return;
			}
			else
				inst_max_index = index;

			min_index = min(min_index, index);
			elements++;
		}
		max_index = max(max_index, inst_max_index);
		l++;
	}
	rewind(fp);

	// y
	plhs[0] = mxCreateDoubleMatrix(l, 1, mxREAL);
	// x^T
	if (min_index <= 0)
		plhs[1] = mxCreateSparse(max_index-min_index+1, l, elements, mxREAL);
	else
		plhs[1] = mxCreateSparse(max_index, l, elements, mxREAL);

	labels = mxGetPr(plhs[0]);
	samples = mxGetPr(plhs[1]);
	ir = mxGetIr(plhs[1]);
	jc = mxGetJc(plhs[1]);

	k=0;
	for(i=0;i<l;i++)
	{
		char *idx, *val, *label;
		jc[i] = k;

		readline(fp);

		label = strtok(line," \t\n");
		if(label == NULL)
		{
			mexPrintf("Empty line at line %d\n",i+1);
			fake_answer(nlhs, plhs);
			return;
		}
		labels[i] = strtod(label,&endptr);
		if(endptr == label || *endptr != '\0')
		{
			mexPrintf("Wrong input format at line %d\n",i+1);
			fake_answer(nlhs, plhs);
			return;
		}

		// features
		while(1)
		{
			idx = strtok(NULL,":");
			val = strtok(NULL," \t");
			if(val == NULL)
				break;

			ir[k] = (mwIndex) (strtol(idx,&endptr,10) - min_index); // precomputed kernel has <index> start from 0

			errno = 0;
			samples[k] = strtod(val,&endptr);
			if (endptr == val || errno != 0 || (*endptr != '\0' && !isspace(*endptr)))
			{
				mexPrintf("Wrong input format at line %d\n",i+1);
				fake_answer(nlhs, plhs);
				return;
			}
			++k;
		}
	}
	jc[l] = k;

	fclose(fp);
	free(line);

	{
		mxArray *rhs[1], *lhs[1];
		rhs[0] = plhs[1];
		if(mexCallMATLAB(1, lhs, 1, rhs, "transpose"))
		{
			mexPrintf("Error: cannot transpose problem\n");
			fake_answer(nlhs, plhs);
			return;
		}
		plhs[1] = lhs[0];
	}
}

void mexFunction( int nlhs, mxArray *plhs[],
		int nrhs, const mxArray *prhs[] )
{
	char filename[256];

	if(nrhs != 1 || nlhs != 2)
	{
		exit_with_help();
		fake_answer(nlhs, plhs);
		return;
	}

	mxGetString(prhs[0], filename, mxGetN(prhs[0]) + 1);

	if(filename == NULL)
	{
		mexPrintf("Error: filename is NULL\n");
		return;
	}

	read_problem(filename, nlhs, plhs);

	return;
}

