#include "allocate.h"
#include "backtrack.h"
#include "colors.h"
#include "decide.h"
#include "inline.h"
#include "print.h"
#include "prophyper.h"
#include "proprobe.h"
#include "promote.h"
#include "report.h"
#include "sort.h"
#include "trail.h"
#include "terminate.h"
#include "vivify.h"

#include <inttypes.h>

static inline bool
more_occurrences (unsigned *counts, unsigned a, unsigned b)
{
  const unsigned s = counts[a];
  const unsigned t = counts[b];
  if (s > t)
    return true;
  if (s < t)
    return false;
  return a < b;
}

#define MORE_OCCURRENCES(A,B) \
  more_occurrences (counts, (A), (B))

static void
vivify_sort_lits_by_counts (kissat * solver,
			    size_t size, unsigned *lits, unsigned *counts)
{
  SORT (unsigned, size, lits, MORE_OCCURRENCES);
}

static void
vivify_sort_stack_by_counts (kissat * solver,
			     unsigneds * stack, unsigned *counts)
{
  const size_t size = SIZE_STACK (*stack);
  unsigned *lits = BEGIN_STACK (*stack);
  vivify_sort_lits_by_counts (solver, size, lits, counts);
}

static void
vivify_sort_clause_by_counts (kissat * solver, clause * c, unsigned *counts)
{
  vivify_sort_lits_by_counts (solver, c->size, c->lits, counts);
}

static void
count_literal (unsigned lit, unsigned *counts)
{
  const unsigned old_count = counts[lit];
  const unsigned new_count =
    (old_count < UINT_MAX) ? old_count + 1 : UINT_MAX;
  counts[lit] = new_count;
}

static void
count_clause (clause * c, unsigned *counts)
{
  for (all_literals_in_clause (lit, c))
    count_literal (lit, counts);
}

static bool
simplify_vivification_candidate (kissat * solver, clause * c)
{
  assert (!solver->level);
  bool satisfied = false;
  assert (EMPTY_STACK (solver->clause.lits));
  const value *values = solver->values;
  for (all_literals_in_clause (lit, c))
    {
      const value value = values[lit];
      if (value > 0)
	{
	  satisfied = true;
	  LOGCLS (c, "vivification %s satisfied candidate", LOGLIT (lit));
	  kissat_mark_clause_as_garbage (solver, c);
	  break;
	}
      if (!value)
	PUSH_STACK (solver->clause.lits, lit);
    }
  unsigned non_false = SIZE_STACK (solver->clause.lits);
  if (satisfied)
    {
      CLEAR_STACK (solver->clause.lits);
      return true;
    }
  if (non_false == c->size)
    {
      CLEAR_STACK (solver->clause.lits);
      return false;
    }
  assert (1 < non_false);
  assert (non_false <= c->size);
  if (non_false == 2)
    {
      const unsigned first = PEEK_STACK (solver->clause.lits, 0);
      const unsigned second = PEEK_STACK (solver->clause.lits, 1);
      LOGBINARY (first, second, "vivification shrunken candidate");
      kissat_new_binary_clause (solver, c->redundant, first, second);
      kissat_mark_clause_as_garbage (solver, c);
    }
  else
    {
      CHECK_AND_ADD_STACK (solver->clause.lits);
      ADD_STACK_TO_PROOF (solver->clause.lits);

      REMOVE_CHECKER_CLAUSE (c);
      DELETE_CLAUSE_FROM_PROOF (c);

      const unsigned old_size = c->size;
      unsigned new_size = 0, *lits = c->lits;;
      for (unsigned i = 0; i < old_size; i++)
	{
	  const unsigned lit = lits[i];
	  const value value = kissat_fixed (solver, lit);
	  assert (value <= 0);
	  if (value < 0)
	    continue;
	  lits[new_size++] = lit;
	}
      assert (2 < new_size);
      assert (new_size == non_false);
      assert (new_size < old_size);
      c->size = new_size;
      c->searched = 2;
      if (c->redundant && c->glue >= new_size)
	kissat_promote_clause (solver, c, new_size - 1);
      if (!c->shrunken)
	{
	  c->shrunken = true;
	  lits[old_size - 1] = INVALID_LIT;
	}
      LOGCLS (c, "vivification shrunken candidate");
    }
  CLEAR_STACK (solver->clause.lits);
  return false;
}

