#include "factorl.h"

#include "bump.h"
#include "clause.h"
#include "dense.h"
#include "heap.h"
#include "import.h"
#include "inline.h"
#include "inlineheap.h"
#include "inlinequeue.h"
#include "inlinevector.h"
#include "internal.h"
#include "logging.h"
#include "print.h"
#include "report.h"
#include "sort.h"
#include "terminate.h"
#include "vector.h"
#include "watch.h"

#include <inttypes.h>
#include <string.h>

#define FACTOR 1
#define QUOTIENT 2
#define NOUNTED 4

struct quotient {
  size_t id;
  struct quotient *prev, *next;
  unsigned factor;
  statches clauses;
  sizes matches;
  size_t matched;
};
typedef struct quotient quotient;

struct scores {
  double *score;
  unsigneds scored;
};
typedef struct scores scores;

struct factoring {
  kissat *solver;
  size_t size, allocated;
  unsigned initial;
  unsigned *count;
  scores *scores;
  unsigned hops;
  unsigned bound;
  unsigneds fresh;
  unsigneds counted;
  unsigneds nounted;
  references qlauses;
  uint64_t limit;
  struct {
    quotient *first, *last;
  } quotients;
  heap schedule;
};
typedef struct factoring factoring;

static int simplify_clause_under_assignment_learnt (kissat *solver) {
  unsigned *begin_lits = BEGIN_STACK (solver->clause);
  unsigned *end_lits = END_STACK (solver->clause);

  unsigned *p_read = begin_lits;
  unsigned *p_write = begin_lits;
  bool clause_is_true = false;

  while (p_read != end_lits) {
    unsigned lit = *p_read++;
    value lit_value = VALUE (lit);

    if (lit_value > 0 && LEVEL (lit) == 0) {
      clause_is_true = true;
      break;
    }
    if (lit_value)
      continue;
    if (p_write + 1 != p_read)
      *p_write = lit;
    p_write++;
  }

  size_t new_size = p_write - begin_lits;
  if (clause_is_true)
    return 0;
  if (!new_size) {
    solver->inconsistent = true;
    return -1;
  }
  SET_END_OF_STACK (solver->clause, p_write);
  return (int) new_size;
}

static void init_factoring_learnt (kissat *solver, factoring *factoring) {
  memset (factoring, 0, sizeof *factoring);

  factoring->solver = solver;
  factoring->initial = factoring->allocated = factoring->size = LITS;
  factoring->limit = 0;
  factoring->bound = 0;
  factoring->hops = 0;

  CALLOC (factoring->count, factoring->allocated);
}

static void release_quotients_learnt (factoring *factoring) {
  kissat *const solver = factoring->solver;
  mark *marks = solver->marks;

  for (quotient *q = factoring->quotients.first, *next; q; q = next) {
    next = q->next;
    unsigned factor = q->factor;
    marks[factor] = 0;
    RELEASE_STACK (q->clauses);
    RELEASE_STACK (q->matches);
    kissat_free (solver, q, sizeof *q);
  }

  factoring->quotients.first = factoring->quotients.last = 0;
}

static void release_factoring_learnt (factoring *factoring) {
  kissat *const solver = factoring->solver;

  DEALLOC (factoring->count, factoring->allocated);
  RELEASE_STACK (factoring->counted);
  RELEASE_STACK (factoring->nounted);
  RELEASE_STACK (factoring->fresh);
  RELEASE_STACK (factoring->qlauses);

  release_quotients_learnt (factoring);
  kissat_release_heap (solver, &factoring->schedule);
}

static void update_candidate_learnt (factoring *factoring, unsigned lit) {
  heap *cands = &factoring->schedule;
  kissat *const solver = factoring->solver;
  const size_t size = SIZE_WATCHES (solver->watches[lit]);

  if (size > 1) {
    kissat_adjust_heap (solver, cands, lit);
    kissat_update_heap (solver, cands, lit, size);
    if (!kissat_heap_contains (cands, lit))
      kissat_push_heap (solver, cands, lit);
  } else if (kissat_heap_contains (cands, lit)) {
    kissat_pop_heap (solver, cands, lit);
  }
}

