// clang-format off

const char * usage =
"usage: recats [ <option> ... ] [ <dimacs> ]\n"
"\n"
"where '<option>' is one of the following common options:\n"
"\n"
"  -h             print list of common command line options\n"
"  --help         print complete list of command line options\n"
"\n"
"  -n             no witness (do not print 'v' lines)\n"
"\n"
#ifdef LOGGING
"  -l             increase logging level\n"
#endif
#if !defined(QUIET) && !defined(NOPTIONS)
"  -q             suppress all messages\n"
"  -v             increase verbose level\n"
#endif
;

const char * full_usage =
"\n"
"Or one of the following less common options:\n"
"\n"
"  --banner       print banner\n"
;

const char * usage_suffix =
"Furthermore '<dimacs>' is the input file in DIMACS format.\n"
;

// clang-format on

#include "error.h"
#include "file.h"
#include "handle.h"
#include "internal.h"
#include "kissat.h"
#include "parse.h"
#include "print.h"
#include "resources.h"
#include "statistics.h"
#include "utilities.h"

#include "cover.h"

#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char prefix[64];
static kissat *volatile solver;

static unsigned limit = 1;
static uint64_t recursions;

static uint64_t conflicts;
static uint64_t decisions;
static uint64_t propagations;
static uint64_t reductions;
static uint64_t restarts;

static kissat *init_solver (void) {
  solver = kissat_init ();
  snprintf (prefix, sizeof prefix, "c%" PRIu64 " ", ++recursions);
  kissat_set_prefix (solver, prefix);
  // kissat_set_configuration (solver, "plain");
  kissat_set_option (solver, "factor", 0);
  return solver;
}

static void add_statistics (void) {
  if (!solver)
    return;
  statistics *stats = &solver->statistics;
  conflicts += stats->conflicts;
  decisions += stats->decisions;
  propagations += stats->propagations;
  reductions += stats->reductions;
  restarts += stats->restarts;
}

static void print_statistics (void) {
#ifndef QUIET
  if (GET_OPTION (quiet))
    return;
  if (solver)
    kissat_section (solver, "statistics");
  else
    printf ("%s ---- [ statistics ] --------------------------\n", prefix);
  add_statistics ();
  double time = kissat_process_time ();
  size_t memory = kissat_maximum_resident_set_size ();
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "conflicts:", conflicts, kissat_average (conflicts, time),
          "per second");
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "decisions:", decisions, kissat_average (decisions, conflicts),
          "per conflict");
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "propagations:", propagations,
          kissat_average (propagations * 1e-6, time), "millions/second");
  printf ("%s\n", prefix);
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "recursions:", recursions, kissat_average (conflicts, recursions),
          "conflict interval");
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "reductions:", reductions, kissat_average (conflicts, reductions),
          "conflict interval");
  printf ("%s %-23s %20" PRIu64 " %12.2f %s\n", prefix,
          "restarts:", restarts, kissat_average (restarts, reductions),
          "conflict interval");
  printf ("%s\n", prefix);
  printf ("%s %-27s %16zu %12.2f %s\n", prefix,
          "maximum-resident-set-size:", memory,
          memory / (double) (1u << 20), "MB");
  printf ("%s %-23s %33.2f seconds\n", prefix, "process-time:", time);
  fflush (stdout);
#endif
}

static void kissat_signal_handler (int sig) {
  kissat_signal (solver, "caught", sig);
  print_statistics ();
  kissat_signal (solver, "raising", sig);
  (void) sig;
}

static void recats_fatal (const char *fmt, ...)
    __attribute__ ((format (printf, 1, 2)));

static void recats_fatal (const char *fmt, ...) {
  va_list ap;
  fputs ("recats: fatal error: ", stderr);
  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  fputc ('\n', stderr);
  exit (1);
}

static void recats_error (const char *fmt, ...)
    __attribute__ ((format (printf, 1, 2)));

static void recats_error (const char *fmt, ...) {
  va_list ap;
  fputs ("recats: error: ", stderr);
  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  fputc ('\n', stderr);
  fflush (stderr);
}

#define ERROR(...) \
  do { \
    recats_error (__VA_ARGS__); \
    res = 1; \
    goto DONE; \
  } while (0)

#define SOLVER_NAME "Recats Recursive Kissat SAT Solver"

static int original_max_var;
static int extended_max_var;
static size_t size_values;
static signed char *values;

