#include "search.h"
#include "analyze.h"
#include "bump.h"
#include "classify.h"
#include "decide.h"
#include "dynauto.h"
#include "dynctrl.h"
#include "eliminate.h"
#include "inline.h"
#include "internal.h"
#include "logging.h"
#include "lucky.h"
#include "pred.h"
#include "preprocess.h"
#include "print.h"
#include "probe.h"
#include "propsearch.h"
#include "reduce.h"
#include "reluctant.h"
#include "reorder.h"
#include "rephase.h"
#include "report.h"
#include "restart.h"
#include "terminate.h"
#include "trail.h"
#include "walk.h"

#include <inttypes.h>
#include <math.h>

typedef enum simplifier_kind {
  NO_SIMPLIFIER = 0,
  REDUCE_SIMPLIFIER,
  PROBE_SIMPLIFIER,
  ELIMINATE_SIMPLIFIER,
} simplifier_kind;

static inline double normalize_effectiveness (double effectiveness) {
  if (effectiveness <= 0.0)
    return 0.0;
  // Map arbitrary positive effectiveness into \[0,1) using a cheap
  // saturating transform that is smooth near zero but quickly flattens.
  const double capped = effectiveness > 1e6 ? 1e6 : effectiveness;
  return capped / (capped + 1.0);
}

static inline double normalize_age (uint64_t age) {
  if (!age)
    return 0.0;
  // Age is in conflicts or ticks; use a light-weight saturation that
  // reaches ~1.0 once the age is comfortably "old enough".
  const double x = (double) age / (double) (age + 1024.0);
  return x < 0.0 ? 0.0 : (x > 1.0 ? 1.0 : x);
}

