#include "analyze.h"
#include "backtrack.h"
#include "bump.h"
#include "deduce.h"
#include "inline.h"
#include "learn.h"
#include "minimize.h"

#include <inttypes.h>

#define INVALID_LEVEL UINT_MAX

static bool
one_literal_on_conflict_level(kissat *solver,
                              clause *conflict,
                              unsigned *conflict_level_ptr)
{
  assert(conflict);
  assert(conflict->size > 1);

  unsigned conflict_level = INVALID_LEVEL;
  unsigned literals_on_conflict_level = 0;
  unsigned forced_lit = INVALID_LIT;

  assigned *all_assigned = solver->assigned;

  unsigned *lits = conflict->lits;
  const unsigned conflict_size = conflict->size;
  const unsigned *end_of_lits = lits + conflict_size;

  for (const unsigned *p = lits; p != end_of_lits; p++)
  {
    const unsigned lit = *p;
    assert(VALUE(lit) < 0);
    const unsigned idx = IDX(lit);
    const unsigned level = all_assigned[idx].level;
    if (conflict_level == level)
    {
      if (++literals_on_conflict_level > 1 && level == solver->level)
        break;
    }
    else if (conflict_level == INVALID_LEVEL || conflict_level < level)
    {
      forced_lit = lit;
      conflict_level = level;
      literals_on_conflict_level = 1;
    }
  }
  assert(conflict_level != INVALID_LEVEL);
  assert(literals_on_conflict_level);

  LOG("found %u literals on conflict level %u",
      literals_on_conflict_level, conflict_level);
  *conflict_level_ptr = conflict_level;

  if (!conflict_level)
  {
    solver->inconsistent = true;
    LOG("learned empty clause from conflict at conflict level zero");
    CHECK_AND_ADD_EMPTY();
    ADD_EMPTY_TO_PROOF();
    return false;
  }

  if (conflict_level < solver->level)
  {
    LOG("forced backtracking due to conflict level %u < level %u",
        conflict_level, solver->level);
    kissat_backtrack(solver, conflict_level);
  }

  if (conflict_size > 2)
  {
    for (unsigned i = 0; i < 2; i++)
    {
      const unsigned lit = lits[i];
      const unsigned lit_idx = IDX(lit);
      unsigned highest_position = i;
      unsigned highest_literal = lit;
      unsigned highest_level = all_assigned[lit_idx].level;
      for (unsigned j = i + 1; j < conflict_size; j++)
      {
        const unsigned other = lits[j];
        const unsigned other_idx = IDX(other);
        const unsigned level = all_assigned[other_idx].level;
        if (highest_level >= level)
          continue;
        highest_literal = other;
        highest_position = j;
        highest_level = level;
        if (highest_level == conflict_level)
          break;
      }
      if (highest_position == i)
        continue;
      reference ref = INVALID_REF;
      if (highest_position > 1)
      {
        ref = kissat_reference_clause(solver, conflict);
        kissat_unwatch_blocking(solver, lit, ref);
      }
      lits[highest_position] = lit;
      lits[i] = highest_literal;
      if (highest_position > 1)
        kissat_watch_blocking(solver, lits[i], lits[!i], ref);
    }
  }

  if (literals_on_conflict_level > 1)
    return false;

  assert(literals_on_conflict_level == 1);
  assert(forced_lit != INVALID_LIT);

  LOG("reusing conflict as driving clause of %s", LOGLIT(forced_lit));
  kissat_backtrack(solver, solver->level - 1);
  if (conflict_size == 2)
  {
    assert(conflict == &solver->conflict);
    const unsigned other = lits[0] ^ lits[1] ^ forced_lit;
    kissat_assign_binary(solver, conflict->redundant, forced_lit, other);
  }
  else
  {
    const reference ref = kissat_reference_clause(solver, conflict);
    kissat_assign_reference(solver, forced_lit, ref, conflict);
  }

  return true;
}

static void
mark_literal_as_analyzed(kissat *solver, assigned *all_assigned,
                         unsigned lit, const char *type)
{
  const unsigned idx = IDX(lit);
  assigned *a = all_assigned + idx;
  if (a->analyzed == ANALYZED)
    return;
  a->analyzed = ANALYZED;
  LOG("marking %s literal %s as analyzed", type, LOGLIT(lit));
  PUSH_STACK(solver->analyzed, idx);
  (void)type;
}