static void reset () {
  if (values)
    free (values - size_values);
}

static struct {
  size_t units, clauses, literals, extensions;
} cloned;

static void import_literal (int lit) {
  assert (lit);
  assert (lit != INT_MIN);
  size_t idx = abs (lit);
  if (idx <= (size_t) extended_max_var)
    return;
  if (idx >= size_values) {
    size_t new_size_values = size_values ? 2 * size_values : 1;
    while (idx >= new_size_values)
      new_size_values *= 2;
    signed char *new_values = calloc (2 * new_size_values, 1);
    if (!new_values)
      recats_fatal ("out-of-memory allocating %zu value bytes",
                    2 * new_size_values);
    new_values += new_size_values;
    signed char *old_values = values;
    if (old_values)
      for (int other = -extended_max_var; other <= extended_max_var;
           other++)
        new_values[other] = old_values[other];
    values = new_values;
    if (old_values)
      free (old_values - size_values);
    size_values = new_size_values;
  }
  extended_max_var = idx;
}

static void clone_units (void *ptr, int unit) {
  import_literal (unit);
  assert (values[unit] >= 0);
  values[unit] = 1;
  values[-unit] = -1;
  kissat *new_solver = ptr;
  assert (new_solver == solver);
  kissat_add (new_solver, unit);
  kissat_add (new_solver, 0);
  cloned.units++;
}

static void clone_clauses (void *ptr, const int *lits) {
  kissat *new_solver = ptr;
  assert (new_solver == solver);
  int lit;
  for (const int *p = lits; (lit = *p); p++) {
    import_literal (lit);
    kissat_add (new_solver, lit);
    cloned.literals++;
  }
  kissat_add (new_solver, 0);
  cloned.clauses++;
}

static kissat *next_solver (kissat *old_solver, int argc, char **argv) {
  kissat *new_solver = init_solver ();
#ifndef NOPTIONS
  for (int i = 1; i != argc; i++) {
    const char *arg = argv[i];
#ifdef LOGGING
    if (!strcmp (arg, "-l")) {
      int value = GET_OPTION (log);
      if (value < INT_MAX)
        value++;
      kissat_set_option (solver, "log", value);
      continue;
    }
#endif
#if !defined(QUIET)
    if (!strcmp (arg, "-q")) {
      kissat_set_option (solver, "quiet", 1);
      continue;
    }
    if (!strcmp (arg, "-v")) {
      int value = GET_OPTION (verbose);
      if (value < INT_MAX)
        value++;
      kissat_set_option (solver, "verbose", value);
      continue;
    }
#endif
    char name[kissat_options_max_name_buffer_size];
    int value;
    if (kissat_options_parse_arg (arg, name, &value))
      kissat_set_option (solver, name, value);
  }
#else
  (void) argc, (void) argv;
#endif

  kissat_section (new_solver, "cloning");
  memset (&cloned, 0, sizeof cloned);

  kissat_traverse_units (old_solver, new_solver, clone_units);
  kissat_message (solver, "cloned %zu units %.2f%%", cloned.units,
                  kissat_percent (cloned.units, extended_max_var));

  kissat_traverse_clauses (old_solver, new_solver, clone_clauses);
  kissat_message (solver, "cloned %zu clauses size %.2f", cloned.clauses,
                  kissat_average (cloned.literals, cloned.clauses));

  kissat_message (
      solver, "cloned %zu variables %.2f%%",
      solver->statistics.variables_original,
      kissat_percent (solver->statistics.variables_original,
                      old_solver->statistics.variables_original));

  return new_solver;
}