static void schedule_factorization_learnt (factoring *factoring) {
  kissat *const solver = factoring->solver;
  flags *flags = solver->flags;

  for (all_variables (idx)) {
    if (!ACTIVE (idx))
      continue;

    struct flags *f = flags + idx;
    const unsigned lit = LIT (idx);
    const unsigned not_lit = NOT (lit);

    if (f->factorl != 3u)
      f->factorl |= 3u;
    update_candidate_learnt (factoring, lit);
    update_candidate_learnt (factoring, not_lit);
  }
}

static quotient *new_quotient_learnt (factoring *factoring, unsigned factor) {
  kissat *const solver = factoring->solver;
  mark *marks = solver->marks;
  marks[factor] = FACTOR;

  quotient *res = kissat_malloc (solver, sizeof *res);
  memset (res, 0, sizeof *res);
  res->factor = factor;

  quotient *last = factoring->quotients.last;
  if (last) {
    last->next = res;
    res->id = last->id + 1;
  } else {
    factoring->quotients.first = res;
  }
  factoring->quotients.last = res;
  res->prev = last;
  return res;
}

static size_t first_factor_learnt (factoring *factoring, unsigned factor) {
  kissat *const solver = factoring->solver;
  watches *all_watches = solver->watches;
  watches *factor_watches = all_watches + factor;

  quotient *quotient = new_quotient_learnt (factoring, factor);
  statches *clauses = &quotient->clauses;

  for (all_binary_large_watches (watch, *factor_watches))
    PUSH_STACK (*clauses, watch);

  return SIZE_STACK (*clauses);
}

static void clear_nounted_learnt (kissat *solver, unsigneds *nounted) {
  mark *marks = solver->marks;
  for (all_stack (unsigned, lit, *nounted))
    marks[lit] &= ~NOUNTED;
  CLEAR_STACK (*nounted);
}

static void clear_qlauses_learnt (kissat *solver, references *qlauses) {
  ward *const arena = BEGIN_STACK (solver->arena);
  for (all_stack (reference, ref, *qlauses)) {
    clause *const c = (clause *) (arena + ref);
    c->quotient = false;
  }
  CLEAR_STACK (*qlauses);
}

static double watches_score_learnt (factoring *factoring, unsigned lit) {
  kissat *const solver = factoring->solver;
  watches *watches = solver->watches + lit;
  return SIZE_WATCHES (*watches);
}

static double tied_next_factor_score_learnt (factoring *factoring,
                                             unsigned lit) {
  return watches_score_learnt (factoring, lit);
}

