#define INLINE_SORT

#include "dense.h"
#include "inline.h"
#include "propsearch.h"
#include "proprobe.h"
#include "trail.h"

#include "sort.c"

static void
flush_large_watches (kissat * solver,
		     litpairs * irredundant, litwatches * redundant)
{
  assert (!solver->level);
  assert (solver->watching);
#ifndef LOGGING
  LOG ("flushing large watches");
  if (irredundant)
    LOG ("flushing and saving irredundant binary clauses too");
  else
    LOG ("keep watching irredundant binary clauses");
  if (redundant)
    LOG ("flushing and saving redundant clauses too");
  else
    LOG ("keep watching redundant binary clauses");
#endif
  const value *values = solver->values;
  size_t flushed = 0, collected = 0;
  watches *all_watches = solver->watches;
  for (all_literals (lit))
    {
      const value lit_value = values[lit];
      watches *watches = all_watches + lit;
      watch *begin = BEGIN_WATCHES (*watches), *q = begin;
      const watch *end_watches = END_WATCHES (*watches), *p = q;
      while (p != end_watches)
	{
	  const watch watch = *p++;
	  if (watch.type.binary)
	    {
	      const unsigned other = watch.binary.lit;
	      const value other_value = values[other];
	      if (!lit_value && !other_value)
		{
		  if (irredundant && !watch.binary.redundant)
		    {
		      const unsigned other = watch.binary.lit;
		      if (lit < other)
			{
			  const litpair litpair = {.lits = {lit, other} };
			  PUSH_STACK (*irredundant, litpair);
			}
		    }
		  else if (redundant && watch.binary.redundant)
		    {
		      const unsigned other = watch.binary.lit;
		      if (lit < other)
			{
			  const litwatch litwatch = { lit, watch };
			  PUSH_STACK (*redundant, litwatch);
			}
		    }
		  else
		    *q++ = watch;
		}
	      else
		{
		  assert (lit_value > 0 || other_value > 0);
		  if (lit < other)
		    {
		      const bool red = watch.binary.redundant;
		      const bool hyper = watch.binary.hyper;
		      kissat_delete_binary (solver, red, hyper, lit, other);
		      collected++;
		    }
		}
	    }
	  else
	    {
	      flushed++;
	      p++;
	    }

	}
      SET_END_OF_WATCHES (*watches, q);
    }
  LOG ("flushed %zu large watches", flushed);
  LOG ("collected %zu satisfied binary clauses", collected);
  if (irredundant)
    LOG ("saved %zu irredundant binary clauses", SIZE_STACK (*irredundant));
  if (redundant)
    LOG ("saved %zu redundant binary clauses", SIZE_STACK (*redundant));
  (void) collected;
  (void) flushed;
}

void
kissat_enter_dense_mode (kissat * solver,
			 litpairs * irredundant, litwatches * redundant)
{
  assert (!solver->level);
  assert (solver->watching);
  assert (solver->propagated == SIZE_STACK (solver->trail));
  LOG ("entering dense mode with full occurrence lists");
  if (irredundant || redundant)
    flush_large_watches (solver, irredundant, redundant);
  else
    kissat_flush_large_watches (solver);
  LOG ("switched to full occurrence lists");
  solver->watching = false;
}

static void
resume_watching_binaries_after_elimination (kissat * solver,
					    litwatches * binaries)
{
  assert (binaries);
#ifdef LOGGING
  size_t resumed_watching = 0;
  size_t flushed_eliminated = 0;
#endif
  const flags *flags = solver->flags;
  watches *all_watches = solver->watches;
  for (all_stack (litwatch, litwatch, *binaries))
    {
      const unsigned first = litwatch.lit;
      watch watch = litwatch.watch;
      const unsigned second = watch.binary.lit;
      const unsigned first_idx = IDX (first);
      const unsigned second_idx = IDX (second);
      if (!flags[first_idx].eliminated && !flags[second_idx].eliminated)
	{
	  watches *first_watches = all_watches + first;
	  PUSH_WATCHES (*first_watches, watch);
	  watches *second_watches = all_watches + second;
	  watch.binary.lit = first;
	  PUSH_WATCHES (*second_watches, watch);
#ifdef LOGGING
	  resumed_watching++;
#endif
	}
      else
	{
	  const bool redundant = watch.binary.redundant;
	  const bool hyper = watch.binary.hyper;
	  kissat_delete_binary (solver, redundant, hyper, first, second);
#ifdef LOGGING
	  flushed_eliminated++;
#endif
	}
    }
  LOG ("resumed watching %zu binary clauses flushed %zu eliminated",
       resumed_watching, flushed_eliminated);
}

