#define INLINE_SORT

#include "dense.h"
#include "inline.h"
#include "propsearch.h"
#include "proprobe.h"
#include "trail.h"

#include "sort.c"

static void flush_large_watches(kissat *solver, litpairs *irredundant, litwatches *redundant)
{
    assert(!solver->level);
    assert(solver->watching);
#ifndef LOGGING
    LOG("flushing large watches");
    if (irredundant)
        LOG("flushing and saving irredundant binary clauses too");
    else
        LOG("keep watching irredundant binary clauses");
    if (redundant)
        LOG("flushing and saving redundant clauses too");
    else
        LOG("keep watching redundant binary clauses");
#endif
    const value *values = solver->values;
    size_t flushed = 0, collected = 0;
    watches *all_watches = solver->watches;
    for (all_literals(lit)) {
        const value lit_value = values[lit];
        watches *watches = all_watches + lit;
        watch *begin = BEGIN_WATCHES(*watches), *q = begin;
        const watch *end_watches = END_WATCHES(*watches), *p = q;
        while (p != end_watches) {
            const watch watch = *p++;
            if (watch.type.binary) {
                const unsigned other = watch.binary.lit;
                const value other_value = values[other];
                if (!lit_value && !other_value) {
                    if (irredundant && !watch.binary.redundant) {
                        const unsigned other = watch.binary.lit;
                        if (lit < other) {
                            const litpair litpair = {
                                .lits = { lit, other }
                            };
                            PUSH_STACK(*irredundant, litpair);
                        }
                    } else if (redundant && watch.binary.redundant) {
                        const unsigned other = watch.binary.lit;
                        if (lit < other) {
                            const litwatch litwatch = { lit, watch };
                            PUSH_STACK(*redundant, litwatch);
                        }
                    } else
                        *q++ = watch;
                } else {
                    assert(lit_value > 0 || other_value > 0);
                    if (lit < other) {
                        const bool red = watch.binary.redundant;
                        const bool hyper = watch.binary.hyper;
                        kissat_delete_binary(solver, red, hyper, lit, other);
                        collected++;
                    }
                }
            } else {
                flushed++;
                p++;
            }
        }
        SET_END_OF_WATCHES(*watches, q);
    }
    LOG("flushed %zu large watches", flushed);
    LOG("collected %zu satisfied binary clauses", collected);
    if (irredundant)
        LOG("saved %zu irredundant binary clauses", SIZE_STACK(*irredundant));
    if (redundant)
        LOG("saved %zu redundant binary clauses", SIZE_STACK(*redundant));
    (void)collected;
    (void)flushed;
}

void kissat_enter_dense_mode(kissat *solver, litpairs *irredundant, litwatches *redundant)
{
    assert(!solver->level);
    assert(solver->watching);
    assert(solver->propagated == SIZE_STACK(solver->trail));
    LOG("entering dense mode with full occurrence lists");
    if (irredundant || redundant)
        flush_large_watches(solver, irredundant, redundant);
    else
        kissat_flush_large_watches(solver);
    LOG("switched to full occurrence lists");
    solver->watching = false;
}

static void resume_watching_binaries_after_elimination(kissat *solver, litwatches *binaries)
{
    assert(binaries);
#ifdef LOGGING
    size_t resumed_watching = 0;
    size_t flushed_eliminated = 0;
#endif
    const flags *flags = solver->flags;
    watches *all_watches = solver->watches;
    for (all_stack(litwatch, litwatch, *binaries)) {
        const unsigned first = litwatch.lit;
        watch watch = litwatch.watch;
        const unsigned second = watch.binary.lit;
        const unsigned first_idx = IDX(first);
        const unsigned second_idx = IDX(second);
        if (!flags[first_idx].eliminated && !flags[second_idx].eliminated) {
            watches *first_watches = all_watches + first;
            PUSH_WATCHES(*first_watches, watch);
            watches *second_watches = all_watches + second;
            watch.binary.lit = first;
            PUSH_WATCHES(*second_watches, watch);
#ifdef LOGGING
            resumed_watching++;
#endif
        } else {
            const bool redundant = watch.binary.redundant;
            const bool hyper = watch.binary.hyper;
            kissat_delete_binary(solver, redundant, hyper, first, second);
#ifdef LOGGING
            flushed_eliminated++;
#endif
        }
    }
    LOG("resumed watching %zu binary clauses flushed %zu eliminated", resumed_watching, flushed_eliminated);
}