static void
schedule_vivification_candidates (kissat * solver,
#ifndef QUIET
				  const char *mode, const char *type,
#endif
				  references * schedule, unsigned *counts,
				  bool redundant, bool tier2)
{
  unsigned lower_glue_limit, upper_glue_limit;
  if (tier2)
    {
      lower_glue_limit = GET_OPTION (tier1) + 1;
      upper_glue_limit = GET_OPTION (tier2);
    }
  else
    {
      lower_glue_limit = 0;
      upper_glue_limit = GET_OPTION (tier1);
    }
  const word *arena = BEGIN_STACK (solver->arena);
  size_t prioritized = 0;
  for (unsigned prioritize = 0; prioritize < 2; prioritize++)
    {
      for (all_clauses (c))
	{
	  if (c->garbage)
	    continue;
	  if (redundant)
	    {
	      if (!c->redundant)
		continue;
	      if (c->hyper)
		continue;
	      if (c->glue < lower_glue_limit)
		continue;
	      if (c->glue > upper_glue_limit)
		continue;
	    }
	  else if (c->redundant)
	    continue;
	  if (c->vivify != prioritize)
	    continue;
	  if (simplify_vivification_candidate (solver, c))
	    continue;
	  if (prioritize)
	    prioritized++;
	  count_clause (c, counts);
	  const reference ref = (word *) c - arena;
	  PUSH_STACK (*schedule, ref);
	}
    }
#ifndef QUIET
  size_t scheduled = SIZE_STACK (*schedule);
#endif
  if (prioritized)
    {
      kissat_phase (solver, mode, GET (probings),
		    "prioritized %zu %s clauses %.0f%%", prioritized,
		    type, kissat_percent (prioritized, scheduled));
    }
  else
    {
      kissat_phase (solver, mode, GET (probings),
		    "prioritizing all %zu scheduled %s clauses",
		    scheduled, type);
      for (all_stack (reference, ref, *schedule))
	{
	  clause *c = (clause *) (arena + ref);
	  assert (kissat_clause_in_arena (solver, c));
	  c->vivify = true;
	}
    }
}

static unsigned *
new_vivification_candidates_counts (kissat * solver)
{
  return kissat_calloc (solver, LITS, sizeof (unsigned));
}

static inline bool
worse_candidate (kissat * solver, unsigned *counts, reference r, reference s)
{
  const clause *c = kissat_dereference_clause (solver, r);
  const clause *d = kissat_dereference_clause (solver, s);

  if (!c->vivify && d->vivify)
    return true;

  if (c->vivify && !d->vivify)
    return false;

  const unsigned *p = BEGIN_LITS (c);
  const unsigned *q = BEGIN_LITS (d);
  const unsigned *e = END_LITS (c);
  const unsigned *f = END_LITS (d);

  while (p != e && q != f)
    {
      const unsigned a = *p++;
      const unsigned b = *q++;
      if (a == b)
	continue;
      const unsigned u = counts[a];
      const unsigned v = counts[b];
      if (u < v)
	return true;
      if (u > v)
	return false;
      return a < b;
    }

  if (p != e && q == f)
    return true;
  if (p == e && q != f)
    return false;

  return r < s;
}

#define WORSE_CANDIDATE(A,B) \
  worse_candidate (solver, counts, (A), (B))

static void
sort_vivification_candidates (kissat * solver,
			      references * schedule, unsigned *counts)
{
  for (all_stack (reference, ref, *schedule))
    {
      clause *c = kissat_dereference_clause (solver, ref);
      vivify_sort_clause_by_counts (solver, c, counts);
    }
  SORT_STACK (reference, *schedule, WORSE_CANDIDATE);
}

static clause *
vivify_unit_conflict (kissat * solver, unsigned lit)
{
  assert (VALUE (lit) > 0);
  LOG ("vivify analyzing conflict unit %s", LOGLIT (NOT (lit)));
  assigned *a = ASSIGNED (lit);
  assert (!a->analyzed);
  assert (a->analyzed != DECISION);
  clause *conflict;
  if (a->binary)
    {
      const unsigned other = a->reason;
      conflict = kissat_binary_conflict (solver, a->redundant, lit, other);
    }
  else
    {
      const reference ref = a->reason;
      conflict = kissat_dereference_clause (solver, ref);
    }
  a->analyzed = true;
  PUSH_STACK (solver->analyzed, lit);
  PUSH_STACK (solver->clause.lits, lit);
  return conflict;
}