static void
completely_resume_watching_binaries (kissat * solver, litwatches * binaries)
{
  assert (binaries);
#ifdef LOGGING
  size_t resumed_watching = 0;
#endif
  watches *all_watches = solver->watches;
  for (all_stack (litwatch, litwatch, *binaries))
    {
      const unsigned first = litwatch.lit;
      watch watch = litwatch.watch;
      const unsigned second = watch.binary.lit;
      assert (!ELIMINATED (IDX (first)));
      assert (!ELIMINATED (IDX (second)));
      watches *first_watches = all_watches + first;
      PUSH_WATCHES (*first_watches, watch);
      watches *second_watches = all_watches + second;
      watch.binary.lit = first;
      PUSH_WATCHES (*second_watches, watch);
#ifdef LOGGING
      resumed_watching++;
#endif
    }
  LOG ("resumed watching %zu binary clauses", resumed_watching);
}

static void
resume_watching_irredundant_binaries (kissat * solver, litpairs * binaries)
{
  assert (binaries);
#ifdef LOGGING
  size_t resumed_watching = 0;
#endif
  watches *all_watches = solver->watches;
  for (all_stack (litpair, litpair, *binaries))
    {
      const unsigned first = litpair.lits[0];
      const unsigned second = litpair.lits[1];

      assert (!ELIMINATED (IDX (first)));
      assert (!ELIMINATED (IDX (second)));

      watches *first_watches = all_watches + first;
      watch first_watch = kissat_binary_watch (second, false, false);
      PUSH_WATCHES (*first_watches, first_watch);

      watches *second_watches = all_watches + second;
      watch second_watch = kissat_binary_watch (first, false, false);
      PUSH_WATCHES (*second_watches, second_watch);

#ifdef LOGGING
      resumed_watching++;
#endif
    }
  LOG ("resumed watching %zu binary clauses", resumed_watching);
}

static void
resume_watching_large_clauses_after_elimination (kissat * solver)
{
#ifdef LOGGING
  size_t resumed_watching_redundant = 0;
  size_t resumed_watching_irredundant = 0;
#endif
  const flags *flags = solver->flags;
  watches *watches = solver->watches;
  const value *values = solver->values;
  const assigned *assigned = solver->assigned;
  const word *arena = BEGIN_STACK (solver->arena);

  for (all_clauses (c))
    {
      if (c->garbage)
	continue;
      bool collect = false;
      for (all_literals_in_clause (lit, c))
	{
	  if (values[lit] > 0)
	    {
	      LOGCLS (c, "%s satisfied", LOGLIT (lit));
	      collect = true;
	      break;
	    }
	  const unsigned idx = IDX (lit);
	  if (flags[idx].eliminated)
	    {
	      LOGCLS (c, "containing eliminated %s", LOGLIT (lit));
	      collect = true;
	      break;
	    }
	}
      if (collect)
	{
	  kissat_mark_clause_as_garbage (solver, c);
	  continue;
	}

      assert (c->size > 2);

      unsigned *lits = c->lits;
      kissat_sort_literals (solver, values, assigned, c->size, lits);
      c->searched = 2;

      const reference ref = (word *) c - arena;
      const unsigned l0 = lits[0];
      const unsigned l1 = lits[1];

      kissat_push_blocking_watch (solver, watches + l0, l1, ref);
      kissat_push_blocking_watch (solver, watches + l1, l0, ref);

#ifdef LOGGING
      if (c->redundant)
	resumed_watching_redundant++;
      else
	resumed_watching_irredundant++;
#endif
    }
  LOG ("resumed watching %zu irredundant and %zu redundant large clauses",
       resumed_watching_irredundant, resumed_watching_redundant);
}

void
kissat_resume_sparse_mode (kissat * solver, bool flush_eliminated,
			   litpairs * irredundant, litwatches * redundant)
{
  assert (!solver->level);
  assert (!solver->watching);
  if (solver->inconsistent)
    return;
  LOG ("resuming sparse mode watching clauses");
  kissat_flush_large_connected (solver);
  LOG ("switched to watching clauses");
  solver->watching = true;
  if (irredundant)
    {
      LOG ("resuming watching %zu irredundant binaries",
	   SIZE_STACK (*irredundant));
      resume_watching_irredundant_binaries (solver, irredundant);
    }
  if (redundant)
    {
      LOG ("resuming watching %zu redundant binaries",
	   SIZE_STACK (*redundant));
      if (flush_eliminated)
	resume_watching_binaries_after_elimination (solver, redundant);
      else
	completely_resume_watching_binaries (solver, redundant);
    }
  if (flush_eliminated)
    resume_watching_large_clauses_after_elimination (solver);
  else
    kissat_watch_large_clauses (solver);
  LOG ("forcing to propagate units on all clauses");
  solver->propagated = 0;

  clause *conflict;
  if (solver->probing)
    conflict = kissat_probing_propagate (solver, 0);
  else
    conflict = kissat_search_propagate (solver);
  if (conflict)
    {
      LOG ("conflict during propagation after resuming sparse mode");
      solver->inconsistent = true;
      CHECK_AND_ADD_EMPTY ();
      ADD_EMPTY_TO_PROOF ();
    }
  else if (solver->unflushed)
    kissat_flush_trail (solver);
}
