#include "allocate.h"
#include "backward.h"
#include "eliminate.h"
#include "inline.h"
#include "terminate.h"

static bool
backward_subsume_lits (kissat * solver, reference ignore,
		       unsigned size, unsigned *lits)
{
  assert (size > 1);

  unsigned min_lit = INVALID_LIT;
  unsigned min_occs = UINT_MAX;

  const unsigned *end_lits = lits + size;

  for (const unsigned *p = lits; p != end_lits; p++)
    {
      const unsigned lit = *p;
      assert (!VALUE (lit));
      const unsigned occs = WATCHES (lit).size;
      if (occs >= min_occs)
	continue;
      min_occs = occs;
      min_lit = lit;
    }

  assert (min_lit != INVALID_LIT);

  const unsigned occlim = solver->bounds.subsume.occurrences;
  if (min_occs > occlim)
    return true;

  LOGTMP ("backward subsumption on %s with %zu occurrences in",
	  LOGLIT (min_lit), min_occs);

  value *marks = solver->marks;
  const value *values = solver->values;
  const word *arena = BEGIN_STACK (solver->arena);

  const unsigned clslim = solver->bounds.subsume.clause_size;

  const unsigned match =
    (size > 2 ? INVALID_LIT : (lits[0] ^ lits[1] ^ min_lit));
  const unsigned not_match = (size > 2 ? INVALID_LIT : NOT (match));

  watches *watches = &WATCHES (min_lit);
  watch *begin_watches = BEGIN_WATCHES (*watches), *q = begin_watches;
  const watch *end_watches = END_WATCHES (*watches), *p = q;

  bool found = false;
  bool marked = false;
  bool unit = false;

  assert (EMPTY_STACK (solver->delayed));
  assert (!values[min_lit]);

  bool terminated = false;

  while (p != end_watches)
    {
      terminated = TERMINATED (4);
      if (terminated)
	break;
      const watch watch = *q++ = *p++;
      if (watch.type.binary)
	{
	  assert (!watch.binary.redundant);
	  if (size > 2)
	    continue;
	  const unsigned other = watch.binary.lit;
	  if (other == not_match)
	    {
	      LOGBINARY (min_lit, match,
			 "backward hyper unary resolution"
			 "on %s first antecedent", LOGLIT (match));
	      LOGBINARY (min_lit, not_match,
			 "backward hyper unary resolution"
			 "on %s second antecedent", LOGLIT (not_match));
	      unit = true;
	      break;
	    }
	  if (other != match)
	    continue;
	  if (found)
	    {
	      LOGBINARY (min_lit, match, "duplicated");
	      INC (duplicated);
	      kissat_disconnect_binary (solver, other, min_lit);
	      kissat_delete_binary (solver, false, false, min_lit, other);
	      kissat_update_after_removing_variable (solver, IDX (other));
	      q--;
	    }
	  else
	    found = true;
	}
      else
	{
	  const reference ref = watch.large.ref;
	  if (ref == ignore)
	    {
	      assert (size > 2);
	      found = true;
	      continue;
	    }
	  assert (ref < SIZE_STACK (solver->arena));
	  struct clause *c = (struct clause *) (arena + ref);
	  if (c->garbage)
	    continue;
	  if (c->size < size)
	    continue;
	  if (c->size > clslim)
	    continue;
	  INC (subsumption_checks);
	  if (!marked)
	    {
	      for (const unsigned *l = lits; l != end_lits; l++)
		{
		  const unsigned lit = *l;
		  assert (!values[lit]);
		  marks[lit] |= 2;
		}
	      marked = true;
	    }
	  unsigned limit = c->size - size;
	  unsigned needed = size;
	  unsigned remove = INVALID_LIT;
	  for (all_literals_in_clause (lit, c))
	    {
	      assert (needed);
	      const value value = values[lit];
	      if (value > 0)
		{
		  LOGCLS (c, "satisfied by %s", LOGLIT (lit));
		  kissat_mark_clause_as_garbage (solver, c);
		  kissat_update_after_removing_clause (solver, c,
						       INVALID_LIT);
		  break;
		}
	      if (value < 0)
		{
		  if (!limit--)
		    break;
		  continue;
		}
	      if (marks[lit] & 2)
		{
		  assert (needed);
		  if (!--needed)
		    break;
		}
	      else
		{
		  const unsigned not_lit = NOT (lit);
		  if (marks[not_lit] & 2)
		    {
		      if (remove != INVALID_LIT)
			break;
		      remove = lit;
		      if (!--needed)
			break;
		    }
		  if (!limit--)
		    break;
		}
	    }
	  if (c->garbage)
	    {
	      q--;
	      continue;
	    }
	  if (needed)
	    continue;
	  if (remove == INVALID_LIT)
	    {
	      LOGCLS (c, "backward subsumed");
	      INC (subsumed);
	      INC (backward_subsumed);
	      kissat_mark_clause_as_garbage (solver, c);
	      kissat_update_after_removing_clause (solver, c, INVALID_LIT);
	      q--;
	      continue;
	    }
	  bool satisfied = false;
	  unsigned old_size = 0;
	  unsigned other = INVALID_LIT;
	  for (all_literals_in_clause (lit, c))
	    {
	      const value value = values[lit];
	      if (value > 0)
		{
		  LOGCLS (c, "satisfied by %s", LOGLIT (lit));
		  satisfied = true;
		  break;
		}
	      else if (!value)
		{
		  if (lit != min_lit && lit != remove)
		    other = lit;
		  old_size++;
		}
	    }
	  if (satisfied)
	    {
	      kissat_mark_clause_as_garbage (solver, c);
	      kissat_update_after_removing_clause (solver, c, INVALID_LIT);
	      q--;
	      continue;
	    }
	  assert (old_size <= c->size);

	  INC (strengthened);
	  INC (backward_strengthened);
	  LOGCLS (c, "backward strengthening by removing %s in",
		  LOGLIT (remove));
	  assert (remove != min_lit);
	  if (old_size == 2)
	    {
	      LOGTMP ("backward large hyper unary resolution on %s"
		      "first antecedent", LOGLIT (NOT (remove)));
	      LOGCLS (c, "backward large hyper unary resolution on %s"
		      "second antecedent", LOGLIT (remove));
	      unit = true;
	      break;
	    }
	  SHRINK_CLAUSE_IN_PROOF (c, remove, INVALID_LIT);
	  CHECK_SHRINK_CLAUSE (c, remove, INVALID_LIT);
	  if (old_size > 3)
	    {
	      unsigned *lits = c->lits;
	      unsigned new_size = 0;
	      for (unsigned i = 0; i < c->size; i++)
		{
		  unsigned lit = lits[i];
		  if (remove == lit)
		    continue;
		  const value value = values[lit];
		  if (value < 0)
		    continue;
		  assert (!value);
		  lits[new_size++] = lit;
		  kissat_mark_added_literal (solver, remove);
		}
	      assert (new_size == old_size - 1);
	      assert (new_size > 2);
	      if (!c->shrunken)
		{
		  c->shrunken = true;
		  lits[c->size - 1] = INVALID_LIT;
		}
	      c->size = new_size;
	      c->searched = 2;
	      LOGCLS (c, "backward strengthened");
	      kissat_disconnect_reference (solver, remove, ref);
	    }
	  else
	    {
	      assert (old_size == 3);
	      assert (other != INVALID_LIT);
	      assert (other != min_lit);
	      assert (other != remove);
	      PUSH_STACK (solver->delayed, other);
	      LOGCLS (c, "garbage");
	      assert (!c->garbage);
	      assert (!c->hyper);
	      const size_t bytes = kissat_actual_bytes_of_clause (c);
	      ADD (arena_garbage, bytes);
	      c->garbage = true;
	      q--;
	    }
	  kissat_mark_removed_literal (solver, remove);
	  kissat_update_after_removing_variable (solver, IDX (remove));
	}
    }

  while (p != end_watches)
    *q++ = *p++;

  SET_END_OF_WATCHES (*watches, q);

  if (unit)
    {
      LOG ("backward strengthened unit clause %s", LOGLIT (min_lit));
      kissat_assign_unit (solver, min_lit);
      CHECK_AND_ADD_UNIT (min_lit);
      ADD_UNIT_TO_PROOF (min_lit);
    }

  for (all_stack (unsigned, other, solver->delayed))
    {
      LOGBINARY (other, min_lit, "backward strengthened");
      kissat_watch_other (solver, false, false, other, min_lit);
      kissat_watch_other (solver, false, false, min_lit, other);
      kissat_mark_added_literal (solver, other);
      kissat_mark_added_literal (solver, min_lit);
    }
  CLEAR_STACK (solver->delayed);

  if (marked)
    for (const unsigned *l = lits; l != end_lits; l++)
      marks[*l] &= ~2;

  return !terminated;
}

bool
kissat_backward_subsume_temporary (kissat * solver, reference ignore)
{
  assert (!solver->watching);
  assert (GET_OPTION (backward));
  START (backward);
  unsigneds *clause = &solver->clause.lits;
  const size_t size = SIZE_STACK (*clause);
  assert (size), assert (size <= UINT_MAX);
  unsigned *lits = BEGIN_STACK (*clause);
  bool res = backward_subsume_lits (solver, ignore, size, lits);
  STOP (backward);
  return res;
}