static void
vivify_binary_or_large_conflict (kissat * solver, clause * conflict)
{
  assert (conflict->size >= 2);
  LOGCLS (conflict, "vivify analyzing conflict");
  for (all_literals_in_clause (lit, conflict))
    {
      assert (VALUE (lit) < 0);
      assigned *a = ASSIGNED (lit);
      if (!a->level)
	continue;
      assert (!a->analyzed);
      a->analyzed = ANALYZED;
      PUSH_STACK (solver->analyzed, lit);
    }
}

static bool
vivify_analyze (kissat * solver, clause * c,
		clause * conflict, bool * irredundant_ptr)
{
  assert (conflict);
  assert (!EMPTY_STACK (solver->analyzed));

  value *marks = solver->marks;
  for (all_literals_in_clause (lit, c))
    {
      assert (!marks[lit]);
      marks[lit] = 1;
    }

  bool subsumed = false;

  if (c->redundant || !conflict->redundant)
    {
      subsumed = true;
      for (all_literals_in_clause (lit, conflict))
	{
	  const value value = kissat_fixed (solver, lit);
	  if (value < 0)
	    continue;
	  assert (!value);
	  if (marks[lit])
	    continue;
	  subsumed = false;
	  break;
	}
      if (subsumed)
	LOGCLS (conflict, "vivify subsuming");
    }

  size_t analyzed = 0;
  bool irredundant = conflict && !conflict->redundant;

  while (!subsumed && analyzed < SIZE_STACK (solver->analyzed))
    {
      const unsigned not_lit = PEEK_STACK (solver->analyzed, analyzed);
      const unsigned lit = NOT (not_lit);
      analyzed++;
      assigned *a = ASSIGNED (lit);
      assert (a->level);
      assert (a->analyzed);
      if (a->reason == DECISION)
	{
	  LOG ("vivify analyzing decision %s", LOGLIT (not_lit));
	  PUSH_STACK (solver->clause.lits, not_lit);
	}
      else if (a->binary)
	{
	  const unsigned other = a->reason;
	  if (a->redundant)
	    irredundant = false;
	  assert (VALUE (other) < 0);
	  assigned *b = ASSIGNED (other);
	  assert (b->level);
	  if (c->redundant || !a->redundant)
	    {
	      if (marks[lit] && marks[other])
		{
		  LOGBINARY (lit, other, "vivify subsuming");
		  subsumed = true;
		  break;
		}
	    }
	  if (b->analyzed)
	    continue;
	  LOGBINARY (lit, other, "vivify analyzing %s reason", LOGLIT (lit));
	  b->analyzed = ANALYZED;
	  PUSH_STACK (solver->analyzed, other);
	}
      else
	{
	  const reference ref = a->reason;
	  LOGREF (ref, "vivify analyzing %s reason", LOGLIT (lit));
	  clause *reason = kissat_dereference_clause (solver, ref);
	  if (reason->redundant)
	    irredundant = false;
	  subsumed = marks[lit];
	  for (all_literals_in_clause (other, reason))
	    {
	      if (other == lit)
		continue;
	      if (other == not_lit)
		continue;
	      assert (VALUE (other) < 0);
	      assigned *b = ASSIGNED (other);
	      if (!b->level)
		continue;
	      if (!marks[other])
		subsumed = false;
	      if (b->analyzed)
		continue;
	      b->analyzed = ANALYZED;
	      PUSH_STACK (solver->analyzed, other);
	    }
	  if (subsumed && (c->redundant || !reason->redundant))
	    {
	      LOGCLS (reason, "vivify subsuming");
	      break;
	    }
	  subsumed = false;
	}
    }

  for (all_literals_in_clause (lit, c))
    {
      assert (marks[lit]);
      marks[lit] = 0;
    }

  if (!subsumed)
    {
      *irredundant_ptr = irredundant;
      LOGTMP ("vivify learned");
    }

  return subsumed;
}

static void
reset_vivify_analyzed (kissat * solver)
{
  for (all_stack (unsigned, lit, solver->analyzed))
    {
      assigned *a = ASSIGNED (lit);
      assert (a->analyzed);
      a->analyzed = 0;
    }
  CLEAR_STACK (solver->clause.lits);
  CLEAR_STACK (solver->analyzed);
}

