#include "error.h"
#include "options.h"
#include "print.h"

#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#ifdef NOPTIONS

static const opt table[] = {
#define OPTION(N,V,L,H,D) \
  { #N, (int)(V), D },
  OPTIONS
#undef OPTION
};

#else

static const opt table[] = {
#define OPTION(N,V,L,H,D) \
  { #N, (int)(V), (int)(L), (int)(H), D },
  OPTIONS
#undef OPTION
};

#endif

#define size_table (sizeof table / sizeof * table)

const opt *kissat_options_begin = table;
const opt *kissat_options_end = table + size_table;

static void
check_table_sorted (void)
{
#ifndef NDEBUG
  const opt *p = 0;
  for (all_options (o))
    if (p && strcmp (p->name, o->name) >= 0)
      kissat_fatal ("option '%s' before option '%s'", p->name, o->name);
    else
      p = o;
#endif
}

const opt *
kissat_options_has (const char *name)
{
  size_t l = 0, m, r = size_table;
  int tmp;
  const opt *o;
  assert (l < r);
  while (l + 1 < r)
    {
      m = l + (r - l) / 2;
      tmp = strcmp (name, (o = table + m)->name);
      if (tmp < 0)
	r = m;
      else if (tmp > 0)
	l = m;
      else
	return o;
    }
  o = table + l;
  tmp = strcmp (o->name, name);
  return tmp ? 0 : o;
}

bool
kissat_parse_option_value (const char *val_str, int *res_ptr)
{
  if (!strcmp (val_str, "true"))
    {
      *res_ptr = 1;
      return true;
    }
  if (!strcmp (val_str, "false"))
    {
      *res_ptr = 0;
      return true;
    }
  int sign = 1;
  const char *p = val_str;
  char ch = *p++;
  if (ch == '-')
    {
      sign = -1;
      ch = *p++;
    }
  if (!isdigit (ch))		// at least one digit
    return false;
  const unsigned max = -(unsigned) INT_MIN;
  unsigned res = ch - '0';
  while (isdigit ((ch = *p++)))
    {
      if (max / 10 < res)
	return false;
      res *= 10;
      const unsigned digit = ch - '0';
      if (max - digit < res)
	return false;
      res += digit;
      if (!res)
	return false;		// invalid '00'
    }
  if (ch == 'e')		// parse '13e5' etc.
    {
      if (!isdigit ((ch = *p++)))	// at least one digit
	return false;
      if (res)
	{
	  if (*p)		// exactly one digit
	    return false;
	  const unsigned digit = ch - '0';
	  for (unsigned i = 0; i < digit; i++)
	    {
	      if (max / 10 < res)
		return false;
	      res *= 10;
	    }
	}
      else			// parse '0^123123123' etc.
	{
	  while (isdigit (ch = *p++))	// arbitrary many digits
	    ;
	  if (ch)
	    return false;
	}
    }
  else if (ch == '^')		// parse '2^11' etc.
    {
      const unsigned base = res;
      if (!isdigit ((ch = *p++)))	// at least one digit
	return false;
      unsigned exp = ch - '0';
      if (base < 2)		// parse '0^123123123' etc.
	{
	  while (isdigit (ch = *p++))	// arbitrary many digits
	    ;
	  if (ch)
	    return false;
	}
      else if (isdigit (ch = *p++))	// parse '2^30' etc.
	{
	  if (*p)		// at most two digits
	    return false;
	  exp *= 10;
	  const unsigned digit = ch - '0';
	  exp += digit;
	  if (!exp)		// '2^00' invalid
	    return false;
	}
      else if (ch)
	return false;
      if (exp)
	for (unsigned i = 1; i < exp; i++)
	  {
	    if (max / base < res)
	      return false;
	    res *= base;
	  }
      else if (base)
	res = 1;		// parse '3^0'
      else
	return false;		// '0^0' invalid
    }
  else if (ch)
    return false;
  assert (res <= max);
  if (sign > 0 && res == max)
    return false;
  res *= sign;
  *res_ptr = res;
  return true;
}

const char *
kissat_parse_option_name (const char *arg, const char *name)
{
  if (arg[0] != '-' || arg[1] != '-')
    return 0;
  const char *p = arg + 2, *q = name;
  while (*p && *p == *q)
    p++, q++;
  if (*q)
    return 0;
  if (*p != '=')
    return 0;
  return p + 1;
}

#ifdef NOPTIONS

void
kissat_init_options (void)
{
  check_table_sorted ();
}

int
kissat_options_get (const char *name)
{
  const opt *o = kissat_options_has (name);
  return o ? o->value : 0;
}

#else

#include "format.h"

#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>

static void
kissat_printf_usage (const char *option, const char *fmt, ...)
{
  va_list ap;
  printf ("  %-26s ", option);
  va_start (ap, fmt);
  vprintf (fmt, ap);
  va_end (ap);
  fputc ('\n', stdout);
}

static void
check_ranges (void)
{
#define OPTION(N,V,L,H,D) \
do { \
  if ((int)(L) > (int)(H)) \
    kissat_fatal ("minimum '%d' of option '%s' above maximum '%d'", \
      (int)(L), #N, (int)(H)); \
  if ((int)(V) < (int)(L)) \
    kissat_fatal ("default value '%d' of option '%s' below minimum '%d'", \
      (int)(V), #N, (int)(L)); \
  if ((int)(V) > (int)(H)) \
    kissat_fatal ("default value '%d' of option '%s' above maximum '%d'", \
      (int)(V), #N, (int)(H)); \
} while (0);
  OPTIONS
#undef OPTION
}

static void
check_name_length (void)
{
#ifndef NDEBUG
#define OPTION(N,V,L,H,D) \
  if (strlen (#N) + 1 > kissat_options_max_name_buffer_size) \
    kissat_fatal ("option '%s' name length %zu " \
      "exceeds maximum name buffer size %zu", \
      #N, strlen (#N), kissat_options_max_name_buffer_size);
  OPTIONS
#undef OPTION
#endif
}

int
kissat_options_get (const options * options, const char *name)
{
  const int *p = kissat_options_ref (options, kissat_options_has (name));
  return p ? *p : 0;
}

int
kissat_options_set_opt (options * options, const opt * o, int value)
{
  assert (kissat_options_begin <= o);
  assert (o < kissat_options_end);
  int *p = (int *) options + (o - table);
  int res = *p;
  if (value == res)
    return res;
  if (value < o->low)
    value = o->low;
  if (value > o->high)
    value = o->high;
  *p = value;
  return res;
}

int
kissat_options_set (options * options, const char *name, int value)
{
  const opt *o = kissat_options_has (name);

  if (!o)
    return 0;

  return kissat_options_set_opt (options, o, value);
}

void
kissat_init_options (options * options)
{
  check_ranges ();
  check_name_length ();
  check_table_sorted ();
#define OPTION(N,V,L,H,D) \
  assert ((L) <= (V)); \
  assert ((V) <= (H)); \
  options->N = (V);
  OPTIONS
#undef OPTION
}

#define FORMAT_OPTION_LIMIT(V) \
  (((V) == INT_MIN || (V) == INT_MAX) ? \
    "." : kissat_format_value (&format, false, (V)))

void
kissat_options_usage (void)
{
  check_ranges ();
  check_name_length ();
  check_table_sorted ();
  format format;
  memset (&format, 0, sizeof format);
#define OPTION(N,V,L,H,D) \
  do { \
    const bool b = ((L) == 0 && (H) == 1); \
    char buffer[96]; \
    if (b) \
      sprintf (buffer, "--%s=<bool>", #N); \
    else \
      { \
	const char * low_str = FORMAT_OPTION_LIMIT ((L)); \
	const char * high_str = FORMAT_OPTION_LIMIT ((H)); \
	sprintf (buffer, "--%s=%s..%s", #N, low_str, high_str); \
      } \
    const char * val_str = kissat_format_value (&format, b, (V)); \
    kissat_printf_usage (buffer, "%s [%s]", D, val_str); \
  } while (0);
  OPTIONS
#undef OPTION
}

bool
kissat_options_parse_arg (const char *arg, char *buffer, int *val_ptr)
{
  if (arg[0] != '-' || arg[1] != '-')
    return false;
  const char *name = arg + 2, *p = name;
  int ch;
  while ((ch = *p) && ch != '=')
    p++;
  if (ch)
    {
      assert (ch == '=');
      const size_t len = p - name;
      if (len >= kissat_options_max_name_buffer_size)
	return false;
      memcpy (buffer, name, len);
      buffer[len] = 0;
      const opt *o = kissat_options_has (buffer);
      if (!o)
	return false;
      int value;
      if (!kissat_parse_option_value (p + 1, &value))
	return false;
      if (value < o->low || value > o->high)
	return false;
      *val_ptr = value;
    }
  else
    {
      int value = 0;
      if (arg[2] == 'n' && arg[3] == 'o' && arg[4] == '-')
	{
	  name += 3;
	  const opt *o = kissat_options_has (name);
	  if (!o || o->low > (value = 0))
	    return false;
	}
      else
	{
	  const opt *o = kissat_options_has (name);
	  if (!o || o->high < (value = 1))
	    return false;
	}
      assert (strlen (name) < kissat_options_max_name_buffer_size);
      strcpy (buffer, name);
      *val_ptr = value;
    }
  return true;
}

static bool
ignore_embedded_option_for_fuzzing (const char *name)
{
#ifndef NEMBEDDED
  if (!strcmp (name, "embedded"))
    return true;
#endif
#ifndef QUIET
  if (!strcmp (name, "quiet"))
    return true;
#endif
  (void) name;
  return false;
}

void
kissat_print_embedded_option_list (void)
{
#define OPTION(N,V,L,H,D) \
  if (!ignore_embedded_option_for_fuzzing (#N)) \
    printf ("c --%s=%d\n", #N, (int) (V));
  OPTIONS
#undef OPTION
}

static bool
ignore_range_option_for_fuzzing (const char *name)
{
#ifdef LOGGING
  if (!strcmp (name, "log"))
    return true;
#endif
#ifndef NEMBEDDED
  if (!strcmp (name, "embedded"))
    return true;
#endif
#ifndef QUIET
  if (!strcmp (name, "quiet"))
    return true;
#endif
  if (!strcmp (name, "reduce"))
    return true;
  if (!strcmp (name, "reluctant"))
    return true;
  if (!strcmp (name, "rephase"))
    return true;
  if (!strcmp (name, "restart"))
    return true;
  return false;
}

void
kissat_print_option_range_list (void)
{
#define OPTION(N,V,L,H,D) \
  if (!ignore_range_option_for_fuzzing (#N)) \
    printf ("%s %d %d %d\n", #N, (int)(L), (int) (V), (int)(H));
  OPTIONS
#undef OPTION
}

#endif
