/*
 * ptype.c
 */
#include "private.h"
#include "lub/string.h"
#include "lub/ctype.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <stdio.h>

/*---------------------------------------------------------
 * PRIVATE METHODS
 *--------------------------------------------------------- */
static char *clish_ptype_select__get_name(const clish_ptype_t * this,
					  unsigned index)
{
	char *result = NULL;
	const char *arg = lub_argv__get_arg(this->u.select.items, index);
	if (NULL != arg) {
		size_t name_len = 0;
		const char *lbrk = strchr(arg, '(');
		if (NULL != lbrk) {
			name_len = (size_t) (lbrk - arg);
		}
		assert(name_len < strlen(arg));	/* check for syntax error */
		result = lub_string_dupn(arg, name_len);
	}
	return result;
}

/*--------------------------------------------------------- */
static char *clish_ptype_select__get_value(const clish_ptype_t * this,
					   unsigned index)
{
	char *result = NULL;
	const char *arg = lub_argv__get_arg(this->u.select.items, index);
	if (NULL != arg) {
		const char *lbrk = strchr(arg, '(');
		const char *rbrk = strchr(arg, ')');
		const char *value = NULL;
		size_t value_len = 0;
		if (lbrk) {
			value = lbrk + 1;
			if (rbrk) {
				value_len = (size_t) (rbrk - value);
			}
		}
		assert(value_len < strlen(arg));	/* check for syntax error */
		result = lub_string_dupn(value, value_len);
	}
	return result;
}