static inline void
analyze_reason_side_literals(kissat *solver)
{
  assert(!solver->probing);
  if (!GET_OPTION(bumpreasons))
    return;
  assigned *all_assigned = solver->assigned;
  for (all_stack(unsigned, lit, solver->clause.lits))
    all_assigned[IDX(lit)].analyzed = ANALYZED;
  word *arena = BEGIN_STACK(solver->arena);
  for (all_stack(unsigned, lit, solver->clause.lits))
  {
    const unsigned idx = IDX(lit);
    assigned *a = all_assigned + idx;
    assert(a->level > 0);
    assert(a->reason != UNIT);
    if (a->reason == DECISION)
      continue;
    if (a->binary)
      mark_literal_as_analyzed(solver, all_assigned, a->reason,
                               "reason side");
    else
    {
      assert(a->reason < SIZE_STACK(solver->arena));
      clause *c = (clause *)(arena + a->reason);
      solver->dps_ticks++;
      for (all_literals_in_clause(lit, c))
        if (IDX(lit) != idx)
          mark_literal_as_analyzed(solver, all_assigned, lit,
                                   "reason side");
    }
  }
}

static void
reset_levels(kissat *solver)
{
  LOG("reset %zu marked levels", SIZE_STACK(solver->levels));
  frame *frames = BEGIN_STACK(solver->frames);
  for (all_stack(unsigned, level, solver->levels))
  {
    assert(level < SIZE_STACK(solver->frames));
    frame *f = frames + level;
    assert(f->used > 0);
    f->used = 0;
  }
  CLEAR_STACK(solver->levels);
}

static void
reset_markings(kissat *solver)
{
  LOG("unmarking %zu analyzed variables", SIZE_STACK(solver->analyzed));
  assigned *all_assigned = solver->assigned;
  for (all_stack(unsigned, idx, solver->analyzed))
    all_assigned[idx].analyzed = 0;
}

static void
reset_analyze(kissat *solver)
{
  LOG("reset %zu analyzed variables", SIZE_STACK(solver->analyzed));
  CLEAR_STACK(solver->analyzed);

  LOG("reset %zu learned literals", SIZE_STACK(solver->clause.lits));
  CLEAR_STACK(solver->clause.lits);
}

static void
update_trail_average(kissat *solver)
{
  assert(!solver->probing);
  const unsigned size = SIZE_STACK(solver->trail);
  const unsigned assigned = size - solver->unflushed;
  const unsigned active = solver->active;
  const double filled = kissat_percent(assigned, active);
  LOG("trail filled %.0f%% (size %u, unflushed %u, active %u)",
      filled, size, solver->unflushed, active);
  UPDATE(trail, filled);
}

int kissat_analyze(kissat *solver, clause *conflict)
{
  assert(!solver->inconsistent);
  START(analyze);
  if (!solver->probing)
  {
    update_trail_average(solver);
    UPDATE(level, solver->level);
  }
  int res;
  do
  {
    LOGCLS(conflict, "analyzing conflict %" PRIu64, CONFLICTS);
    unsigned conflict_level;
    if (one_literal_on_conflict_level(solver, conflict, &conflict_level))
      res = 1;
    else if (!conflict_level)
      res = -1;
    else if ((conflict = kissat_deduce_first_uip_clause(solver, conflict)))
    {
      reset_markings(solver);
      reset_analyze(solver);
      reset_levels(solver);
      res = 0;
    }
    else
    {
      kissat_minimize_clause(solver);
      if (!solver->probing)
        analyze_reason_side_literals(solver);
      reset_markings(solver);
      kissat_learn_clause(solver);
      solver->nconflict++;
      // MAB
      if (!solver->probing && (!solver->stable || solver->heuristic == 0))
        kissat_bump_variables(solver);
      if (!solver->probing && solver->stable && (solver->heuristic == 1))
        kissat_update_conflicted_chb(solver);

      reset_analyze(solver);
      reset_levels(solver);
      res = 1;
    }
  } while (!res);
  STOP(analyze);
  return res > 0 ? 0 : 20;
}
