#include "CmdLineInterface.h"

#include <shogun/lib/config.h>
#include <shogun/lib/ShogunException.h>
#include <shogun/io/SGIO.h>
#include <shogun/io/CSVFile.h>
#include <shogun/io/LibSVMFile.h>
#include <shogun/ui/SGInterface.h>

#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif

#ifndef WIN32
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif

#include <stdio.h>
#include <strings.h>

void cmdline_print_message(FILE* target, const char* str)
{
	fprintf(target, "%s", str);
}

void cmdline_print_warning(FILE* target, const char* str)
{
	fprintf(target, "%s", str);
}

void cmdline_print_error(FILE* target, const char* str)
{
	fprintf(target, "%s", str);
}

void cmdline_cancel_computations(bool &delayed, bool &immediately)
{
}

using namespace shogun;

const int32_t READLINE_BUFFER_SIZE = 10000;
extern CSGInterface* interface;
extern CSGInterfaceMethod sg_methods[];

CCmdLineInterface::CCmdLineInterface()
: CSGInterface(), m_lhs(NULL), m_rhs(NULL)
{
	reset();
}

CCmdLineInterface::~CCmdLineInterface()
{
	delete m_lhs;
	delete m_rhs;
}

void CCmdLineInterface::reset(const char* line)
{
	CSGInterface::reset();

	if (!line)
		return;

	char* element=NULL;
	const char delim_equal[]="=";
	const char delim_lhs[]="=, \t\n";
	const char delim_rhs[]=" \t\n";

	delete m_lhs;
	m_lhs=NULL;
	delete m_rhs;
	m_rhs=NULL;

	/* split lhs from rhs
	 * for some reason strstr on sunos and newer libc's
	 * requires a char* haystack
	 */
	char* equal_sign=strstr((char*) line, delim_equal);
	if (equal_sign)
	{
#ifdef DEBUG_CMDLINEIF
		SG_PRINT("has lhs!\n");
#endif
		element=strtok((char*) line, delim_lhs);
		if (element)
		{
			m_lhs=new DynArray<char*>();
			m_lhs->append_element(element);
			m_nlhs++;
			while ((element=strtok(NULL, delim_lhs)))
			{
				if (element>equal_sign) // on rhs now
					break;

				m_lhs->append_element(element);
				m_nlhs++;
			}
		}
	}
	else
		element=strtok((char*) line, delim_rhs);

	if (element)
	{
		m_rhs=new DynArray<char*>();
		m_rhs->append_element(element);
		m_nrhs++;
		while ((element=strtok(NULL, delim_rhs)))
		{
			m_rhs->append_element(element);
			m_nrhs++;
		}
	}
	else
		m_rhs=NULL;

#ifdef DEBUG_CMDLINEIF
	SG_PRINT("nlhs=%d nrhs=%d\n", m_nlhs, m_nrhs);
	if (m_lhs)
	{
		for (int32_t i=0; i<m_lhs->get_num_elements(); i++)
			SG_PRINT("element lhs %i %s\n", i, m_lhs->get_element(i));
	}

	if (m_rhs)
	{
		for (int32_t i=0; i<m_rhs->get_num_elements(); i++)
			SG_PRINT("element rhs %i %s\n", i, m_rhs->get_element(i));
	}
#endif
}


/** get functions - to pass data from the target interface to shogun */

/** determine argument type
 *
 * a signature is read from a data file. this signature contains the argument
 * type.
 *
 * currently, the signature must be in the beginning of the file,
 * consists of a line starting with 3 hash signs, 1 space and then
 * the argument type. e.g.:
 *
 * ### SHOGUN V0 STRING_CHAR
 * ACGTGCAAAAGC
 * AGTCDTCD
 */
