#include "analyze.h"
#include "decide.h"
#include "eliminate.h"
#include "internal.h"
#include "logging.h"
#include "print.h"
#include "probe.h"
#include "propsearch.h"
#include "search.h"
#include "reduce.h"
#include "reluctant.h"
#include "report.h"
#include "restart.h"
#include "terminate.h"
#include "trail.h"
#include "walk.h"

#include <inttypes.h>

static void
start_search(kissat *solver)
{
  START(search);
  INC(searches);

  REPORT(0, '*');

  bool stable = (GET_OPTION(stable) == 2);

  solver->stable = stable;
  kissat_phase(solver, "search", GET(searches),
               "initializing %s search after %" PRIu64 " conflicts",
               (stable ? "stable" : "focus"), CONFLICTS);

  kissat_init_averages(solver, &AVERAGES);

  if (solver->stable)
    kissat_init_reluctant(solver);

  kissat_init_limits(solver);

  unsigned seed = GET_OPTION(seed);
  solver->random = seed;
  LOG("initialized random number generator with seed %u", seed);

  kissat_reset_rephased(solver);

  const unsigned eagersubsume = GET_OPTION(eagersubsume);
  if (eagersubsume && !solver->clueue.elements)
    kissat_init_clueue(solver, &solver->clueue, eagersubsume);
#ifndef QUIET
  limits *limits = &solver->limits;
  limited *limited = &solver->limited;
  if (!limited->conflicts && !limited->decisions)
    kissat_very_verbose(solver, "starting unlimited search");
  else if (limited->conflicts && !limited->decisions)
    kissat_very_verbose(solver,
                        "starting search with conflicts limited to %" PRIu64,
                        limits->conflicts);
  else if (!limited->conflicts && limited->decisions)
    kissat_very_verbose(solver,
                        "starting search with decisions limited to %" PRIu64,
                        limits->decisions);
  else
    kissat_very_verbose(solver,
                        "starting search with decisions limited to %" PRIu64
                        " and conflicts limited to %" PRIu64,
                        limits->decisions, limits->conflicts);
  if (stable)
  {
    START(stable);
    REPORT(0, '[');
  }
  else
  {
    START(focused);
    REPORT(0, '{');
  }
#endif
}

static void
stop_search(kissat *solver, int res)
{
  if (solver->limited.conflicts)
  {
    LOG("reset conflict limit");
    solver->limited.conflicts = false;
  }

  if (solver->limited.decisions)
  {
    LOG("reset decision limit");
    solver->limited.decisions = false;
  }

  if (solver->terminate)
  {
    kissat_very_verbose(solver, "termination forced externally");
    solver->terminate = 0;
  }

#ifndef QUIET
  LOG("search result %d", res);
  if (solver->stable)
  {
    REPORT(0, ']');
    STOP(stable);
    solver->stable = false;
  }
  else
  {
    REPORT(0, '}');
    STOP(focused);
  }
  char type = (res == 10 ? '1' : res == 20 ? '0'
                                           : '?');
  REPORT(0, type);
#else
  (void)res;
#endif

  STOP(search);
}

static void
iterate(kissat *solver)
{
  assert(solver->iterating);
  solver->iterating = false;
  REPORT(0, 'i');
}

static bool
conflict_limit_hit(kissat *solver)
{
  if (!solver->limited.conflicts)
    return false;
  if (solver->limits.conflicts > solver->statistics.conflicts)
    return false;
  kissat_very_verbose(solver, "conflict limit %" PRIu64 " hit after %" PRIu64 " conflicts",
                      solver->limits.conflicts,
                      solver->statistics.conflicts);
  return true;
}

static bool
decision_limit_hit(kissat *solver)
{
  if (!solver->limited.decisions)
    return false;
  if (solver->limits.decisions > solver->statistics.decisions)
    return false;
  kissat_very_verbose(solver, "decision limit %" PRIu64 " hit after %" PRIu64 " decisions",
                      solver->limits.decisions,
                      solver->statistics.decisions);
  return true;
}

int kissat_search(kissat *solver)
{
  start_search(solver);

  int res = kissat_walk_initially(solver);

  while (!res)
  {
    if (!solver->level && solver->reseting)
    {
      kissat_shuffle_score(solver);
      solver->reseting = 0;
    }
    if (!solver->level) {
      int should_free = 0;
      if (!kissat_importUnitClauses(solver)) return 20;
      if (!kissat_importClauses(solver)) return 20;
      if (solver->dps == 1)
        solver->cbk_free_clauses(solver->issuer);
    }
    clause *conflict = kissat_search_propagate(solver);
    if (conflict)
      res = kissat_analyze(solver, conflict);
    else if (solver->iterating)
      iterate(solver);
    else if (!solver->unassigned)
      res = 10;
    else if (TERMINATED(11))
      break;
    else if (conflict_limit_hit(solver))
      break;
    else if (kissat_reducing(solver))
      res = kissat_reduce(solver);
    else if (kissat_restarting(solver))
      kissat_restart(solver);
    else if (kissat_rephasing(solver))
      kissat_rephase(solver);
    else if (kissat_eliminating(solver))
      res = kissat_eliminate(solver);
    else if (kissat_probing(solver))
      res = kissat_probe(solver);
    else if (!solver->level && solver->unflushed)
      kissat_flush_trail(solver);
    else if (decision_limit_hit(solver))
      break;
    else
      kissat_decide(solver);
  }

  stop_search(solver, res);

  return res;
}
