/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * XXX This ancient code is gradually being rewritten.
 */

/*
 * Test if an integer falls within a list of ranges specified by a string.
 *
 * <range_spec> ::= <empty> | <list>
 * <list>       ::= <element> | <element> <element-sep-char> <list>
 * <element>    ::= <integer> | <range>
 * <range>      ::= <pair>
 *                | <span-sep-str> <integer>
 *                | <integer> <span-sep-str>
 *                | <span-sep-str>
 * <pair>       ::= <integer1> <span-sep-str> <integer2>
 *
 * By default, <element-sep-char> is a comma and <span-sep-str> is a colon,
 * and <integer> can be an optionally signed integer number.
 * But the syntax can be selected.
 *
 * Note that the <elements> in a <range_spec> do not need to be ordered,
 * but <integer1> must not be greater than <integer2>.
 *
 * With the default syntax, ranges are like those used by [nt]roff
 * and other commands; i.e., a comma separated list of specifiers:
 *	1) n	selects     x such that x = n
 *	2) :n	selects all x such that x <= n
 *	3) n:	selects all x such that x >= n
 *	4) n:m	selects all x such that n <= x <= m
 *	5) :	selects all x
 * where n is an optionally signed integer
 *
 * If the <span-sep-str> includes a '-', only unsigned <integer> values
 * should be used because of the ambiguity in e.g., "-5" (and similarly
 * for '+' and "+5").
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: range.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include "str.h"
#include "range.h"

static Range_syntax default_range_syntax = {
  RANGE_ELEMENT_SEP_CHAR, RANGE_SPAN_SEP_STR, RANGE_ELEMENT_MAX_STR,
RANGE_SIGNED_VALUES
};

static char *log_module_name = "range";

/*
 * Look for an optionally signed integer starting at PP and ending with the
 * end-of-string, an element separator, or an span separator.
 * If found, set VALP to the value,
 * set PP to the first character that follows, and return 1.
 * If no valid integer is found, return 0 and if an invalid integer is found,
 * return -1.
 * XXX no overflow check
 */
static int
getnum_signed(Range_syntax *syntax, char **pp, int *valp)
{
  int sign, val;
  char *p;

  p = *pp;
  if (!isdigit((int) *p) && *p != '-' && *p != '+')
	return(0);

  if (*p == '-') {
	sign = -1;
	p++;
  }
  else {
	sign = 1;
	if (*p == '+')
	  p++;
  }

  if (!isdigit((int) *p))
	return(-1);

  for (val = 0; isdigit((int) *p) && *p != '\0'; p++)
	val = val * 10 + *p - '0';

  if (*p == '\0'
	  || *p == syntax->element_sep_char
	  || strprefix(p, syntax->span_sep_str) != NULL) {
	*pp = p;
	*valp  = sign * val;
	return(1);
  }

  return(-1);
}

/*
 * Look for an unsigned integer starting at PP and ending with the
 * end-of-string, an element separator, or an span separator.
 * If found, set VALP to the value,
 * set PP to the first character that follows, and return 1.
 * If no valid integer is found, return 0 and if an invalid integer is found,
 * return -1.
 * XXX no overflow check
 */
static int
getnum_unsigned(Range_syntax *syntax, char **pp, int *valp)
{
  int val;
  char *p;

  p = *pp;
  if (!isdigit((int) *p))
	return(0);

  for (val = 0; isdigit((int) *p) && *p != '\0'; p++)
	val = val * 10 + *p - '0';

  if (*p == '\0'
	  || *p == syntax->element_sep_char
	  || strprefix(p, syntax->span_sep_str) != NULL) {
	*pp = p;
	*valp  = val;

	return(1);
  }

  return(-1);
}

static int
getnum(Range_syntax *syntax, char **pp, int *valp)
{

  return(syntax->signed_values ?
		 getnum_signed(syntax, pp, valp) : getnum_unsigned(syntax, pp, valp));
}

/*
 * Check if SYNTAX has been properly configured.
 * Return 0 if so, -1 otherwise.
 */
static int
check_range_syntax(Range_syntax *syntax)
{
  char el_ch, *p, *span_str;

  el_ch = syntax->element_sep_char;
  span_str = syntax->span_sep_str;

  if (strchr(span_str, (int) el_ch) != NULL)
	return(-1);

  for (p = span_str; *p != '\0'; p++) {
	if (!ispunct((int) *p) && *p != ' ')
	  return(-1);
	if (syntax->signed_values && (*p == '+' || *p == '-'))
	  return(-1);
  }

  if (!ispunct((int) el_ch) && el_ch != ' ')
	return(-1);

  if (syntax->signed_values) {
	if (el_ch == '+' || el_ch == '-')
	  return(-1);
  }

  return(0);
}
	  
/*
 * Return (a copy) of the current default syntax.
 * If SYNTAX is non-NULL, set it (a copy) to be the default syntax.
 */
Range_syntax *
range_set_default_syntax(Range_syntax *syntax)
{
  Range_syntax *current_syntax, *rs;

  current_syntax = ALLOC(Range_syntax);
  *current_syntax = default_range_syntax;

  if (syntax != NULL)
	default_range_syntax = *syntax;

  return(current_syntax);
}