IFType CCmdLineInterface::get_argument_type()
{
	const int32_t len=1024;
	IFType argtype=UNDEFINED;
	const char* filename=m_rhs->get_element(m_rhs_counter);

	// read the first 1024 of the file and heuristically decide about its
	// content
	FILE* fh=fopen((char*) filename, "r");
	if (!fh)
		SG_ERROR("Could not find file %s.\n", filename);

	char* chunk=SG_MALLOC(char, len+1);
	memset(chunk, 0, sizeof(char)*(len+1));
	size_t nread=fread(chunk, sizeof(char), len, fh);
	fclose(fh);
	if (nread<=0)
		SG_ERROR("Could not read data from %s.\n");

	char* signature=SG_MALLOC(char, len+1);
	int32_t num=sscanf(chunk, "### SHOGUN V0 %s\n", signature);

	// if file has valid shogun signature use it to determine file type
	if (num==1)
	{
		SG_DEBUG("read signature: %s\n", signature);

		if (strncmp(signature, "STRING_CHAR", 11)==0)
			argtype=STRING_CHAR;
		else if (strncmp(signature, "STRING_BYTE", 11)==0)
			argtype=STRING_BYTE;
		else if (strncmp(signature, "DENSE_REAL", 10)==0)
			argtype=DENSE_REAL;
		else if (strncmp(signature, "SPARSE_REAL", 11)==0)
			argtype=SPARSE_REAL;
	}
	else
	{
		SG_DEBUG("could not find signature in file %s guessing file type.\n", filename);

		// special for cubes
		if (strspn(chunk, "123456\n")==nread)
		{
			argtype=STRING_CHAR;
			SG_DEBUG("guessing STRING_CHAR\n");
		}
		else if (strspn(chunk, "0123456789.e+- \t\n")==nread)
		{
			argtype=DENSE_REAL;
			SG_DEBUG("guessing DENSE_REAL\n");
		}
		else if (strspn(chunk, "0123456789:.e+- \t\n")==nread)
		{
			argtype=SPARSE_REAL;
			SG_DEBUG("guessing SPARSE_REAL\n");
		}
		else
		{
			argtype=STRING_CHAR;
			SG_DEBUG("guessing STRING_CHAR\n");
		}
	}

	SG_FREE(signature);
	SG_FREE(chunk);
	return argtype;
}


int32_t CCmdLineInterface::get_int()
{
	const char* i=get_arg_increment();
	if (!i)
		SG_ERROR("Expected Scalar Integer as argument %d\n", m_rhs_counter);

	int32_t value=-1;
	int32_t num=sscanf(i, "%d", &value);
	if (num!=1)
		SG_ERROR("Expected Scalar Integer as argument %d\n", m_rhs_counter);

	return value;
}

float64_t CCmdLineInterface::get_real()
{
	const char* r=get_arg_increment();
	if (!r)
		SG_ERROR("Expected Scalar Real as argument %d\n", m_rhs_counter);

	float64_t value=-1;
	int32_t num=sscanf(r, "%lf", &value);
	if (num!=1)
		SG_ERROR("Expected Scalar Real as argument %d\n", m_rhs_counter);

	return value;
}

bool CCmdLineInterface::get_bool()
{
	const char* b=get_arg_increment();
	if (!b)
		SG_ERROR("Expected Scalar Bool as argument %d\n", m_rhs_counter);

	int32_t value=-1;
	int32_t num=sscanf(b, "%i", &value);
	if (num!=1)
		SG_ERROR("Expected Scalar Bool as argument %d\n", m_rhs_counter);

	return (value!=0);
}


char* CCmdLineInterface::get_string(int32_t& len)
{
	const char* s=get_arg_increment();
	if (!s)
		SG_ERROR("Expected 1 String as argument %d.\n", m_rhs_counter);

	len=strlen(s);
	ASSERT(len>0);

	char* result=SG_MALLOC(char, len+1);
	memcpy(result, s, len*sizeof(char));
	result[len]='\0';

	return result;
}

#define GET_VECTOR(fname, sg_type) \
void CCmdLineInterface::fname(sg_type*& vec, int32_t& len)	\
{																		\
	const char* filename=get_arg_increment();							\
	if (!filename)														\
		SG_ERROR("No filename given to read vector.\n");				\
																		\
	CCSVFile f((char*) filename, 'r');								\
																		\
	try																	\
	{																	\
		f.fname(vec, len);												\
	}																	\
	catch (...)															\
	{																	\
		SG_ERROR("Could not read data from %s.\n", filename);			\
	}																	\
}
GET_VECTOR(get_vector, uint8_t)
GET_VECTOR(get_vector, char)
GET_VECTOR(get_vector, int32_t)
GET_VECTOR(get_vector, float32_t)
GET_VECTOR(get_vector, float64_t)
GET_VECTOR(get_vector, int16_t)
GET_VECTOR(get_vector, uint16_t)
#undef GET_VECTOR