static unsigned next_factor_learnt (factoring *factoring,
                                    unsigned *next_count_ptr) {
  quotient *last_quotient = factoring->quotients.last;
  statches *last_clauses = &last_quotient->clauses;

  kissat *const solver = factoring->solver;
  watches *all_watches = solver->watches;
  unsigned *count = factoring->count;
  unsigneds *counted = &factoring->counted;
  references *qlauses = &factoring->qlauses;

  ward *const arena = BEGIN_STACK (solver->arena);
  mark *marks = solver->marks;
  const unsigned initial = factoring->initial;

  for (all_stack (watch, quotient_watch, *last_clauses)) {
    if (quotient_watch.type.binary) {
      const unsigned q = quotient_watch.binary.lit;
      watches *q_watches = all_watches + q;
      for (all_binary_large_watches (next_watch, *q_watches)) {
        if (!next_watch.type.binary)
          continue;
        const unsigned next = next_watch.binary.lit;

        if (next > initial)
          continue;
        if (marks[next] & FACTOR)
          continue;
        const unsigned next_idx = IDX (next);
        if (!ACTIVE (next_idx))
          continue;

        if (!count[next])
          PUSH_STACK (*counted, next);
        count[next]++;
      }
    } else {
      const reference c_ref = quotient_watch.large.ref;
      clause *const c = (clause *) (arena + c_ref);

      unsigned min_lit = INVALID_LIT, factors = 0;
      size_t min_size = 0;
      for (all_literals_in_clause (other, c)) {
        if (marks[other] & FACTOR) {
          if (factors++)
            break;
        } else {
          marks[other] |= QUOTIENT;
          watches *other_watches = all_watches + other;
          const size_t other_size = SIZE_WATCHES (*other_watches);
          if (min_lit != INVALID_LIT && min_size <= other_size)
            continue;
          min_lit = other;
          min_size = other_size;
        }
      }

      if (factors == 1) {
        watches *min_watches = all_watches + min_lit;
        unsigned c_size = c->size;
        unsigneds *nounted = &factoring->nounted;

        for (all_binary_large_watches (min_watch, *min_watches)) {
          if (min_watch.type.binary)
            continue;

          const reference d_ref = min_watch.large.ref;
          if (c_ref == d_ref)
            continue;

          clause *const d = (clause *) (arena + d_ref);
          if (d->quotient)
            continue;
          if (d->size != c_size)
            continue;

          unsigned next = INVALID_LIT;
          for (all_literals_in_clause (other, d)) {
            const mark mark = marks[other];
            if (mark & QUOTIENT)
              continue;
            if (mark & FACTOR)
              goto CONTINUE_WITH_NEXT_MIN_WATCH;
            if (mark & NOUNTED)
              goto CONTINUE_WITH_NEXT_MIN_WATCH;
            if (next != INVALID_LIT)
              goto CONTINUE_WITH_NEXT_MIN_WATCH;
            next = other;
          }

          if (next > initial)
            continue;
          const unsigned next_idx = IDX (next);
          if (!ACTIVE (next_idx))
            continue;

          marks[next] |= NOUNTED;
          PUSH_STACK (*nounted, next);
          d->quotient = true;
          PUSH_STACK (*qlauses, d_ref);

          if (!count[next])
            PUSH_STACK (*counted, next);
          count[next]++;

        CONTINUE_WITH_NEXT_MIN_WATCH:;
        }
        clear_nounted_learnt (solver, nounted);
      }

      for (all_literals_in_clause (other, c))
        marks[other] &= ~QUOTIENT;
    }
  }

  clear_qlauses_learnt (solver, qlauses);

  unsigned ties = 0;
  unsigned next_count = 0, next = INVALID_LIT;
  for (all_stack (unsigned, lit, *counted)) {
    const unsigned lit_count = count[lit];
    if (lit_count < next_count)
      continue;

    if (lit_count == next_count) {
      ties++;
    } else {
      next_count = lit_count;
      next = lit;
      ties = 1;
    }
  }

  if (next_count < 2) {
    next = INVALID_LIT;
  } else if (ties > 1) {
    double next_score = -1;
    for (all_stack (unsigned, lit, *counted)) {
      const unsigned lit_count = count[lit];
      if (lit_count != next_count)
        continue;
      double lit_score = tied_next_factor_score_learnt (factoring, lit);

      if (lit_score <= next_score)
        continue;
      next_score = lit_score;
      next = lit;
    }
  } else {
    assert (ties == 1);
  }

  for (all_stack (unsigned, lit, *counted))
    count[lit] = 0;
  CLEAR_STACK (*counted);
  *next_count_ptr = next_count;
  return next;
}

