#include "inline.h"

#ifndef HYPER_PROPAGATION
static inline void kissat_watch_large_delayed(kissat *solver, unsigneds *delayed)
{
    const unsigned *end_delayed = END_STACK(*delayed);
    const unsigned *d = BEGIN_STACK(*delayed);
    watches *all_watches = solver->watches;
    while (d != end_delayed) {
        const unsigned lit = *d++;
        assert(d != end_delayed);
        const watch watch = {
            .raw = *d++
        };
        assert(!watch.type.binary);
        watches *lit_watches = all_watches + lit;
        assert(d != end_delayed);
        const reference ref = *d++;
        const unsigned blocking = watch.blocking.lit;
        //LOGREF(ref, "watching %s blocking %s in", LOGLIT(lit), LOGLIT(blocking));
        kissat_push_blocking_watch(solver, lit_watches, blocking, ref);
    }
    CLEAR_STACK(*delayed);
}

#endif

#if defined(HYPER_PROPAGATION) || defined(PROBING_PROPAGATION)

static inline void kissat_update_probing_propagation_statistics(kissat *solver, unsigned propagated)
{
    assert(propagated <= solver->propagated);
    propagated = solver->propagated - propagated;

    //LOG(PROPAGATION_TYPE " propagation took %" PRIu64 " propagations", propagated);

    //LOG(PROPAGATION_TYPE " propagation took %" PRIu64 " ticks", solver->ticks);

    ADD(propagations, propagated);
    ADD(probing_ticks, solver->ticks);

    ADD(probing_propagations, propagated);
    ADD(ticks, solver->ticks);
}

#endif

static inline void kissat_delay_watching_large(kissat *solver, unsigneds *delayed, unsigned lit, unsigned other,
    reference ref)
{
    const watch watch = kissat_blocking_watch(other);
    PUSH_STACK(*delayed, lit);
    PUSH_STACK(*delayed, watch.raw);
    PUSH_STACK(*delayed, ref);
}