static void
vivify_inc_subsume (kissat * solver, clause * c)
{
  if (c->redundant)
    INC (vivify_sub_red);
  else
    INC (vivify_sub_irr);
  INC (vivify_subsumed);
  INC (subsumed);
}

static void
vivify_inc_strengthened (kissat * solver, clause * c)
{
  if (c->redundant)
    INC (vivify_str_red);
  else
    INC (vivify_str_irr);
  INC (vivify_strengthened);
  INC (strengthened);
}

static bool
vivify_learn (kissat * solver, clause * c,
	      unsigned non_false, bool irredundant, unsigned implied)
{
  bool res;

  size_t size = SIZE_STACK (solver->clause.lits);
  assert (size <= non_false);
  assert (2 < non_false);

  if (size < non_false)
    {
      if (solver->level)
	kissat_backtrack (solver, 0);
      LOGTMP ("vivified");
    }

  if (size == 1)
    {
      const unsigned unit = PEEK_STACK (solver->clause.lits, 0);
      kissat_assign_unit (solver, unit);
      solver->iterating = true;
      CHECK_AND_ADD_UNIT (unit);
      ADD_UNIT_TO_PROOF (unit);
      kissat_mark_clause_as_garbage (solver, c);
      clause *conflict = kissat_probing_propagate (solver, 0);
      if (conflict)
	{
	  CHECK_AND_ADD_EMPTY ();
	  ADD_EMPTY_TO_PROOF ();
	  LOG ("propagating vivified unit failed");
	  solver->inconsistent = true;
	}
      else
	{
	  assert (solver->unflushed);
	  kissat_flush_trail (solver);
	}
      vivify_inc_strengthened (solver, c);
      INC (failed);
      res = true;
    }
  else if (size == 2)
    {
      if (c->redundant)
	(void) kissat_new_redundant_clause (solver, 1);
      else
	(void) kissat_new_irredundant_clause (solver);
      kissat_mark_clause_as_garbage (solver, c);
      vivify_inc_strengthened (solver, c);
      res = true;
    }
  else if (size < non_false)
    {
      CHECK_AND_ADD_STACK (solver->clause.lits);
      ADD_STACK_TO_PROOF (solver->clause.lits);

      REMOVE_CHECKER_CLAUSE (c);
      DELETE_CLAUSE_FROM_PROOF (c);

      assert (size > 2);
      const unsigned old_size = c->size;
      unsigned new_size = 0, *lits = c->lits;
      unsigned watched[2] = { lits[0], lits[1] };
      for (unsigned i = 0; i < old_size; i++)
	{
	  const unsigned lit = lits[i];
	  bool keep = true;
	  if (lit != implied)
	    {
	      assigned *a = ASSIGNED (lit);
	      if (!a->analyzed)
		keep = false;
	      else if (a->reason != DECISION)
		keep = false;
	    }
	  if (!c->redundant)
	    {
	      if (keep)
		kissat_mark_added_literal (solver, lit);
	      else
		kissat_mark_removed_literal (solver, lit);
	    }
	  if (keep)
	    lits[new_size++] = lit;
	}
      assert (new_size < old_size);
      assert (new_size == size);
      if (!c->shrunken)
	{
	  c->shrunken = true;
	  lits[old_size - 1] = INVALID_LIT;
	}
      c->size = new_size;
      if (c->redundant && c->glue >= new_size)
	kissat_promote_clause (solver, c, new_size - 1);
      c->searched = 2;
      LOGCLS (c, "vivified shrunken");

      const reference ref = kissat_reference_clause (solver, c);

      // Beware of 'stale blocking literals' ... so rewatch if shrunken.

      kissat_unwatch_blocking (solver, watched[0], ref);
      kissat_unwatch_blocking (solver, watched[1], ref);
      kissat_watch_blocking (solver, lits[0], lits[1], ref);
      kissat_watch_blocking (solver, lits[1], lits[0], ref);

      vivify_inc_strengthened (solver, c);
      res = true;
    }
  else if (irredundant && !c->redundant)
    {
      LOGCLS (c, "vivify subsumed");
      vivify_inc_subsume (solver, c);
      kissat_mark_clause_as_garbage (solver, c);
      res = true;
    }
  else
    {
      LOG ("vivify failed");
      res = false;
    }

  return res;
}