static void factorize_next_learnt (factoring *factoring, unsigned next,
                                   unsigned expected_next_count) {
  quotient *last_quotient = factoring->quotients.last;
  quotient *next_quotient = new_quotient_learnt (factoring, next);

  kissat *const solver = factoring->solver;
  watches *all_watches = solver->watches;
  ward *const arena = BEGIN_STACK (solver->arena);
  mark *marks = solver->marks;

  statches *last_clauses = &last_quotient->clauses;
  statches *next_clauses = &next_quotient->clauses;

  sizes *matches = &next_quotient->matches;
  references *qlauses = &factoring->qlauses;

  size_t i = 0;
  for (all_stack (watch, last_watch, *last_clauses)) {
    if (last_watch.type.binary) {
      const unsigned q = last_watch.binary.lit;
      watches *q_watches = all_watches + q;

      for (all_binary_large_watches (q_watch, *q_watches))
        if (q_watch.type.binary && q_watch.binary.lit == next) {
          PUSH_STACK (*next_clauses, last_watch);
          PUSH_STACK (*matches, i);
          break;
        }
    } else {
      const reference c_ref = last_watch.large.ref;
      clause *const c = (clause *) (arena + c_ref);

      unsigned min_lit = INVALID_LIT, factors = 0;
      size_t min_size = 0;

      for (all_literals_in_clause (other, c)) {
        if (marks[other] & FACTOR) {
          if (factors++)
            break;
        } else {
          marks[other] |= QUOTIENT;
          watches *other_watches = all_watches + other;
          const size_t other_size = SIZE_WATCHES (*other_watches);
          if (min_lit != INVALID_LIT && min_size <= other_size)
            continue;
          min_lit = other;
          min_size = other_size;
        }
      }

      if (factors == 1) {
        watches *min_watches = all_watches + min_lit;
        unsigned c_size = c->size;

        for (all_binary_large_watches (min_watch, *min_watches)) {
          if (min_watch.type.binary)
            continue;

          const reference d_ref = min_watch.large.ref;
          if (c_ref == d_ref)
            continue;

          clause *const d = (clause *) (arena + d_ref);
          if (d->quotient)
            continue;
          if (d->size != c_size)
            continue;

          for (all_literals_in_clause (other, d)) {
            const mark mark = marks[other];
            if (mark & QUOTIENT)
              continue;
            if (other != next)
              goto CONTINUE_WITH_NEXT_MIN_WATCH;
          }

          PUSH_STACK (*next_clauses, min_watch);
          PUSH_STACK (*matches, i);
          PUSH_STACK (*qlauses, d_ref);
          d->quotient = true;
          break;
        CONTINUE_WITH_NEXT_MIN_WATCH:;
        }
      }

      for (all_literals_in_clause (other, c))
        marks[other] &= ~QUOTIENT;
    }
    i++;
  }
  clear_qlauses_learnt (solver, qlauses);
  (void) expected_next_count;
}

static quotient *best_quotient_learnt (factoring *factoring,
                                       size_t *best_reduction_ptr) {
  kissat *const solver = factoring->solver;

  size_t factors = 1, best_reduction = 0;
  quotient *best = 0;

  for (quotient *q = factoring->quotients.first; q; q = q->next) {
    size_t quotients = SIZE_STACK (q->clauses);
    size_t before_factorization = quotients * factors;
    size_t after_factorization = quotients + factors;

    if (before_factorization > after_factorization) {
      size_t delta = before_factorization - after_factorization;
      if (!best || best_reduction < delta) {
        best_reduction = delta;
        best = q;
      }
    }
    factors++;
  }

  if (!best)
    return 0;

  *best_reduction_ptr = best_reduction;
  (void) solver;
  return best;
}

static void flush_unmatched_clauses_learnt (kissat *solver, quotient *q) {
  quotient *prev = q->prev;
  sizes *q_matches = &q->matches, *prev_matches = &prev->matches;
  statches *q_clauses = &q->clauses, *prev_clauses = &prev->clauses;

  const size_t n = SIZE_STACK (*q_clauses);

  size_t i = 0;
  bool prev_is_first = !prev->id;
  while (i != n) {
    size_t j = PEEK_STACK (*q_matches, i);
    if (!prev_is_first) {
      size_t matches = PEEK_STACK (*prev_matches, j);
      POKE_STACK (*prev_matches, i, matches);
    }
    watch watch = PEEK_STACK (*prev_clauses, j);
    POKE_STACK (*prev_clauses, i, watch);
    i++;
  }

  if (!prev_is_first)
    RESIZE_STACK (*prev_matches, n);
  RESIZE_STACK (*prev_clauses, n);

  (void) solver;
}