/*--------------------------------------------------------- */
static void clish_ptype__set_range(clish_ptype_t * this)
{
	char tmp[80];

	/* now set up the range values */
	switch (this->method) {
	/*------------------------------------------------- */
	case CLISH_PTYPE_REGEXP:
		{
			/* 
			 * nothing more to do 
			 */
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_INTEGER:
		{
			/*
			 * Setup the integer range
			 */
			sprintf(tmp,
				"%d..%d",
				this->u.integer.min, this->u.integer.max);
			this->range = lub_string_dup(tmp);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_UNSIGNEDINTEGER:
		{
			/*
			 * Setup the unsigned integer range
			 */
			sprintf(tmp,
				"%u..%u",
				(unsigned int)this->u.integer.min,
				(unsigned int)this->u.integer.max);
			this->range = lub_string_dup(tmp);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_SELECT:
		{
			/*
			 * Setup the selection values to the help text
			 */
			unsigned i;

			for (i = 0;
			     i < lub_argv__get_count(this->u.select.items);
			     i++) {
				char *p = tmp;
				char *name =
				    clish_ptype_select__get_name(this, i);

				if (i > 0) {
					p += sprintf(p, "/");
				}
				p += sprintf(p, "%s", name);
				lub_string_cat(&this->range, tmp);
				lub_string_free(name);
			}
			break;
		}
	/*------------------------------------------------- */
	}
}

/*---------------------------------------------------------
 * PUBLIC META FUNCTIONS
 *--------------------------------------------------------- */
int clish_ptype_bt_compare(const void *clientnode, const void *clientkey)
{
	const clish_ptype_t *this = clientnode;
	const char *key = clientkey;

	return strcmp(this->name, key);
}

/*-------------------------------------------------------- */
void clish_ptype_bt_getkey(const void *clientnode, lub_bintree_key_t * key)
{
	const clish_ptype_t *this = clientnode;

	/* fill out the opaque key */
	strcpy((char *)key, this->name);
}

/*--------------------------------------------------------- */
size_t clish_ptype_bt_offset(void)
{
	return offsetof(clish_ptype_t, bt_node);
}

/*--------------------------------------------------------- */
static const char *method_names[] = {
	"regexp",
	"integer",
	"unsignedInteger",
	"select"
};

/*--------------------------------------------------------- */
const char *clish_ptype_method__get_name(clish_ptype_method_e method)
{
	int max_method = sizeof(method_names) / sizeof(char *);

	if (method >= max_method)
		return NULL;
	return method_names[method];
}

/*--------------------------------------------------------- */
clish_ptype_method_e clish_ptype_method_resolve(const char *name)
{
	clish_ptype_method_e result = CLISH_PTYPE_REGEXP;
	if (NULL != name) {
		unsigned i;
		for (i = 0; i < CLISH_PTYPE_SELECT + 1; i++) {
			if (0 == strcmp(name, method_names[i])) {
				result = (clish_ptype_method_e) i;
				break;
			}
		}
		/* error for incorrect type spec */
		assert(i <= CLISH_PTYPE_SELECT);
	}
	return result;
}

/*--------------------------------------------------------- */
static const char *preprocess_names[] = {
	"none",
	"toupper",
	"tolower"
};

/*--------------------------------------------------------- */
const char *clish_ptype_preprocess__get_name(clish_ptype_preprocess_e
					     preprocess)
{
	return preprocess_names[preprocess];
}

/*--------------------------------------------------------- */
clish_ptype_preprocess_e clish_ptype_preprocess_resolve(const char *name)
{
	clish_ptype_preprocess_e result = CLISH_PTYPE_NONE;
	if (NULL != name) {
		unsigned i;
		for (i = 0; i < CLISH_PTYPE_TOLOWER + 1; i++) {
			if (0 == strcmp(name, preprocess_names[i])) {
				result = (clish_ptype_preprocess_e) i;
				break;
			}
		}
		/* error for incorrect type spec */
		assert((clish_ptype_preprocess_e) i <= CLISH_PTYPE_TOLOWER);
	}
	return result;
}

/*---------------------------------------------------------
 * PUBLIC METHODS
 *--------------------------------------------------------- */

/*--------------------------------------------------------- */
char *clish_ptype_word_generator(clish_ptype_t * this,
				 const char *text, unsigned state)
{
	char *result = NULL;
	if (0 == state) {
		/* first of all simply try to validate the result */
		result = clish_ptype_validate(this, text);
	}
	if (NULL == result) {
		switch (this->method) {
	    /*--------------------------------------------- */
		case CLISH_PTYPE_SELECT:
			{
				if (0 == state) {
					this->last_name = 0;
				}
				while ((result =
					clish_ptype_select__get_name(this,
								     this->
								     last_name++)))
				{
					/* get the next item and check if it is a completion */
					if (result ==
					    lub_string_nocasestr(result,
								 text)) {
						/* found the next completion */
						break;
					}
					lub_string_free(result);
				}
				break;
			}
	    /*--------------------------------------------- */
		case CLISH_PTYPE_INTEGER:
		case CLISH_PTYPE_UNSIGNEDINTEGER:
		case CLISH_PTYPE_REGEXP:
			{
				/* do nothing */
				break;
			}
	    /*--------------------------------------------- */
		}
	}
	return result;
}

/*--------------------------------------------------------- */
static char *clish_ptype_validate_or_translate(const clish_ptype_t * this,
					       const char *text,
					       bool_t translate)
{
	char *result = lub_string_dup(text);
	assert(this->pattern);

	switch (this->preprocess) {
	/*----------------------------------------- */
	case CLISH_PTYPE_NONE:
		{
			break;
		}
	/*----------------------------------------- */
	case CLISH_PTYPE_TOUPPER:
		{
			unsigned char *p = result;
			while (*p) {
				/*lint -e155 Ignoring { }'ed sequence within an expression, 0 assumed 
				 * MACRO implementation uses braces to prevent multiple increments
				 * when called.
				 */
				*p = lub_ctype_toupper(*p);
				p++;
			}
			break;
		}
	/*----------------------------------------- */
	case CLISH_PTYPE_TOLOWER:
		{
			unsigned char *p = result;
			while (*p) {
				*p = lub_ctype_tolower(*p);
				p++;
			}
			break;
		}
	/*----------------------------------------- */
	}
	/* 
	 * now validate according the specified method 
	 */
	switch (this->method) {
	/*------------------------------------------------- */
	case CLISH_PTYPE_REGEXP:
		{
			/* test the regular expression against the string */
			/*lint -e64 Type mismatch (arg. no. 4) */
			/*
			 * lint seems to equate regmatch_t[] as being of type regmatch_t !!!
			 */
			if (0 != regexec(&this->u.regexp, result, 0, NULL, 0)) {
				lub_string_free(result);
				result = NULL;
			}
			/*lint +e64 */
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_INTEGER:
		{
			/* first of all check that this is a number */

			bool_t ok = BOOL_TRUE;
			const char *p = result;

			if (*p == '-') {
				p++;
			}
			while (*p) {
				if (!lub_ctype_isdigit(*p++)) {
					ok = BOOL_FALSE;
					break;
				}
			}
			if (BOOL_TRUE == ok) {
				/* convert and check the range */
				int value = atoi(result);
				if ((value < this->u.integer.min)
				    || (value > this->u.integer.max)) {
					lub_string_free(result);
					result = NULL;
				}
			} else {
				lub_string_free(result);
				result = NULL;
			}
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_UNSIGNEDINTEGER:
		{
			/* first of all check that this is a number */
			bool_t ok = BOOL_TRUE;
			const char *p = result;
			if (*p == '-') {
				p++;
			}
			while (*p) {
				if (!lub_ctype_isdigit(*p++)) {
					ok = BOOL_FALSE;
					break;
				}
			}
			if (BOOL_TRUE == ok) {
				/* convert and check the range */
				unsigned int value = (unsigned int)atoi(result);
				if ((value < (unsigned)this->u.integer.min)
				    || (value > (unsigned)this->u.integer.max)) {
					lub_string_free(result);
					result = NULL;
				}
			} else {
				lub_string_free(result);
				result = NULL;
			}
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_SELECT:
		{
			unsigned i;
			for (i = 0;
			     i < lub_argv__get_count(this->u.select.items);
			     i++) {
				unsigned char *name =
				    clish_ptype_select__get_name(this, i);
				unsigned char *value =
				    clish_ptype_select__get_value(this, i);
				int tmp = lub_string_nocasecmp(result, name);
				lub_string_free((BOOL_TRUE ==
						 translate) ? name : value);
				if (0 == tmp) {
					lub_string_free(result);
					result =
					    ((BOOL_TRUE ==
					      translate) ? value : name);
					break;
				} else {
					lub_string_free((BOOL_TRUE ==
							 translate) ? value :
							name);
				}
			}
			if (i == lub_argv__get_count(this->u.select.items)) {
				/* failed to find a match */
				lub_string_free(result);
				result = NULL;
			}
			break;
		}
	/*------------------------------------------------- */
	}
	return (char *)result;
}

/*--------------------------------------------------------- */
static void
clish_ptype_init(clish_ptype_t * this,
		 const char *name,
		 const char *text,
		 const char *pattern,
		 clish_ptype_method_e method,
		 clish_ptype_preprocess_e preprocess)
{
	assert(name);
	this->name = lub_string_dup(name);
	this->text = NULL;
	this->pattern = NULL;
	this->preprocess = preprocess;
	this->range = NULL;

	/* Be a good binary tree citizen */
	lub_bintree_node_init(&this->bt_node);

	if (NULL != pattern) {
		/* set the pattern for this type */
		clish_ptype__set_pattern(this, pattern, method);
	} else {
		/* The method is regexp by default */
		this->method = CLISH_PTYPE_REGEXP;
	}
	
	if (NULL != text) {
		/* set the help text for this type */
		clish_ptype__set_text(this, text);
	}
}

/*--------------------------------------------------------- */
char *clish_ptype_validate(const clish_ptype_t * this, const char *text)
{
	return clish_ptype_validate_or_translate(this, text, BOOL_FALSE);
}

/*--------------------------------------------------------- */
char *clish_ptype_translate(const clish_ptype_t * this, const char *text)
{
	return clish_ptype_validate_or_translate(this, text, BOOL_TRUE);
}

/*--------------------------------------------------------- */
clish_ptype_t *clish_ptype_new(const char *name,
			       const char *help,
			       const char *pattern,
			       clish_ptype_method_e method,
			       clish_ptype_preprocess_e preprocess)
{
	clish_ptype_t *this = malloc(sizeof(clish_ptype_t));

	if (NULL != this) {
		clish_ptype_init(this, name, help, pattern, method, preprocess);
	}
	return this;
}

/*--------------------------------------------------------- */
static void clish_ptype_fini(clish_ptype_t * this)
{
	lub_string_free(this->name);
	this->name = NULL;
	lub_string_free(this->text);
	this->text = NULL;
	lub_string_free(this->pattern);
	this->pattern = NULL;
	lub_string_free(this->range);
	this->range = NULL;

	switch (this->method) {
	/*------------------------------------------------- */
	case CLISH_PTYPE_REGEXP:
		{
			regfree(&this->u.regexp);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_INTEGER:
	case CLISH_PTYPE_UNSIGNEDINTEGER:
		{
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_SELECT:
		{
			lub_argv_delete(this->u.select.items);
			break;
		}
	/*------------------------------------------------- */
	}
}

/*--------------------------------------------------------- */
void clish_ptype_delete(clish_ptype_t * this)
{
	clish_ptype_fini(this);
	free(this);
}

/*--------------------------------------------------------- */
const char *clish_ptype__get_name(const clish_ptype_t * this)
{
	return (const char *)this->name;
}

/*--------------------------------------------------------- */
const char *clish_ptype__get_text(const clish_ptype_t * this)
{
	return (const char *)this->text;
}

/*--------------------------------------------------------- */
void
clish_ptype__set_pattern(clish_ptype_t * this,
			 const char *pattern, clish_ptype_method_e method)
{
	assert(NULL == this->pattern);
	this->method = method;

	switch (this->method) {
	/*------------------------------------------------- */
	case CLISH_PTYPE_REGEXP:
		{
			int result;

			/* only the expression is allowed */
			lub_string_cat(&this->pattern, "^");
			lub_string_cat(&this->pattern, pattern);
			lub_string_cat(&this->pattern, "$");

			/* compile the regular expression for later use */
			result =
			    regcomp(&this->u.regexp, this->pattern,
				    REG_NOSUB | REG_EXTENDED);
			assert(0 == result);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_INTEGER:
		{
			/* default the range to that of an integer */
			this->u.integer.min = INT_MIN;
			this->u.integer.max = INT_MAX;
			this->pattern = lub_string_dup(pattern);
			/* now try and read the specified range */
			sscanf(this->pattern,
			       "%d..%d",
			       &this->u.integer.min, &this->u.integer.max);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_UNSIGNEDINTEGER:
		{
			/* default the range to that of an unsigned integer */
			this->u.integer.min = 0;
			this->u.integer.max = (int)UINT_MAX;
			this->pattern = lub_string_dup(pattern);
			/* now try and read the specified range */
			sscanf(this->pattern,
			       "%u..%u",
			       (unsigned int *)&this->u.integer.min,
			       (unsigned int *)&this->u.integer.max);
			break;
		}
	/*------------------------------------------------- */
	case CLISH_PTYPE_SELECT:
		{
			this->pattern = lub_string_dup(pattern);
			/* store a vector of item descriptors */
			this->u.select.items = lub_argv_new(this->pattern, 0);
			break;
		}
	/*------------------------------------------------- */
	}
	/* now set up the range details */
	clish_ptype__set_range(this);
}

/*--------------------------------------------------------- */
void clish_ptype__set_text(clish_ptype_t * this, const char *text)
{
	assert(NULL == this->text);
	this->text = lub_string_dup(text);

}

/*--------------------------------------------------------- */
void
clish_ptype__set_preprocess(clish_ptype_t * this,
			    clish_ptype_preprocess_e preprocess)
{
	this->preprocess = preprocess;
}

/*--------------------------------------------------------- */
const char *clish_ptype__get_range(const clish_ptype_t * this)
{
	return (const char *)this->range;
}

/*--------------------------------------------------------- */