static bool
vivify_clause (kissat * solver, clause * c,
	       unsigneds * sorted, unsigned *counts)
{
  assert (!c->garbage);
  assert (solver->probing);
  assert (solver->watching);
  assert (!solver->inconsistent);

  LOGCLS (c, "vivify candidate");

  CLEAR_STACK (*sorted);

  for (all_literals_in_clause (lit, c))
    {
      const value value = kissat_fixed (solver, lit);
      if (value < 0)
	continue;
      if (value > 0)
	{
	  LOGCLS (c, "%s satisfied", LOGLIT (lit));
	  kissat_mark_clause_as_garbage (solver, c);
	  break;
	}
      PUSH_STACK (*sorted, lit);
    }

  if (c->garbage)
    return false;

  const unsigned non_false = SIZE_STACK (*sorted);

  assert (1 < non_false);
  assert (non_false <= c->size);

#ifdef LOGGING
  if (!non_false)
    LOG ("no root level falsified literal");
  else if (non_false == c->size)
    LOG ("all literals root level unassigned");
  else
    LOG ("found %u root level non-falsified literals");
#endif

  if (non_false == 2)
    {
      LOGCLS (c, "skipping actually binary");
      return false;
    }

  INC (vivification_checks);

  unsigned unit = INVALID_LIT;
  for (all_literals_in_clause (lit, c))
    {
      const value value = VALUE (lit);
      if (value < 0)
	continue;
      if (!value)
	{
	  unit = INVALID_LIT;
	  break;
	}
      assert (value > 0);
      if (unit != INVALID_LIT)
	{
	  unit = INVALID_LIT;
	  break;
	}
      unit = lit;
    }
  if (unit != INVALID_LIT)
    {
      assigned *a = ASSIGNED (unit);
      assert (a->level);
      if (a->binary)
	unit = INVALID_LIT;
      else
	{
	  reference ref = kissat_reference_clause (solver, c);
	  if (a->reason != ref)
	    unit = INVALID_LIT;
	}
    }
  if (unit == INVALID_LIT)
    LOG ("non-reason candidate clause");
  else
    {
      LOG ("candidate is the reason of %s", LOGLIT (unit));
      const unsigned level = LEVEL (unit);
      assert (level > 0);
      LOG ("forced to backtrack to level %u", level - 1);
      kissat_backtrack (solver, level - 1);
    }

  assert (EMPTY_STACK (solver->analyzed));
  assert (EMPTY_STACK (solver->clause.lits));

  vivify_sort_stack_by_counts (solver, sorted, counts);

#if defined(LOGGING) && !defined(NOPTIONS)
  if (solver->options.log)
    {
      TERMINAL (stdout, 1);
      COLOR (MAGENTA);
      printf ("c LOG %u vivify sorted", solver->level);
      heap *scores = solver->heuristic==0?&solver->scores:&solver->scores_chb;
      links *links = solver->links;
      for (all_stack (unsigned, lit, *sorted))
	{
	  printf (" %s", LOGLIT (lit));
	  if (counts)
	    printf ("#%u", counts[lit]);
	  else
	    {
	      const unsigned idx = IDX (lit);
	      if (solver->stable)
		printf ("[%g]", kissat_get_heap_score (scores, idx));
	      else
		printf ("{%u}", links[idx].stamp);
	    }

	}
      COLOR (NORMAL);
      printf ("\n");
      fflush (stdout);
    }
#endif

  unsigned implied = INVALID_LIT;
  clause *conflict = 0;
  unsigned level = 0;
  bool res = false;

  for (all_stack (unsigned, lit, *sorted))
    {
      if (level++ < solver->level)
	{
	  frame *frame = &FRAME (level);
	  const unsigned not_lit = NOT (lit);
	  if (frame->decision == not_lit)
	    {
	      LOG ("reusing assumption %s", LOGLIT (not_lit));
	      INC (vivify_reused);
	      INC (vivify_probes);
	      assert (VALUE (lit) < 0);
	      continue;
	    }

	  LOG ("forced to backtrack to decision level %u", level - 1);
	  kissat_backtrack (solver, level - 1);
	}

      const value value = VALUE (lit);
      assert (!value || LEVEL (lit) <= level);

      if (!value)
	{
	  LOG ("literal %s unassigned", LOGLIT (lit));
	  const unsigned not_lit = NOT (lit);
	  INC (vivify_assumed);
	  INC (vivify_probes);
	  kissat_internal_assume (solver, not_lit);
	  if (solver->level == 1)
	    conflict = kissat_hyper_propagate (solver, c);
	  else
	    conflict = kissat_probing_propagate (solver, c);
	  if (!conflict)
	    continue;
	  vivify_binary_or_large_conflict (solver, conflict);
	  assert (!EMPTY_STACK (solver->analyzed));
	  break;
	}

      if (value < 0)
	{
	  assert (LEVEL (lit));
	  LOG ("literal %s already falsified", LOGLIT (lit));
	  continue;
	}

      assert (value > 0);
      assert (LEVEL (lit));
      LOG ("literal %s already satisfied", LOGLIT (lit));
      if (!c->redundant || GET_OPTION (vivifyimply) == 1)
	{
	  implied = lit;
	  conflict = vivify_unit_conflict (solver, lit);
	  assert (!EMPTY_STACK (solver->analyzed));
	}
      else if (GET_OPTION (vivifyimply) == 2)
	{
	  LOGCLS (c, "vivify implied");
	  kissat_mark_clause_as_garbage (solver, c);
	  INC (vivify_implied);
	  res = true;
	}
      break;
    }

  if (c->garbage)
    assert (EMPTY_STACK (solver->analyzed));
  else if (conflict)
    {
      assert (!EMPTY_STACK (solver->analyzed));
      assert (solver->level);
      bool irredundant;
      const bool subsumed =
	vivify_analyze (solver, c, conflict, &irredundant);

      if (subsumed)
	{
	  LOGCLS (c, "vivify subsumed");
	  kissat_mark_clause_as_garbage (solver, c);
	  vivify_inc_subsume (solver, c);
	  res = true;
	}
      else
	res = vivify_learn (solver, c, non_false, irredundant, implied);

      reset_vivify_analyzed (solver);
    }
  else
    {
      assert (EMPTY_STACK (solver->analyzed));
      LOG ("vivify failed");
    }

  if (!res)
    return false;

  INC (vivified);
  if (c->redundant)
    INC (vivify_redundant);
  else
    INC (vivify_irredundant);

  return true;
}