#define GET_MATRIX(fname, sg_type) \
void CCmdLineInterface::fname(sg_type*& matrix, int32_t& num_feat, int32_t& num_vec) \
{																		\
	const char* filename=get_arg_increment();							\
	if (!filename)														\
		SG_ERROR("No filename given to read matrix.\n");				\
																		\
	CCSVFile f((char*) filename, 'r');								\
																		\
	try																	\
	{																	\
		f.fname(matrix, num_feat, num_vec);								\
	}																	\
	catch (...)															\
	{																	\
		SG_ERROR("Could not read data from %s.\n", filename);			\
	}																	\
}
GET_MATRIX(get_matrix, uint8_t)
GET_MATRIX(get_matrix, char)
GET_MATRIX(get_matrix, int32_t)
GET_MATRIX(get_matrix, float32_t)
GET_MATRIX(get_matrix, float64_t)
GET_MATRIX(get_matrix, int16_t)
GET_MATRIX(get_matrix, uint16_t)
#undef GET_MATRIX

void CCmdLineInterface::get_ndarray(uint8_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(char*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(int32_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(float32_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(float64_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(int16_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_ndarray(uint16_t*& array, int32_t*& dims, int32_t& num_dims)
{
}

void CCmdLineInterface::get_sparse_matrix(SGSparseVector<float64_t>*& matrix, int32_t& num_feat, int32_t& num_vec)
{
	const char* filename=get_arg_increment();
	if (!filename)
		SG_ERROR("No filename given to read SPARSE REAL matrix.\n");

	CLibSVMFile f((char*) filename, 'r');
	f.get_sparse_matrix(matrix, num_feat, num_vec);
}


void CCmdLineInterface::get_string_list(SGString<uint8_t>*& strings, int32_t& num_str, int32_t& max_string_len)
{
	strings=NULL;
	num_str=0;
	max_string_len=0;
}

void CCmdLineInterface::get_string_list(SGString<char>*& strings, int32_t& num_str, int32_t& max_string_len)
{
	const char* filename=get_arg_increment();
	if (!filename)
		SG_ERROR("No filename given to read CHAR string list.\n");

	CCSVFile f((char*) filename, 'r');
	f.get_string_list(strings, num_str, max_string_len);
}

void CCmdLineInterface::get_string_list(SGString<int32_t>*& strings, int32_t& num_str, int32_t& max_string_len)
{
	strings=NULL;
	num_str=0;
	max_string_len=0;
}

void CCmdLineInterface::get_string_list(SGString<int16_t>*& strings, int32_t& num_str, int32_t& max_string_len)
{
	strings=NULL;
	num_str=0;
	max_string_len=0;
}

void CCmdLineInterface::get_string_list(SGString<uint16_t>*& strings, int32_t& num_str, int32_t& max_string_len)
{
	strings=NULL;
	num_str=0;
	max_string_len=0;
}


void CCmdLineInterface::get_attribute_struct(const CDynamicArray<T_ATTRIBUTE>* &attrs)
{
	attrs=NULL;
}

/** set functions - to pass data from shogun to the target interface */
bool CCmdLineInterface::create_return_values(int32_t num)
{
	if (num==m_nlhs)
		return true;

	return false;
}

void* CCmdLineInterface::get_return_values()
{
	return NULL;
}


/** set functions - to pass data from shogun to the target interface */

#define SET_SCALAR(fname, mfname, sg_type)	\
void CCmdLineInterface::fname(sg_type scalar)	\
{												\
	const char* filename=set_arg_increment();	\
	CCSVFile f((char*) filename, 'w');		\
	f.mfname(&scalar, 1, 1);					\
}
SET_SCALAR(set_int, set_matrix, int32_t)
SET_SCALAR(set_real, set_matrix, float64_t)
SET_SCALAR(set_bool, CFile::set_matrix, bool)
#undef SET_SCALAR

#define SET_VECTOR(fname, mfname, sg_type)	\
void CCmdLineInterface::fname(const sg_type* vec, int32_t len)	\
{																\
	const char* filename=set_arg_increment();					\
	if (!filename)												\
		SG_ERROR("No filename given to write vector.\n");		\
																\
	CCSVFile f((char*) filename, 'w');						\
	f.mfname(vec, len, 1);										\
}
SET_VECTOR(set_vector, set_matrix, uint8_t)
SET_VECTOR(set_vector, set_matrix, char)
SET_VECTOR(set_vector, set_matrix, int32_t)
SET_VECTOR(set_vector, set_matrix, float32_t)
SET_VECTOR(set_vector, set_matrix, float64_t)
SET_VECTOR(set_vector, set_matrix, int16_t)
SET_VECTOR(set_vector, set_matrix, uint16_t)
#undef SET_VECTOR

#define SET_MATRIX(fname, sg_type)	\
void CCmdLineInterface::fname(const sg_type* matrix, int32_t num_feat, int32_t num_vec)	\
{																						\
	const char* filename=set_arg_increment();											\
	if (!filename)																		\
		SG_ERROR("No filename given to write matrix.\n");								\
																						\
	CCSVFile f((char*) filename, 'w');												\
	f.fname(matrix, num_feat, num_vec);										\
}
SET_MATRIX(set_matrix, uint8_t)
SET_MATRIX(set_matrix, char)
SET_MATRIX(set_matrix, int32_t)
SET_MATRIX(set_matrix, float32_t)
SET_MATRIX(set_matrix, float64_t)
SET_MATRIX(set_matrix, int16_t)
SET_MATRIX(set_matrix, uint16_t)

void CCmdLineInterface::set_sparse_matrix(const SGSparseVector<float64_t>* matrix, int32_t num_feat, int32_t num_vec, int64_t nnz)
{
	const char* filename=set_arg_increment();
	if (!filename)
		SG_ERROR("No filename given to write SPARSE REAL matrix.\n");

	CCSVFile f((char*) filename, 'w');
	f.set_sparse_matrix(matrix, num_feat, num_vec);
}

#define SET_STRING_LIST(fname, sg_type)	\
void CCmdLineInterface::fname(const SGString<sg_type>* strings, int32_t num_str)		\
{																						\
	const char* filename=set_arg_increment();											\
	if (!filename)																		\
		SG_ERROR("No filename given to write CHAR string list.\n");						\
																						\
	CCSVFile f((char*) filename, 'w');												\
	f.fname(strings, num_str);															\
}
SET_STRING_LIST(set_string_list, uint8_t)
SET_STRING_LIST(set_string_list, char)
SET_STRING_LIST(set_string_list, int32_t)
SET_STRING_LIST(set_string_list, int16_t)
SET_STRING_LIST(set_string_list, uint16_t)
#undef SET_STRING_LIST

void CCmdLineInterface::set_attribute_struct(const CDynamicArray<T_ATTRIBUTE>* attrs)
{
}

bool CCmdLineInterface::skip_line(const char* line)
{
	if (!line)
		return true;

	if (line[0]=='\n' ||
		(line[0]=='\r' && line[1]=='\n') ||
		int(line[0])==0)
	{
		return true;
	}

	//SG_PRINT("ascii(0) %d, %c\n", int(line[0]), line[0]);

	char* skipped=SGIO::skip_blanks((char*) line);
	if (skipped[0]==CMDLINE_COMMENT0 || skipped[0]==CMDLINE_COMMENT1)
		return true;

	return false;
}

void CCmdLineInterface::print_prompt()
{
	SG_PRINT( "\033[1;34mshogun\033[0m >> ");
	//SG_PRINT("shogun >> ");
}


char* CCmdLineInterface::get_line(FILE* infile, bool interactive_mode)
{
	char* in=NULL;
	memset(input, 0, sizeof(input));

	if (feof(infile))
		return NULL;

#ifdef HAVE_READLINE
	if (interactive_mode)
	{
		in=readline("\033[1;34mshogun\033[0m >> ");
		if (in)
		{
			strncpy(input, in, sizeof(input));
			add_history(in);
			free(in);
		}
	}
	else
	{
		if (fgets(input, sizeof(input), infile)==NULL)
			return NULL;
		in=input;
	}
#else
	if (interactive_mode)
		print_prompt();
	if (fgets(input, sizeof(input), infile)==NULL)
		return NULL;
	in=input;
#endif

	if (in==NULL)
		return NULL;
	else
		return input;
}

bool CCmdLineInterface::parse_line(char* line)
{
	if (!line)
		return false;

	if (skip_line(line))
		return true;
	else
	{
		((CCmdLineInterface*) interface)->reset(line);
		return interface->handle();
	}
}

#ifdef HAVE_READLINE
char* command_generator(const char *text, int state)
{
	static int list_index, len;
	const char *name;

	/* If this is a new word to complete, initialize now.  This
	 *      includes saving the length of TEXT for efficiency, and
	 *           initializing the index variable to 0. */
	if (!state)
	{
		list_index = 0;
		len = strlen (text);
	}

	/* Return the next name which partially matches from the
	 *      command list. */
	while ((name = sg_methods[list_index].command))
	{
		list_index++;

		if (strncmp (name, text, len) == 0)
			return (strdup(name));
	}

	/* If no names matched, then return NULL. */
	return NULL;
}

/* Attempt to complete on the contents of TEXT.  START and END
 * bound the region of rl_line_buffer that contains the word to
 * complete.  TEXT is the word to complete.  We can use the entire
 * contents of rl_line_buffer in case we want to do some simple
 * parsing.  Returnthe array of matches, or NULL if there aren't
 * any. */
char** shogun_completion (const char *text, int start, int end)
{
	char **matches;

	matches = (char **)NULL;

	/* If this word is at the start of the line, then it is a command
	 *      to complete.  Otherwise it is the name of a file in the
	 *      current
	 *           directory. */
	if (start == 0)
		matches = rl_completion_matches (text, command_generator);

	return (matches);
}
#endif //HAVE_READLINE

int main(int argc, char* argv[])
{
#ifdef HAVE_READLINE
	rl_readline_name = "shogun";
	rl_attempted_completion_function = shogun_completion;
#endif //HAVE_READLINE

	init_shogun(&cmdline_print_message, &cmdline_print_warning,
			&cmdline_print_error, &cmdline_cancel_computations);
	try
	{
		interface=new CCmdLineInterface();
		CCmdLineInterface* intf=(CCmdLineInterface*) interface;

		// interactive
		if (argc<=1)
		{
			while (true)
			{
				char* line=intf->get_line();

				if (!line)
					break;

				try
				{
					intf->parse_line(line);
				}
				catch (ShogunException e) { }

			}

			SG_UNREF(interface);
			exit_shogun();
			return 0;
		}

		// help
		if ( argc>2 || ((argc==2) &&
					( !strcmp(argv[1], "-h") || !strcmp(argv[1], "/?") || !strcmp(argv[1], "--help")) )
		   )
		{
			SG_SPRINT("\n\n");
			SG_SPRINT("usage: shogun [ -h | --help | /? | -i | filename ]\n\n");
			SG_SPRINT("if no options are given shogun enters interactive mode\n");
			SG_SPRINT("if filename is specified the commands will be read and executed from file\n");
			SG_SPRINT("if -i is specified shogun will listen on port 7367 from file\n");
			SG_SPRINT("==hex(sg), *dangerous* as commands from any source are accepted\n\n");

			SG_UNREF(interface);
			exit_shogun();
			return 1;
		}

#ifndef CYGWIN
		// from tcp
		if ( argc==2 && !strcmp(argv[1], "-i"))
		{
			int s=socket(AF_INET, SOCK_STREAM, 0);
			struct sockaddr_in sa;
			sa.sin_family=AF_INET;
			sa.sin_port=htons(7367);
			sa.sin_addr.s_addr=INADDR_ANY;
			bzero(&(sa.sin_zero), 8);

			bind(s, (sockaddr*) (&sa), sizeof(sockaddr_in));
			listen(s, 1);
			int s2=accept(s, NULL, NULL);
			SG_SINFO( "accepting connection\n");

			char input[READLINE_BUFFER_SIZE];
			do
			{
				bzero(input, sizeof(input));
				int length=read(s2, input, sizeof(input));
				if (length>0 && length<(int) sizeof(input))
					input[length]='\0';
				else
				{
					SG_SERROR( "error reading cmdline\n");
					return 1;
				}
			}
			while (intf->parse_line(input));
			SG_UNREF(interface);
			exit_shogun();
			return 0;
		}
#endif

		// from file
		if (argc==2)
		{
			FILE* file=fopen(argv[1], "r");

			if (!file)
			{
				SG_SERROR( "error opening/reading file: \"%s\"",argv[1]);
				SG_UNREF(interface);
				exit_shogun();
				return 1;
			}
			else
			{
				try
				{
					while(!feof(file) && intf->parse_line(intf->get_line(file, false)));
				}
				catch (ShogunException e)
				{
					fclose(file);
					SG_UNREF(interface);
					exit_shogun();
					return 1;
				}

				fclose(file);
				SG_UNREF(interface);
				exit_shogun();
				return 0;
			}
		}

	}
	catch (std::bad_alloc)
	{
		SG_SPRINT("Out of memory error.\n");
		SG_UNREF(interface);
		exit_shogun();
		return 2;
	}
	catch (ShogunException e)
	{
		SG_SPRINT("%s", e.get_exception_string());
		SG_UNREF(interface);
		exit_shogun();
		return 3;
	}
	catch (...)
	{
		SG_SPRINT("Returning from SHOGUN unknown error.");
		SG_UNREF(interface);
		exit_shogun();
		return 4;
	}
}