static int
range_internal(int do_eval, int num, char *range, Range_syntax *range_syntax,
			   char **errmsg)
{
  char *p, *q;
  int type1, type2, val1, val2;
  Range_syntax *syntax;

  if (range_syntax != NULL)
	syntax = range_syntax;
  else
	syntax = &default_range_syntax;

  if (check_range_syntax(syntax) == -1) {
	*errmsg = "Invalid range syntax";
	return(-1);
  }

  if (*range == '\0')
	return(0);

  p = range;
  while (1) {
	if ((type1 = getnum(syntax, &p, &val1)) == -1) {
	  *errmsg = "Bad first number in range";
	  return(-1);
	}

	if (type1 == 1) {
	  if (*p == '\0') {
		if (do_eval && num == val1)
		  return(1);

		return(0);
	  }

	  if (*p == syntax->element_sep_char) {
		if (do_eval && num == val1)
		  return(1);
		p++;
		continue;
	  }
	  /* Must be a pair... fall through. */
	}

	if (type1 == 0
		&& (q = strprefix(p, syntax->element_max_str)) != NULL) {
	  p = q;
	  if (*p == syntax->element_sep_char) {
		if (do_eval)
		  return(1);
		p++;
		continue;
	  }
	  if (*p == '\0')
		return(do_eval ? 1 : 0);
	}

	if ((q = strprefix(p, syntax->span_sep_str)) == NULL) {
	  *errmsg = "Missing first number in range";
	  return(-1);
	}

	if (type1 == 0) {
	  if (syntax->signed_values)
		val1 = DACS_MIN_INT;
	  else
		val1 = 0;
	}

	p = q;
	/* The second number can be elided. */
	val2 = DACS_MAX_INT;

	if (*p == '\0')
	  return(do_eval ? (num >= val1 && num <= val2) : 0);

	if (*p == syntax->element_sep_char) {
	  if (do_eval && (num >= val1 && num <= val2))
		return(1);
	  p++;
	  continue;
	}

	if ((type2 = getnum(syntax, &p, &val2)) == -1) {
	  *errmsg = "Bad second number in range";
	  return(-1);
	}

	if (type2 == 0) {
	  if ((q = strprefix(p, syntax->element_max_str)) == NULL) {
		*errmsg = "Bad second number in range";
		return(-1);
	  }
	  val2 = DACS_MAX_INT;
	  p = q;
	}

	if (val1 > val2) {
	  *errmsg = "Range values reversed";
	  return(-1);
	}

	if (do_eval && (num >= val1 && num <= val2))
	  return(1);

	if (*p == '\0')
	  return(0);

	if (*p != syntax->element_sep_char) {
	  *errmsg = "Invalid character";
	  return(-1);
	}

	p++;
  }
}

/*
 * Check if RANGE is a syntactically valid specification.
 * Return NULL if it is, otherwise an error message.
 */
int
range_is_valid(char *range, Range_syntax *range_syntax, char **errmsg)
{

  return(range_internal(0, 0, range, range_syntax, errmsg));
}

/*
 * Return 1 if the given number is in the specified range,
 * -1 if there is an error in the range specification,
 * 0 otherwise
 *
 * The routine used to print an error message if the range is strange - since
 * this might not always be desirable, the original API was changed.
 *
 * I've left the error checking in range_test(); it is redundant (I hope!)
 * Nov/85 BJB
 */
int
range_test(int num, char *range, Range_syntax *range_syntax, char **errmsg)
{

  return(range_internal(1, num, range, range_syntax, errmsg));
}

static int
selftest_range(FILE *out, int num, char *range_spec, Range_syntax *rs,
			   int result)
{
  int rc;
  char *errmsg;

  fprintf(out, "Range: test if %d is%s in \"%s\" ... ", num,
		  (result == 0) ? " not" : "", range_spec);

  if ((rc = range_test(num, range_spec, rs, &errmsg)) != result) {
	if (rc == -1)
	  fprintf(out, "error: %s\n", errmsg);
	else
	  fprintf(out, "failed\n");
	return(-1);
  }

  if (rc == -1)
	fprintf(out, "ok (correctly rejected)\n");
  else
	fprintf(out, "ok\n");

  return(0);
}

int
range_selftest(FILE *fp)
{
  FILE *out;
  Range_syntax *rs;

  if (fp == NULL)
	out = stderr;
  else
	out = fp;

  if (selftest_range(out, 1, "1,2:4,99", NULL, 1) == -1)
	return(-1);

  if (selftest_range(out, 2, "1,2:4,99", NULL, 1) == -1)
	return(-1);

  if (selftest_range(out, 3, "1,2:4,99", NULL, 1) == -1)
	return(-1);

  if (selftest_range(out, 99, "1,2:4,99", NULL, 1) == -1)
	return(-1);

  if (selftest_range(out, 0, "1,2:4,99", NULL, 0) == -1)
	return(-1);

  if (selftest_range(out, 5, "1,2:4,99", NULL, 0) == -1)
	return(-1);

  if (selftest_range(out, 100, "1,2:4,99", NULL, 0) == -1)
	return(-1);

  if (selftest_range(out, 17, ":", NULL, 1) == -1)
	return(-1);

  if (selftest_range(out, 17, "20:", NULL, 0) == -1)
	return(-1);

  if (selftest_range(out, 17, ":17", NULL, 1) == -1)
	return(-1);

  rs = range_set_default_syntax(NULL);
  rs->span_sep_str = "-";
  rs->signed_values = 0;

  if (selftest_range(out, 100, "1,2-4,99", rs, 0) == -1)
	return(-1);

  if (selftest_range(out, 3, "1,2-4,99", rs, 1) == -1)
	return(-1);

  return(0);
}

#ifdef PROG
int
main(int argc, char **argv)
{

  fprintf(stderr, "Testing ranges...\n");
  if (range_selftest(stderr) == -1) {
	fprintf(stderr, "Range self-test failed!\n");
	exit(1);
  }

  fprintf(stderr, "All range tests succeeded!\n");

  exit(0);
}
#endif