enum round
{
  REDUNDANT_TIER1_ROUND = 1,
  REDUNDANT_TIER2_ROUND = 2,
  IRREDUNDANT_ROUND = 3
};

typedef enum round round;

static void
vivify_round (kissat * solver, round round)
{
  assert (solver->watching);
  assert (solver->probing);

#ifndef QUIET
  const char *mode;
  const char *type;
  char tag;
  if (round == REDUNDANT_TIER2_ROUND)
    {
      mode = "vivify-redundant-tier2";
      type = "redundant";
      tag = 'u';
    }
  else if (round == REDUNDANT_TIER1_ROUND)
    {
      mode = "vivify-redundant-tier1";
      type = "redundant";
      tag = 'v';
    }
  else
    {
      assert (round == IRREDUNDANT_ROUND);
      mode = "vivify-irredundant";
      type = "irredundant";
      tag = 'w';
    }
#endif

  references schedule;
  INIT_STACK (schedule);

  unsigned *counts = 0;
  kissat_flush_large_watches (solver);
  counts = new_vivification_candidates_counts (solver);
  {
    bool redundant, tier2;

    if (round == REDUNDANT_TIER1_ROUND)
      redundant = true, tier2 = false;
    else if (round == REDUNDANT_TIER2_ROUND)
      redundant = tier2 = true;
    else
      {
	assert (round == IRREDUNDANT_ROUND);
	redundant = tier2 = false;
      }

    schedule_vivification_candidates (solver,
#ifndef QUIET
				      mode, type,
#endif
				      &schedule, counts, redundant, tier2);
  }
  sort_vivification_candidates (solver, &schedule, counts);
  kissat_watch_large_clauses (solver);

  const size_t scheduled = SIZE_STACK (schedule);
#ifndef QUIET
  const size_t total =
    (round == IRREDUNDANT_ROUND) ? IRREDUNDANT_CLAUSES : REDUNDANT_CLAUSES;
  kissat_phase (solver, mode, GET (probings),
		"scheduled %zu %s clauses %.0f%% of %zu", scheduled,
		type, kissat_percent (scheduled, total), total);
#endif
  SET_EFFICIENCY_BOUND (ticks_limit, vivify,
			probing_ticks, search_ticks,
			kissat_nlogn (scheduled));

  if (round == REDUNDANT_TIER2_ROUND)
    {
      const uint64_t delta = ticks_limit - solver->statistics.probing_ticks;
      ticks_limit += 3 * delta;
      kissat_very_verbose (solver,
			   "increasing redundant tier2 efficiency limit to %"
			   PRIu64 " by %" PRIu64 " = 3 * %" PRIu64,
			   ticks_limit, 3 * delta, delta);
    }

  size_t vivified = 0, tried = 0;
  unsigneds sorted;
  INIT_STACK (sorted);
  while (!EMPTY_STACK (schedule))
    {
      if (solver->statistics.probing_ticks > ticks_limit)
	break;
      if (TERMINATED (19))
	break;
      const reference ref = POP_STACK (schedule);
      clause *c = kissat_dereference_clause (solver, ref);
      if (c->garbage)
	continue;
      tried++;
      if (vivify_clause (solver, c, &sorted, counts))
	vivified++;
      c->vivify = false;
      if (solver->inconsistent)
	break;
    }
  if (solver->level)
    kissat_backtrack (solver, 0);
  kissat_dealloc (solver, counts, LITS, sizeof *counts);
  RELEASE_STACK (sorted);
#ifndef QUIET
  kissat_phase (solver, mode, GET (probings),
		"vivified %zu %s clauses %.0f%% out of %zu tried",
		vivified, type, kissat_percent (vivified, tried), tried);
  if (!solver->inconsistent)
    {
      size_t remain = SIZE_STACK (schedule);
      if (remain)
	{
	  kissat_phase (solver, mode, GET (probings),
			"%zu %s clauses remain %.0f%% out of %zu scheduled",
			remain, type, kissat_percent (remain, scheduled),
			scheduled);

	  const word *arena = BEGIN_STACK (solver->arena);
	  size_t prioritized = 0;
	  while (!EMPTY_STACK (schedule))
	    {
	      const unsigned ref = POP_STACK (schedule);
	      clause *c = (clause *) (arena + ref);
	      if (c->vivify)
		prioritized++;
	    }
	  if (prioritized)
	    kissat_phase (solver, mode, GET (probings),
			  "keeping %zu %s clauses prioritized %.0f%%",
			  prioritized, type,
			  kissat_percent (prioritized, remain));
	  else
	    kissat_phase (solver, mode, GET (probings),
			  "no prioritized %s clauses left", type);
	}
      else
	kissat_phase (solver, mode, GET (probings),
		      "all scheduled %s clauses tried", type);
    }
#endif
  RELEASE_STACK (schedule);
  REPORT (!vivified, tag);
}

