#include "allocate.h"
#include "collect.h"
#include "inline.h"
#include "print.h"
#include "rank.h"
#include "reduce.h"
#include "report.h"
#include "trail.h"

#include <inttypes.h>
#include <math.h>

bool kissat_reducing (kissat *solver) {
  if (!GET_OPTION (reduce))
    return false;
  if (!solver->statistics.clauses_redundant)
    return false;
  if (CONFLICTS < solver->limits.reduce.conflicts)
    return false;
  return true;
}

typedef struct reducible reducible;

struct reducible {
  uint64_t rank;
  size_t ref;
};

#define RANK_REDUCIBLE(RED) (RED).rank

// clang-format off
typedef STACK (reducible) reducibles;
// clang-format on

static size_t collect_reducibles (kissat *solver, reducibles *reds,
                                  reference start_ref) {
  assert (start_ref != INVALID_REF);
  assert (start_ref <= SIZE_STACK (solver->arena));
  ward *const arena = BEGIN_STACK (solver->arena);
  clause *start = (clause *) (arena + start_ref);
  const clause *const end = (clause *) END_STACK (solver->arena);
  assert (start < end);
  while (start != end && !start->redundant)
    start = kissat_next_clause (start);
  if (start == end) {
    solver->first_reducible = INVALID_REF;
    LOG ("no reducible clause candidate left");
    return 0;
  }
  const reference redundant = (ward *) start - arena;
#ifdef LOGGING
  if (redundant < solver->first_reducible)
    LOG ("updating start of redundant clauses from %zu to %zu",
         (size_t) solver->first_reducible, (size_t) redundant);
  else
    LOG ("no update to start of redundant clauses %zu",
         (size_t) solver->first_reducible);
#endif
  solver->first_reducible = redundant;
  const unsigned keep_glue = GET_OPTION (reducekeepglue);
  const unsigned keep_size = GET_OPTION (reducekeepsize);
  size_t sizes = 0;
  for (clause *c = start; c != end; c = kissat_next_clause (c)) {
    if (!c->redundant)
      continue;
    if (c->garbage)
      continue;
    if (c->used) {
      c->used = false;
      continue;
    }
    if (c->reason)
      continue;
    if (c->glue <= keep_glue)
      continue;
    if (c->size <= keep_size)
      continue;
    sizes += c->size;
    assert (kissat_clause_in_arena (solver, c));
    reducible red;
    const unsigned negative_size = ~c->size;
    const unsigned negative_glue = ~c->glue;
    red.rank = ((uint64_t) negative_size << 32) | negative_glue;
    red.ref = (ward *) c - arena;
    PUSH_STACK (*reds, red);
  }
  if (EMPTY_STACK (*reds)) {
    kissat_phase (solver, "reduce", GET (reductions),
                  "did not find any reducible redundant clause");
    return 0;
  }
  return sizes;
}

#define USEFULNESS RANK_REDUCIBLE

static void sort_reducibles (kissat *solver, reducibles *reds) {
  RADIX_STACK (reducible, uint64_t, *reds, USEFULNESS);
}

static void mark_less_useful_clauses_as_garbage (kissat *solver,
                                                 size_t sizes,
                                                 reducibles *reds) {
  const double fraction = GET_OPTION (reducetarget) * 1e-3;
  size_t target_sizes = sizes * fraction;
#ifndef QUIET
  size_t reducible_clauses = SIZE_STACK (*reds);
  statistics *statistics = &solver->statistics;
  const size_t total_clauses =
      statistics->clauses_irredundant + statistics->clauses_redundant;
  kissat_phase (solver, "reduce", GET (reductions),
                "reducing %zu (%.0f%%) out of %zu reducible literals "
                "in %zu out of %zu clauses %.0f%%",
                target_sizes, kissat_percent (target_sizes, sizes), sizes,
                reducible_clauses, total_clauses,
                kissat_percent (reducible_clauses, total_clauses));
#endif
  size_t reduced_sizes = 0, reduced_clauses = 0;
  ward *arena = BEGIN_STACK (solver->arena);
  const reducible *const begin = BEGIN_STACK (*reds);
  const reducible *const end = END_STACK (*reds);
  for (const reducible *p = begin; reduced_sizes < target_sizes && p != end; p++) {
    clause *c = (clause *) (arena + p->ref);
    assert (kissat_clause_in_arena (solver, c));
    assert (!c->garbage);
    assert (!c->reason);
    assert (c->redundant);
    LOGCLS (c, "reducing");
    kissat_mark_clause_as_garbage (solver, c);
    reduced_sizes += c->size;
    reduced_clauses++;
  }
  ADD (clauses_reduced, reduced_clauses);
  kissat_phase (solver, "reduce", GET (reductions),
                "marked %zu clauses as garbage out of %zu reducible %.0f%%",
                reduced_clauses, reducible_clauses,
                kissat_percent (reduced_clauses, reducible_clauses));
}

int kissat_reduce (kissat *solver) {
  START (reduce);
  INC (reductions);
  kissat_phase (solver, "reduce", GET (reductions),
                "reduce limit %" PRIu64 " hit after %" PRIu64 " conflicts",
                solver->limits.reduce.conflicts, CONFLICTS);
  bool compact = kissat_compacting (solver);
  reference start = compact ? 0 : solver->first_reducible;
  if (start != INVALID_REF) {
#ifndef QUIET
    size_t arena_size = SIZE_STACK (solver->arena);
    size_t words_to_sweep = arena_size - start;
    size_t bytes_to_sweep = sizeof (word) * words_to_sweep;
    kissat_phase (solver, "reduce", GET (reductions),
                  "reducing clauses after offset %" REFERENCE_FORMAT
                  " in arena",
                  start);
    kissat_phase (solver, "reduce", GET (reductions),
                  "reducing %zu words %s %.0f%%", words_to_sweep,
                  FORMAT_BYTES (bytes_to_sweep),
                  kissat_percent (words_to_sweep, arena_size));
#endif
    if (kissat_flush_and_mark_reason_clauses (solver, start)) {
      reducibles reds;
      INIT_STACK (reds);
      size_t sizes = collect_reducibles (solver, &reds, start);
      if (sizes) {
        sort_reducibles (solver, &reds);
        mark_less_useful_clauses_as_garbage (solver, sizes, &reds);
        RELEASE_STACK (reds);
        kissat_sparse_collect (solver, compact, start);
      } else if (compact)
        kissat_sparse_collect (solver, compact, start);
      else
        kissat_unmark_reason_clauses (solver, start);
    } else
      assert (solver->inconsistent);
  } else
    kissat_phase (solver, "reduce", GET (reductions), "nothing to reduce");
  kissat_classify (solver);
  UPDATE_CONFLICT_LIMIT (reduce, reductions, SQRT, false);
  solver->last.conflicts.reduce = CONFLICTS;
  REPORT (0, '-');
  STOP (reduce);
  return solver->inconsistent ? 20 : 0;
}