static void add_factored_divider_learnt (factoring *factoring, unsigned fresh,
                                         unsigned other) {
  kissat *const solver = factoring->solver;

  unsigneds *clause = &solver->clause;
  CLEAR_STACK (*clause);
  PUSH_STACK (*clause, fresh);
  PUSH_STACK (*clause, other);

  int size = simplify_clause_under_assignment_learnt (solver);
  if (size == 1) {
    kissat_learned_unit (solver, TOP_STACK (solver->clause));
  } else if (size > 1) {
    kissat_new_redundant_clause (solver, SIZE_STACK (solver->clause));
  }

  CLEAR_STACK (*clause);
}

static void add_factored_quotient_learnt (factoring *factoring, quotient *q,
                                          unsigned not_fresh) {
  kissat *const solver = factoring->solver;

  for (all_stack (watch, watch, q->clauses)) {
    if (watch.type.binary) {
      const unsigned other = watch.binary.lit;
      add_factored_divider_learnt (factoring, not_fresh, other);
    } else {
      const reference c_ref = watch.large.ref;
      clause *const c = kissat_dereference_clause (solver, c_ref);
      unsigneds *clause = &solver->clause;

      const unsigned factor = q->factor;
      PUSH_STACK (*clause, not_fresh);
      for (all_literals_in_clause (other, c)) {
        if (other == factor)
          continue;
        PUSH_STACK (*clause, other);
      }

      int size = simplify_clause_under_assignment_learnt (solver);
      if (size == 1) {
        kissat_learned_unit (solver, TOP_STACK (solver->clause));
      } else if (size > 1) {
        kissat_new_redundant_clause (solver, SIZE_STACK (solver->clause));
      }

      CLEAR_STACK (*clause);
    }
  }
}

static void eagerly_remove_watch_learnt (kissat *solver, watches *watches,
                                         watch needle) {
  watch *p = BEGIN_WATCHES (*watches);
  watch *end = END_WATCHES (*watches);
  watch *last = end - 1;

  while (p->raw != needle.raw)
    p++;

  if (p != last)
    memmove (p, p + 1, (last - p) * sizeof *p);
  SET_END_OF_WATCHES (*watches, last);
}

static void eagerly_remove_binary_learnt (kissat *solver, watches *watches,
                                          unsigned lit) {
  const watch needle = kissat_binary_watch (lit);
  eagerly_remove_watch_learnt (solver, watches, needle);
}

static void delete_unfactored_learnt (factoring *factoring, quotient *q) {
  kissat *const solver = factoring->solver;
  const unsigned factor = q->factor;

  for (all_stack (watch, watch, q->clauses)) {
    if (watch.type.binary) {
      const unsigned other = watch.binary.lit;
      eagerly_remove_binary_learnt (solver, &WATCHES (other), factor);
      eagerly_remove_binary_learnt (solver, &WATCHES (factor), other);
      kissat_delete_binary (solver, factor, other);
    } else {
      const reference ref = watch.large.ref;
      clause *c = kissat_dereference_clause (solver, ref);
      for (all_literals_in_clause (lit, c))
        eagerly_remove_watch_learnt (solver, &WATCHES (lit), watch);
      kissat_mark_clause_as_garbage (solver, c);
    }
  }
}

static void update_factored_learnt (factoring *factoring, quotient *q) {
  kissat *const solver = factoring->solver;
  const unsigned factor = q->factor;

  update_candidate_learnt (factoring, factor);
  update_candidate_learnt (factoring, NOT (factor));

  for (all_stack (watch, watch, q->clauses)) {
    if (watch.type.binary) {
      const unsigned other = watch.binary.lit;
      update_candidate_learnt (factoring, other);
    } else {
      const reference ref = watch.large.ref;
      clause *c = kissat_dereference_clause (solver, ref);
      for (all_literals_in_clause (lit, c))
        if (lit != factor)
          update_candidate_learnt (factoring, lit);
    }
  }
}