static simplifier_kind kissat_next_simplifier (kissat *solver) {
  // Only schedule heavy inprocessing if it would have been considered in
  // the original fixed priority chain.
  const bool reducing = kissat_reducing (solver);
  const bool probing = kissat_probing (solver);
  const bool eliminating = kissat_eliminating (solver);

  if (!reducing && !probing && !eliminating)
    return NO_SIMPLIFIER;

  statistics *statistics = &solver->statistics;

  // Recent effectiveness signals (already maintained by the respective
  // modules) are first normalized into \[0,1).
  const double reduce_eff =
      normalize_effectiveness (statistics->reduce_recent_effectiveness);
  const double probe_eff =
      normalize_effectiveness (statistics->probe_recent_effectiveness);
  const double elim_eff =
      normalize_effectiveness (statistics->eliminate_recent_effectiveness);

  // Lazily-initialized long-term scaling factors that down/up-weight
  // simplifiers based on their historical cost-effectiveness.
  double reduce_scale = statistics->reduce_weight_scale;
  double probe_scale = statistics->probe_weight_scale;
  double elim_scale = statistics->eliminate_weight_scale;

  if (reduce_scale <= 0.0)
    reduce_scale = 1.0;
  if (probe_scale <= 0.0)
    probe_scale = 1.0;
  if (elim_scale <= 0.0)
    elim_scale = 1.0;

  // Keep scales in a conservative range so that option weights remain the
  // dominant signal and the scheduler stays robust.
  const double min_scale = 0.25;
  const double max_scale = 4.0;
  if (reduce_scale < min_scale)
    reduce_scale = min_scale;
  if (reduce_scale > max_scale)
    reduce_scale = max_scale;
  if (probe_scale < min_scale)
    probe_scale = min_scale;
  if (probe_scale > max_scale)
    probe_scale = max_scale;
  if (elim_scale < min_scale)
    elim_scale = min_scale;
  if (elim_scale > max_scale)
    elim_scale = max_scale;

  // Simple recency terms from last-run timestamps: older runs get a
  // slightly larger multiplicative boost.
  const uint64_t now_conflicts = CONFLICTS;
  const uint64_t reduce_age_conflicts =
      now_conflicts > solver->last.conflicts.reduce
          ? now_conflicts - solver->last.conflicts.reduce
          : 0;

  const uint64_t now_ticks = statistics->search_ticks;
  const uint64_t probe_age_ticks =
      now_ticks > solver->last.ticks.probe
          ? now_ticks - solver->last.ticks.probe
          : 0;
  const uint64_t elim_age_ticks =
      now_ticks > solver->last.ticks.eliminate
          ? now_ticks - solver->last.ticks.eliminate
          : 0;

  const double reduce_age = normalize_age (reduce_age_conflicts);
  const double probe_age = normalize_age (probe_age_ticks);
  const double elim_age = normalize_age (elim_age_ticks);

  // Global memory pressure and focused-mode stagnation flags are used as
  // coarse-grained multipliers that bias the scheduler without making it
  // too brittle.
  const double mem_stress = 1.0; // kissat_memory_stress_factor (solver);
  const bool stagnant_focused =
      (!solver->stable && solver->stagnant && GET_OPTION (restartadaptive));

  double w_reduce = 0.0;
  double w_probe = 0.0;
  double w_elim = 0.0;

  if (reducing && GET_OPTION (simplifyreduceweight) > 0)
    w_reduce = (double) GET_OPTION (simplifyreduceweight) * reduce_scale;

  if (probing && GET_OPTION (simplifyprobeweight) > 0)
    w_probe = (double) GET_OPTION (simplifyprobeweight) * probe_scale;

  if (eliminating && GET_OPTION (simplifyelimweight) > 0)
    w_elim = (double) GET_OPTION (simplifyelimweight) * elim_scale;

  double score_reduce = 0.0, score_probe = 0.0, score_elim = 0.0;

  if (w_reduce > 0.0) {
    double score = w_reduce * (1.0 + reduce_eff) * (1.0 + 0.5 * reduce_age);
    // Under memory pressure, prefer running reduction earlier; when memory
    // is cheap, scale the score down a bit.
    if (mem_stress > 0.0 && mem_stress != 1.0)
      score *= mem_stress;
    score_reduce = score;
  }

  if (w_probe > 0.0) {
    double score = w_probe * (1.0 + probe_eff) * (1.0 + 0.5 * probe_age);
    // Probing is comparatively less memory-driven, but when memory is cheap
    // we prefer to probe more and when it is expensive we de-emphasize it.
    if (mem_stress > 0.0 && mem_stress != 1.0) {
      const double mem_scale = 2.0 - mem_stress;
      score *= mem_scale;
    }
    if (stagnant_focused)
      score *= 1.2;
    score_probe = score;
  }

  if (w_elim > 0.0) {
    double score = w_elim * (1.0 + elim_eff) * (1.0 + 0.5 * elim_age);
    // Variable elimination is fairly expensive but helpful under both
    // memory pressure and focused stagnation.
    if (mem_stress > 0.0 && mem_stress != 1.0)
      score *= mem_stress;
    if (stagnant_focused)
      score *= 1.1;
    score_elim = score;
  }

  // Fall back to the original priority order if scores are numerically tied
  // or only one candidate is available.
  simplifier_kind best = NO_SIMPLIFIER;
  double best_score = 0.0;

  if (score_reduce > best_score) {
    best_score = score_reduce;
    best = REDUCE_SIMPLIFIER;
  }
  if (score_probe > best_score) {
    best_score = score_probe;
    best = PROBE_SIMPLIFIER;
  }
  if (score_elim > best_score) {
    best_score = score_elim;
    best = ELIMINATE_SIMPLIFIER;
  }

  // If all computed scores are zero (e.g., no recent-effectiveness data
  // yet), keep the original deterministic order among enabled simplifiers.
  if (best == NO_SIMPLIFIER) {
    if (reducing)
      best = REDUCE_SIMPLIFIER;
    else if (probing)
      best = PROBE_SIMPLIFIER;
    else if (eliminating)
      best = ELIMINATE_SIMPLIFIER;
  }

  return best;
}

static void init_tiers (kissat *solver) {
  for (unsigned stable = 0; stable != 2; stable++) {
    if (!solver->tier1[stable]) {
      assert (!solver->tier2[stable]);
      solver->tier1[stable] = GET_OPTION (tier1);
      solver->tier2[stable] = GET_OPTION (tier2);
      if (solver->tier2[stable] <= solver->tier1[stable])
        solver->tier2[stable] = solver->tier1[stable];
      LOG ("initialized tier1[%u] glue to %u", stable,
           solver->tier1[stable]);
      LOG ("initialized tier2[%u] glue to %u", stable,
           solver->tier2[stable]);
      if (!solver->limits.glue.interval)
        solver->limits.glue.interval = 2;
    }
  }
}

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

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

  // sc2024_it begin
  solver->stable = stable;
  solver->stagnant = false;
  solver->diversifying = false;
  solver->last_focused_decision_rate = 0.0;
  solver->last_focused_decision_conflicts = 0;
  solver->num_recent_conflict_vars = 0;
  solver->recent_conflict_pos = 0;
  // sc2024_it end

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

  kissat_init_averages (solver, &AVERAGES);

  kissat_classify (solver);

  if (solver->stable) {
    kissat_init_reluctant (solver);
    kissat_update_scores (solver);
  }

  DYN_IF_NOT (sc2025) { init_tiers (solver); }

  kissat_init_limits (solver);

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