static inline clause *PROPAGATE_LITERAL(kissat *solver,
#if defined(HYPER_PROPAGATION) || defined(PROBING_PROPAGATION)
    const clause *ignore,
#endif
    const unsigned lit)
{
    assert(solver->watching);
    //LOG(PROPAGATION_TYPE " propagating %s", LOGLIT(lit));
    assert(VALUE(lit) > 0);
    assert(EMPTY_STACK(solver->delayed));

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

    const unsigned not_lit = NOT(lit);
#ifdef HYPER_PROPAGATION
    const bool hyper = GET_OPTION(hyper);
#endif
    watches *watches = &WATCHES(not_lit);
    watch *begin_watches = BEGIN_WATCHES(*watches), *q = begin_watches;
    const watch *end_watches = END_WATCHES(*watches), *p = q;
    unsigneds *delayed = &solver->delayed;

    uint64_t ticks = kissat_cache_lines(watches->size, sizeof(watch));

    clause *res = 0;

    while (p != end_watches) {
        const watch head = *q++ = *p++;
        const unsigned blocking = head.blocking.lit;
        assert(VALID_INTERNAL_LITERAL(blocking));
        const value blocking_value = values[blocking];
        if (head.type.binary) {
            if (blocking_value > 0)
                continue;
            const bool redundant = head.binary.redundant;
            if (blocking_value < 0) {
                res = kissat_binary_conflict(solver, redundant, not_lit, blocking);
                break;
            } else {
                assert(!blocking_value);
                kissat_assign_binary(solver, values, assigned, redundant, blocking, not_lit);
            }
        } else {
            const watch tail = *q++ = *p++;
            if (blocking_value > 0) {
                continue;
            }


            const reference ref = tail.raw;
            assert(ref < SIZE_STACK(solver->arena));
            clause *c = (clause *)(arena + ref);
#if defined(HYPER_PROPAGATION) || defined(PROBING_PROPAGATION)
            if (c == ignore)
                continue;
#endif
            ticks++;
            if (c->garbage) {
                q -= 2;
                continue;
            }
            unsigned *lits = BEGIN_LITS(c);
            const unsigned other = lits[0] ^ lits[1] ^ not_lit;
            assert(lits[0] != lits[1]);
            assert(VALID_INTERNAL_LITERAL(other));
            assert(not_lit != other);
            assert(lit != other);
            const value other_value = values[other];
            if (other_value > 0)
                q[-2].blocking.lit = other;
            else {
                const unsigned *end_lits = lits + c->size;
                unsigned *searched = lits + c->searched;
                assert(c->lits + 2 <= searched);
                assert(searched < end_lits);
                unsigned *r, replacement = INVALID_LIT;
                value replacement_value = -1;
                for (r = searched; r != end_lits; r++) {
                    replacement = *r;
                    assert(VALID_INTERNAL_LITERAL(replacement));
                    replacement_value = values[replacement];
                    if (replacement_value >= 0)
                        break;
                    /*//手动loop unrolling?!
                    r++;
                    if(r == end_lits) break;
                    replacement = *r;
                    assert(VALID_INTERNAL_LITERAL(replacement));
                    replacement_value = values[replacement];
                    if (replacement_value >= 0)
                        break;*/

                }
                if (replacement_value < 0) {
                    for (r = lits + 2; r != searched; r++) {
                        replacement = *r;
                        assert(VALID_INTERNAL_LITERAL(replacement));
                        replacement_value = values[replacement];
                        if (replacement_value >= 0)
                            break;
                    }
                }

                if (replacement_value >= 0)
                    c->searched = r - c->lits;

                if (replacement_value > 0) {
                    assert(replacement != INVALID_LIT);
                    q[-2].blocking.lit = replacement;
                } else if (!replacement_value) {
                    assert(replacement != INVALID_LIT);
                    //LOGREF(ref, "unwatching %s in", LOGLIT(not_lit));
                    q -= 2;
                    lits[0] = other;
                    lits[1] = replacement;
                    assert(lits[0] != lits[1]);
                    *r = not_lit;
                    kissat_delay_watching_large(solver, delayed, replacement, other, ref);
                    ticks++;
                } else if (other_value) {
                    assert(replacement_value < 0);
                    assert(blocking_value < 0);
                    assert(other_value < 0);
                    //LOGREF(ref, "conflicting");
                    res = c;
                    break;
                }
#ifdef HYPER_PROPAGATION
                else if (hyper) {
                    assert(replacement_value < 0);
                    unsigned dom = kissat_find_dominator(solver, other, c);
                    if (dom != INVALID_LIT) {
                        LOGBINARY(dom, other, "hyper binary resolvent");

                        INC(hyper_binary_resolved);
                        INC(clauses_added);

                        INC(hyper_binaries);
                        INC(clauses_redundant);

                        CHECK_AND_ADD_BINARY(dom, other);
                        ADD_BINARY_TO_PROOF(dom, other);

                        kissat_assign_binary(solver, values, assigned, true, other, dom);

                        delay_watching_hyper(solver, delayed, dom, other);
                        delay_watching_hyper(solver, delayed, other, dom);

                        kissat_delay_watching_large(solver, delayed, not_lit, other, ref);

                        //LOGREF(ref, "unwatching %s in", LOGLIT(not_lit));
                        q -= 2;
                    } else
                        kissat_assign_reference(solver, values, assigned, other, ref, c);
                }
#endif
                else {
                    assert(replacement_value < 0);
                    kissat_assign_reference(solver, values, assigned, other, ref, c);
                }
            }
        }
    }
    solver->ticks += ticks;

    while (p != end_watches)
        *q++ = *p++;
    SET_END_OF_WATCHES(*watches, q);

#ifdef HYPER_PROPAGATION
    watch_hyper_delayed(solver, delayed);
#else
    kissat_watch_large_delayed(solver, delayed);
#endif

    return res;
}