static void resize_factoring_learnt (factoring *factoring, unsigned lit) {
  kissat *const solver = factoring->solver;

  const size_t old_size = factoring->size;
  const size_t old_allocated = factoring->allocated;
  size_t new_size = lit + 1;

  if (new_size > old_allocated) {
    size_t new_allocated = 2 * old_allocated;
    while (new_size > new_allocated)
      new_allocated *= 2;

    unsigned *count = factoring->count;
    count = kissat_nrealloc (solver, count, old_allocated, new_allocated,
                             sizeof *count);
    const size_t delta_allocated = new_allocated - old_allocated;
    const size_t delta_bytes = delta_allocated * sizeof *count;
    memset (count + old_size, 0, delta_bytes);
    factoring->count = count;

    factoring->allocated = new_allocated;
  }
  factoring->size = new_size;
}

static bool apply_factoring_learnt (factoring *factoring, quotient *q) {
  kissat *const solver = factoring->solver;

  const unsigned fresh = kissat_fresh_literal (solver);
  if (fresh == INVALID_LIT)
    return false;

  const unsigned not_fresh = NOT (fresh);

  INC (factoredl);

  PUSH_STACK (factoring->fresh, fresh);
  for (quotient *p = q; p->prev; p = p->prev)
    flush_unmatched_clauses_learnt (solver, p);

  for (quotient *p = q; p; p = p->prev)
    add_factored_divider_learnt (factoring, fresh, p->factor);
  add_factored_quotient_learnt (factoring, q, not_fresh);

  for (quotient *p = q; p; p = p->prev)
    delete_unfactored_learnt (factoring, p);
  for (quotient *p = q; p; p = p->prev)
    update_factored_learnt (factoring, p);

  resize_factoring_learnt (factoring, not_fresh);
  return true;
}

static void
adjust_scores_and_phases_of_fresh_variables_learnt (factoring *factoring) {
  const unsigned *begin = BEGIN_STACK (factoring->fresh);
  const unsigned *end = END_STACK (factoring->fresh);
  kissat *const solver = factoring->solver;
  {
    const unsigned *p = end;
    while (p != begin) {
      const unsigned lit = *--p;
      const unsigned idx = IDX (lit);
      const double score = 0;
      kissat_update_heap (solver, kissat_get_scores (solver), idx, score);
      if (solver->mab)
        kissat_update_heap (solver, &solver->scores_chb, idx, score);
    }
  }
  {
    const unsigned *p = begin;
    links *links = solver->links;
    queue *queue = &solver->queue;

    while (p != end) {
      const unsigned lit = *p++;
      const unsigned idx = IDX (lit);
      kissat_dequeue_links (idx, links, queue);
    }

    p = begin;
    while (p != end) {
      const unsigned lit = *p++;
      const unsigned idx = IDX (lit);
      struct links *l = links + idx;
      if (DISCONNECTED (queue->first)) {
        queue->last = idx;
      } else {
        struct links *first = links + queue->first;
        first->prev = idx;
      }
      l->next = queue->first;
      queue->first = idx;
    }

    queue->stamp = 0;
    unsigned idx = queue->first;
    while (!DISCONNECTED (idx)) {
      struct links *l = links + idx;
      l->stamp = ++queue->stamp;
      idx = l->next;
    }

    solver->queue.search.idx = queue->last;
    solver->queue.search.stamp = queue->stamp;
    kissat_check_queue (solver);
  }
}