int main (int argc, char **argv) {
  bool witness = true;
  const char *path = 0;
  int res = 0;
  (void) init_solver ();
  for (int i = 1; i != argc; i++) {
    const char *arg = argv[i];
    if (!strcmp (arg, "-h")) {
      fputs (usage, stdout);
      fputc ('\n', stdout);
      fputs (usage_suffix, stdout);
      goto DONE;
    } else if (!strcmp (arg, "--help")) {
#ifndef NOPTIONS
      fputs (usage, stdout);
      fputs (full_usage, stdout);
      fputc ('\n', stdout);
      kissat_options_usage ();
      fputc ('\n', stdout);
      fputs (usage_suffix, stdout);
      goto DONE;
#else
      ERROR ("invalid option '%s' "
             "(compiled without options support)",
             arg);
#endif
    } else if (!strcmp (arg, "-b")) {
      kissat_banner (0, SOLVER_NAME);
      goto DONE;
    } else if (!strcmp (arg, "-l")) {
#ifdef LOGGING
      int value = GET_OPTION (log);
      if (value < INT_MAX)
        value++;
      kissat_set_option (solver, "log", value);
#else
      ERROR ("invalid option '%s' "
             "(compiled without logging support)",
             arg);
#endif
    } else if (!strcmp (arg, "-n")) {
      witness = false;
#if !defined(QUIET) && !defined(NOPTIONS)
    } else if (!strcmp (arg, "-q")) {
      kissat_set_option (solver, "quiet", 1);
    } else if (!strcmp (arg, "-v")) {
      int value = GET_OPTION (verbose);
      if (value < INT_MAX)
        value++;
      kissat_set_option (solver, "verbose", value);
#endif
    } else if (arg[0] == '-' && arg[1] == '-') {
#ifndef NOPTIONS
      char name[kissat_options_max_name_buffer_size];
      int value;
      if (!kissat_options_parse_arg (arg, name, &value))
        ERROR ("invalid long option '%s' (try '-h')", arg);
      kissat_set_option (solver, name, value);
#else
      ERROR ("invalid long option '%s' "
             "(compiled without options support)",
             arg);
#endif
    } else if (arg[0] == '-')
      ERROR ("invalid option '%s' (try '-h')", arg);
    else if (path)
      ERROR ("multiple file arguments '%s' and '%s'", path, arg);
    else if (!kissat_file_readable (arg))
      ERROR ("can not read '%s'", arg);
    else
      path = arg;
  }
#ifndef QUIET
  kissat_section (solver, "banner");
  if (!GET_OPTION (quiet)) {
    kissat_banner (prefix, SOLVER_NAME);
    fflush (stdout);
  }
#endif
  file file;
  if (!path)
    kissat_read_already_open_file (&file, stdin, "<stdin>");
  else if (!kissat_open_to_read_file (&file, path))
    ERROR ("failed to open '%s' for reading", path);
  kissat_init_signal_handler (kissat_signal_handler);
  kissat_section (solver, "parsing");
  kissat_message (solver, "opened and reading %sDIMACS file:",
                  file.compressed ? "compressed " : "");
  kissat_line (solver);
  kissat_message (solver, "  %s", file.path);
  kissat_line (solver);
  uint64_t lineno;
  const char *error =
      kissat_parse_dimacs (solver, 0, &file, &lineno, &original_max_var);
  kissat_close_file (&file);
  if (error)
    ERROR ("%s:%" PRIu64 ": parse error: %s", file.path, lineno, error);
  size_values = (size_t) original_max_var + 1;
  values = calloc (2 * size_values, 1);
  if (!values)
    recats_fatal ("out-of-memory allocating %zu value bytes", size_values);
  values += size_values;
  kissat_section (solver, "solving");
REPEAT:
  kissat_message (solver, "conflict limit %u", limit);
  kissat_line (solver);
  kissat_set_conflict_limit (solver, limit);
  res = kissat_solve (solver);
  if (!res) {
    kissat *old_solver = solver;
    add_statistics ();
    kissat_line (old_solver);
    solver = 0;
    // kissat_dump (old_solver);
    (void) next_solver (old_solver, argc, argv);
    // kissat_dump (solver);
    kissat_release (old_solver);
    char buffer[64];
    snprintf (buffer, sizeof buffer, "recursion %" PRIu64, recursions);
    kissat_section (solver, buffer);
    limit = (UINT_MAX - limit < limit) ? UINT_MAX : 2 * limit;
    goto REPEAT;
  }
  kissat_section (solver, "result");
  if (res == 10)
    printf ("s SATISFIABLE\n");
  else if (res == 20)
    printf ("s UNSATISFIABLE\n");
  else
    printf ("%s UNKNOWN\n", prefix);
  fflush (stdout);
  if (witness && res == 10) {
    printf ("v 0\n");
    fflush (stdout);
  }
  kissat_reset_signal_handler ();
#ifndef QUIET
  print_statistics ();
  kissat_section (solver, "shutting down");
  kissat_message (solver, "exit %d", res);
#endif
DONE:
  kissat_release (solver);
  reset ();
  return res;
}