static void completely_resume_watching_binaries(kissat *solver, litwatches *binaries)
{
    assert(binaries);
#ifdef LOGGING
    size_t resumed_watching = 0;
#endif
    watches *all_watches = solver->watches;
    for (all_stack(litwatch, litwatch, *binaries)) {
        const unsigned first = litwatch.lit;
        watch watch = litwatch.watch;
        const unsigned second = watch.binary.lit;
        assert(!ELIMINATED(IDX(first)));
        assert(!ELIMINATED(IDX(second)));
        watches *first_watches = all_watches + first;
        PUSH_WATCHES(*first_watches, watch);
        watches *second_watches = all_watches + second;
        watch.binary.lit = first;
        PUSH_WATCHES(*second_watches, watch);
#ifdef LOGGING
        resumed_watching++;
#endif
    }
    LOG("resumed watching %zu binary clauses", resumed_watching);
}

static void resume_watching_irredundant_binaries(kissat *solver, litpairs *binaries)
{
    assert(binaries);
#ifdef LOGGING
    size_t resumed_watching = 0;
#endif
    watches *all_watches = solver->watches;
    for (all_stack(litpair, litpair, *binaries)) {
        const unsigned first = litpair.lits[0];
        const unsigned second = litpair.lits[1];

        assert(!ELIMINATED(IDX(first)));
        assert(!ELIMINATED(IDX(second)));

        watches *first_watches = all_watches + first;
        watch first_watch = kissat_binary_watch(second, false, false);
        PUSH_WATCHES(*first_watches, first_watch);

        watches *second_watches = all_watches + second;
        watch second_watch = kissat_binary_watch(first, false, false);
        PUSH_WATCHES(*second_watches, second_watch);

#ifdef LOGGING
        resumed_watching++;
#endif
    }
    LOG("resumed watching %zu binary clauses", resumed_watching);
}

static void resume_watching_large_clauses_after_elimination(kissat *solver)
{
#ifdef LOGGING
    size_t resumed_watching_redundant = 0;
    size_t resumed_watching_irredundant = 0;
#endif
    const flags *flags = solver->flags;
    watches *watches = solver->watches;
    const value *values = solver->values;
    const assigned *assigned = solver->assigned;
    const word *arena = BEGIN_STACK(solver->arena);

    for (all_clauses(c)) {
        if (c->garbage)
            continue;
        bool collect = false;
        for (all_literals_in_clause(lit, c)) {
            if (values[lit] > 0) {
                LOGCLS(c, "%s satisfied", LOGLIT(lit));
                collect = true;
                break;
            }
            const unsigned idx = IDX(lit);
            if (flags[idx].eliminated) {
                LOGCLS(c, "containing eliminated %s", LOGLIT(lit));
                collect = true;
                break;
            }
        }
        if (collect) {
            kissat_mark_clause_as_garbage(solver, c);
            continue;
        }

        assert(c->size > 2);

        unsigned *lits = c->lits;
        kissat_sort_literals(solver, values, assigned, c->size, lits);
        c->searched = 2;

        const reference ref = (word *)c - arena;
        const unsigned l0 = lits[0];
        const unsigned l1 = lits[1];

        kissat_push_blocking_watch(solver, watches + l0, l1, ref);
        kissat_push_blocking_watch(solver, watches + l1, l0, ref);

#ifdef LOGGING
        if (c->redundant)
            resumed_watching_redundant++;
        else
            resumed_watching_irredundant++;
#endif
    }
    LOG("resumed watching %zu irredundant and %zu redundant large clauses", resumed_watching_irredundant,
        resumed_watching_redundant);
}

void kissat_resume_sparse_mode(kissat *solver, bool flush_eliminated, litpairs *irredundant, litwatches *redundant)
{
    assert(!solver->level);
    assert(!solver->watching);
    if (solver->inconsistent)
        return;
    LOG("resuming sparse mode watching clauses");
    kissat_flush_large_connected(solver);
    LOG("switched to watching clauses");
    solver->watching = true;
    if (irredundant) {
        LOG("resuming watching %zu irredundant binaries", SIZE_STACK(*irredundant));
        resume_watching_irredundant_binaries(solver, irredundant);
    }
    if (redundant) {
        LOG("resuming watching %zu redundant binaries", SIZE_STACK(*redundant));
        if (flush_eliminated)
            resume_watching_binaries_after_elimination(solver, redundant);
        else
            completely_resume_watching_binaries(solver, redundant);
    }
    if (flush_eliminated)
        resume_watching_large_clauses_after_elimination(solver);
    else
        kissat_watch_large_clauses(solver);
    LOG("forcing to propagate units on all clauses");
    solver->propagated = 0;

    clause *conflict;
    if (solver->probing)
        conflict = kissat_probing_propagate(solver, 0);
    else
        conflict = kissat_search_propagate(solver);
    if (conflict) {
        LOG("conflict during propagation after resuming sparse mode");
        solver->inconsistent = true;
        CHECK_AND_ADD_EMPTY();
        ADD_EMPTY_TO_PROOF();
    } else if (solver->unflushed)
        kissat_flush_trail(solver);
}