static void
vivify_redundant_tier1 (kissat * solver)
{
  if (TERMINATED (20))
    return;
  vivify_round (solver, REDUNDANT_TIER1_ROUND);
}

static void
vivify_redundant_tier2 (kissat * solver)
{
  if (TERMINATED (21))
    return;
  vivify_round (solver, REDUNDANT_TIER2_ROUND);
}

static void
vivify_irredundant (kissat * solver)
{
  if (TERMINATED (22))
    return;
  vivify_round (solver, IRREDUNDANT_ROUND);
}

static bool
really_vivify (kissat * solver)
{
  if (!GET_OPTION (really))
    return true;
  const uint64_t limit = IRREDUNDANT_CLAUSES + 5 * REDUNDANT_CLAUSES;
  statistics *statistics = &solver->statistics;
  const uint64_t visits = statistics->search_ticks;
  return limit < visits + GET_OPTION (vivifymineff);
}

void
kissat_vivify (kissat * solver)
{
  if (solver->inconsistent)
    return;
  assert (!solver->level);
  assert (solver->probing);
  assert (solver->watching);
  if (!GET_OPTION (vivify))
    return;
  if (!solver->statistics.clauses_redundant)
    return;
  if (!really_vivify (solver))
    return;
  START (vivify);
  vivify_redundant_tier2 (solver);
  if (!solver->inconsistent)
    {
      vivify_redundant_tier1 (solver);
      if (!solver->inconsistent &&
	  IRREDUNDANT_CLAUSES / 10 < REDUNDANT_CLAUSES)
	vivify_irredundant (solver);
    }
  STOP (vivify);
}
