#include "dominate.h"
#include "inline.h"

unsigned
kissat_find_dominator (kissat * solver, unsigned lit, clause * c)
{
  assert (solver->watching);
  assert (solver->level == 1);

  LOGCLS (c, "starting to find dominator of %s from", LOGLIT (lit));

  const word *arena = BEGIN_STACK (solver->arena);
  assigned *assigned = solver->assigned;

  unsigned count = 0;
  unsigned leaf = INVALID_LIT;

  for (all_literals_in_clause (other, c))
    {
      if (other == lit)
	continue;
      assert (VALUE (other) < 0);
      const unsigned other_idx = IDX (other);
      if (!assigned[other_idx].level)
	continue;
      if (!count++)
	leaf = other;
    }
  assert (count);
  if (count < 2)
    {
      LOGCLS (c, "essentially binary");
      return INVALID_LIT;
    }

  unsigneds *analyzed = &solver->analyzed;

  assert (EMPTY_STACK (*analyzed));
  assert (leaf != INVALID_LIT);
  const unsigned leaf_idx = IDX (leaf);
  struct assigned *a = assigned + leaf_idx;
  a->analyzed = true;

  PUSH_STACK (*analyzed, leaf);

  LOG ("starting to mark implication chain at %s", LOGLIT (leaf));

  unsigned root = leaf;

  for (;;)
    {
      assert (a == ASSIGNED (root));
      if (a->reason == DECISION)
	break;
      unsigned prev = INVALID_LIT;
      if (a->binary)
	{
	  prev = a->reason;
	  LOGBINARY (root, prev, "following %s reason", LOGLIT (root));
	}
      else
	{
	  const reference ref = a->reason;
	  LOGREF (ref, "following %s reason", LOGLIT (root));
	  clause *reason = (clause *) (arena + ref);
	  assert (kissat_clause_in_arena (solver, reason));
	  for (all_literals_in_clause (other, reason))
	    {
	      if (other == NOT (root))
		continue;
	      assert (VALUE (other) < 0);
	      const unsigned other_idx = IDX (other);
	      if (!assigned[other_idx].level)
		continue;
	      if (prev != INVALID_LIT)
		{
		  assert (!solver->probing);
		  LOGCLS (reason, "early abort due to");
		  prev = INVALID_LIT;
		  break;
		}
	      prev = other;
	    }
	}
      if (prev == INVALID_LIT)
	break;
      assert (VALUE (prev) < 0);
      LOG ("marking implied %s", LOGLIT (prev));
      const unsigned prev_idx = IDX (prev);
      a = assigned + prev_idx;
      assert (!a->analyzed);
      a->analyzed = true;
      PUSH_STACK (*analyzed, prev);
      root = prev;
    }
  LOG ("root %s of implication chain", LOGLIT (root));

  unsigned unmarked = 0;
  for (all_literals_in_clause (start, c))
    {
      if (start == lit)
	continue;
      if (start == leaf)
	continue;
      assert (VALUE (start) < 0);
      const unsigned start_idx = IDX (start);
      if (!assigned[start_idx].level)
	continue;
      LOG ("starting next common dominator search at %s", LOGLIT (start));
      unsigned dom = start;
      const unsigned dom_idx = IDX (dom);
      a = assigned + dom_idx;
      while (!a->analyzed)
	{
	  assert (a == ASSIGNED (dom));
	  if (a->reason == DECISION)
	    break;
	  unsigned prev = INVALID_LIT;
	  if (a->binary)
	    {
	      prev = a->reason;
	      LOGBINARY (root, prev, "following %s reason", LOGLIT (root));
	    }
	  else
	    {
	      const reference ref = a->reason;
	      LOGREF (ref, "following %s reason", LOGLIT (root));
	      clause *reason = kissat_dereference_clause (solver, ref);
	      for (all_literals_in_clause (other, reason))
		{
		  if (other == NOT (dom))
		    continue;
		  assert (VALUE (other) < 0);
		  const unsigned other_idx = IDX (other);
		  if (!assigned[other_idx].level)
		    continue;
		  if (prev != INVALID_LIT)
		    {
		      LOGCLS (reason, "early abort due to");
		      prev = INVALID_LIT;
		      break;
		    }
		  prev = other;
		}
	    }
	  if (prev == INVALID_LIT)
	    break;
	  assert (VALUE (prev) < 0);
	  const unsigned prev_idx = IDX (prev);
	  a = assigned + prev_idx;
	  dom = prev;
	}
      LOG ("new common dominator %s of %s", LOGLIT (dom), LOGLIT (start));
      while (unmarked < SIZE_STACK (*analyzed))
	{
	  const unsigned other = PEEK_STACK (*analyzed, unmarked);
	  if (other == dom)
	    break;
	  const unsigned other_idx = IDX (other);
	  a = assigned + other_idx;
	  assert (a->analyzed);
	  a->analyzed = false;
	  unmarked++;
	}
      if (unmarked == SIZE_STACK (*analyzed))
	{
	  LOG ("all analyzed literals unmarked due to early abort");
	  break;
	}
    }

  unsigned res = INVALID_LIT;
  while (unmarked < SIZE_STACK (*analyzed))
    {
      const unsigned other = PEEK_STACK (*analyzed, unmarked);
      if (res == INVALID_LIT)
	res = other;
      const unsigned other_idx = IDX (other);
      a = assigned + other_idx;
      assert (a->analyzed);
      a->analyzed = false;
      unmarked++;
    }
  CLEAR_STACK (*analyzed);

#ifdef LOGGING
  if (res == INVALID_LIT)
    LOG ("no dominator found");
  else
    LOG ("found dominator %s", LOGLIT (res));
#endif

  return res;
}