#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) {
  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->termination.flagged) {
    kissat_very_verbose (solver, "termination forced externally");
    solver->termination.flagged = 0;
  }

  if (solver->stable) {
    REPORT (0, ']');
    STOP (stable);
    solver->stable = false;
  } else {
    REPORT (0, '}');
    STOP (focused);
  }
  STOP (search);
}

static void report_search_result (kissat *solver, int res) {
#ifndef QUIET
  LOG ("search result %d", res);
  char type = (res == 10 ? '1' : res == 20 ? '0' : '?');
  REPORT (0, type);
#else
  (void) solver, (void) res;
#endif
}

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;
}

static bool searching (kissat *solver) {
  if (!kissat_propagated (solver))
    return true;
  if (!GET_OPTION (probeinit))
    return true;
  if (!GET_OPTION (eliminateinit))
    return true;
  if (conflict_limit_hit (solver))
    return false;
  return true;
}

int kissat_search (kissat *solver) {
  REPORT (0, '*');
  int res = 0;
  if (solver->inconsistent)
    res = 20;
  if (!res && GET_OPTION (luckyearly))
    res = kissat_lucky (solver);
  if (!res && kissat_preprocessing (solver))
    res = kissat_preprocess (solver);
  if (!res && GET_OPTION (luckylate))
    res = kissat_lucky (solver);
  if (!res)
    kissat_classify (solver);
  if (!res) {
    kissat_dyn_auto (solver);
    if (GET_OPTION (featuresonly))
      return res;
  }
  if (!res) {
    DYN_IF (pred) {
      if (!res)
        kissat_predict (solver);
      if (GET_OPTION (predictonly))
        return res;
    }
  }
  if (!res && searching (solver)) {
    start_search (solver);
    DYN_IF (sc2022_bulky) {
      if (kissat_walking (solver)) {
        kissat_walk (solver);
        REPORT (0, 'W');
      }
    }
    while (!res) {
      clause *conflict = kissat_search_propagate (solver);
      if (conflict) {
        res = kissat_analyze (solver, conflict);
        DYN_IF (clause) {
          if (kissat_decaying (solver))
            kissat_decay (solver);
        }
      } else if (solver->iterating)
        iterate (solver);
      else if (!solver->unassigned)
        res = 10;
      else if (TERMINATED (search_terminated_1))
        break;
      else if (kissat_reducing (solver))
        res = kissat_reduce (solver);
      else if (kissat_switching_search_mode (solver))
        kissat_switch_search_mode (solver);
      else if (kissat_restarting (solver))
        kissat_restart (solver);
      else
        DYN_IF_NOT (sc2022_bulky) {
          if (kissat_reordering (solver))
            kissat_reorder (solver);
          else if (kissat_rephasing (solver))
            kissat_rephase (solver);
          else
            DYN_IF (sc2024_it) {
              const simplifier_kind next = kissat_next_simplifier (solver);
              if (next == REDUCE_SIMPLIFIER)
                res = kissat_reduce (solver);
              else if (next == PROBE_SIMPLIFIER)
                res = kissat_probe (solver);
              else if (next == ELIMINATE_SIMPLIFIER)
                res = kissat_eliminate (solver);
              else if (conflict_limit_hit (solver))
                break;
              else if (decision_limit_hit (solver))
                break;
              else
                kissat_decide (solver);
            }
          else if (kissat_probing (solver)) res = kissat_probe (solver);
          else if (kissat_eliminating (solver)) res =
              kissat_eliminate (solver);
          else if (conflict_limit_hit (solver)) break;
          else if (decision_limit_hit (solver)) break;
          else kissat_decide (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 (conflict_limit_hit (solver)) break;
      else if (decision_limit_hit (solver)) break;
      else kissat_decide (solver);
    }
    stop_search (solver);
  }
  report_search_result (solver, res);
  return res;
}