static bool run_factorization_learnt (kissat *solver) {
  factoring factoring;
  init_factoring_learnt (solver, &factoring);
  schedule_factorization_learnt (&factoring);
  bool done = false;

  while (!done && !kissat_empty_heap (&factoring.schedule)) {
    const unsigned first = kissat_pop_max_heap (solver, &factoring.schedule);
    const unsigned first_idx = IDX (first);

    if (!ACTIVE (first_idx))
      continue;
    if (TERMINATED (factor_terminated_1))
      break;

    struct flags *f = solver->flags + first_idx;
    const unsigned bit = 1u << NEGATED (first);
    if (!(f->factorl & bit))
      continue;
    f->factorl &= ~bit;

    const size_t first_count = first_factor_learnt (&factoring, first);
    if (first_count > 1) {
      for (;;) {
        unsigned next_count;
        const unsigned next =
            next_factor_learnt (&factoring, &next_count);
        if (next == INVALID_LIT)
          break;
        if (next_count < 2)
          break;

        factorize_next_learnt (&factoring, next, next_count);
      }
      size_t reduction;
      quotient *q = best_quotient_learnt (&factoring, &reduction);
      if (q && reduction > factoring.bound) {
        if (!apply_factoring_learnt (&factoring, q))
          done = true;
      }
    }
    release_quotients_learnt (&factoring);
  }
  bool completed = kissat_empty_heap (&factoring.schedule);
  adjust_scores_and_phases_of_fresh_variables_learnt (&factoring);
  release_factoring_learnt (&factoring);
  return completed;
}

static void connect_clauses_to_factor_learnt (kissat *solver) {
  const unsigned size_limit = GET_OPTION (factorsizel);
  if (size_limit < 3)
    return;

  clause *last_irredundant = kissat_last_irredundant_clause (solver);
  ward *const arena = BEGIN_STACK (solver->arena);
  watches *all_watches = solver->watches;

  const unsigned tier1 = TIER1;
  const unsigned tier2 = MAX (tier1, TIER2);

  for (all_clauses (c)) {
    if (c->garbage)
      continue;
    if (c <= last_irredundant)
      continue;
    if (!c->redundant)
      continue;
    if (c->size > size_limit)
      continue;

    const unsigned used = c->used - 1;
    const unsigned glue = c->glue;
    if (glue <= tier1 && used)
      continue;
    if (glue <= tier2 && used >= MAX_USED - 1)
      continue;

    const reference ref = (ward *) c - arena;
    kissat_inlined_connect_clause (solver, all_watches, c, ref);
  }
}

static bool kissat_factoring_learnt (kissat *solver) {
  if (!GET_OPTION (factorl))
    return false;
  if (!solver->active)
    return false;

  statistics *statistics = &solver->statistics;
  if (solver->last_reduce >= statistics->reductions)
    return false;

  solver->last_reduce = statistics->reductions;
  return true;
}

void kissat_factor_learnt (kissat *solver) {
  assert (solver->watching);

  if (solver->level)
    return;
  if (solver->inconsistent)
    return;
  if (!kissat_factoring_learnt (solver))
    return;

  START (factorl);

  statistics *s = &solver->statistics;
  struct {
    int64_t variables, redundant;
  } before, after, delta;
  before.variables = s->variables_extension + s->variables_original;
  before.redundant = REDUNDANT_CLAUSES;

  litpairs binaries;
  INIT_STACK (binaries);
  kissat_enter_dense_mode (solver, &binaries);
  connect_clauses_to_factor_learnt (solver);
  run_factorization_learnt (solver);
  kissat_resume_sparse_mode (solver, false, &binaries);
  RELEASE_STACK (binaries);

  after.variables = s->variables_extension + s->variables_original;
  after.redundant = REDUNDANT_CLAUSES;
  delta.variables = after.variables - before.variables;
  delta.redundant = before.redundant - after.redundant;

  kissat_very_verbose (
      solver,
      "learnt factoring delta variables %" PRId64 ", redundant clauses %"
      PRId64,
      delta.variables, delta.redundant);

  STOP (factorl);
}
