#include "eliminate.h"
#include "gates.h"
#include "inline.h"
#include "sort.h"
#include "terminate.h"
#include "xors.h"

static void mark_literals(kissat *solver, value *marks, unsigned size, unsigned *lits, unsigned signs)
{
    assert(size < 32);
    for (unsigned i = 0, bit = 1; i < size; i++, bit <<= 1) {
        unsigned sign = ((bit & signs) != 0);
        const unsigned lit = lits[i] ^ sign;
        assert(!marks[lit]);
        marks[lit] = 1;
        LOG("marked %s", LOGLIT(lit));
    }
#ifndef LOGGING
    (void)solver;
#endif
}

static void unmark_literals(kissat *solver, value *marks, unsigned size, unsigned *lits, unsigned signs)
{
    assert(size < 32);
    for (unsigned i = 0, bit = 1; i < size; i++, bit <<= 1) {
        unsigned sign = ((bit & signs) != 0);
        const unsigned lit = lits[i] ^ sign;
        assert(marks[lit]);
        marks[lit] = 0;
        LOG("unmarked %s", LOGLIT(lit));
    }
#ifndef LOGGING
    (void)solver;
#endif
}

static unsigned copy_literals(kissat *solver, unsigned lit, const value *values, unsigned *lits, clause *c)
{
    const unsigned *end = c->lits + c->size;
    unsigned *q = lits;
#ifndef NDEBUG
    bool found_lit = false;
#endif
    for (const unsigned *p = c->lits; p != end; p++) {
        const unsigned other = *p;
        if (other == lit) {
#ifndef NDEBUG
            assert(!found_lit);
            assert(!values[other]);
            found_lit = true;
#endif
        } else {
            const value value = values[other];
            assert(value <= 0);
            if (value < 0) {
                LOG("skipping falsified %s", LOGLIT(other));
                continue;
            }
            LOG("copying %s", LOGLIT(other));
            *q++ = other;
        }
    }
    assert(found_lit);
    *q++ = lit;
    const unsigned size = q - lits;
    LOGLITS(size, lits, "copied", size);
#ifndef LOGGING
    (void)solver;
#endif
    return size;
}

static bool odd_parity(unsigned size, unsigned signs)
{
    bool res = false;
    for (unsigned i = 0; i < size; i++)
        if (signs & (1u << i))
            res = !res;
    return res;
}

static unsigned next_marking(kissat *solver, value *marks, unsigned size, unsigned *lits, unsigned prev)
{
    LOG("prev signs %s", FORMAT_SIGNS(size, prev));
    assert(2 < size);
    assert(size < 32);
    const unsigned limit = (1u << size);

    assert(prev < limit);

    unsigned next;

    for (next = prev + 1; odd_parity(size, next); next++)
        ;

    LOG("next signs %s", FORMAT_SIGNS(size, next));

    for (unsigned i = 0, bit = 1; i < size; i++, bit <<= 1) {
        const unsigned lit = lits[i];
        const unsigned not_lit = NOT(lit);
        if (!(prev & bit) && (next & bit)) {
            assert(marks[lit]);
            assert(!marks[not_lit]);
            marks[lit] = 0;
            LOG("unmarked %s", LOGLIT(lit));
            marks[not_lit] = 1;
            LOG("marked %s", LOGLIT(not_lit));
        } else if ((prev & bit) && !(next & bit)) {
            assert(marks[not_lit]);
            assert(!marks[lit]);
            marks[not_lit] = 0;
            LOG("unmarked %s", LOGLIT(not_lit));
            marks[lit] = 1;
            LOG("marked %s", LOGLIT(lit));
        }
    }
#ifndef LOGGING
    (void)solver;
#endif
    return next == limit ? 0 : next;
}

static bool match_lits_ref(kissat *solver, const value *marks, const value *values, unsigned size, reference ref)
{
    clause *c = kissat_dereference_clause(solver, ref);
    unsigned found = 0;
    for (all_literals_in_clause(lit, c)) {
        const value value = values[lit];
        if (value > 0) {
            kissat_eliminate_clause(solver, c, INVALID_LIT);
            return false;
        }
        if (value < 0)
            continue;
        if (!marks[lit])
            return false;
        found++;
    }
    assert(found <= size);
    if (found < size)
        solver->resolve_gate = true;
    return true;
}

static bool match_lits_watch(kissat *solver, const value *marks, const value *values, unsigned size, watch watch)
{
    if (watch.type.binary) {
        const unsigned other = watch.binary.lit;
        if (!marks[other])
            return false;
        assert(size > 2);
        solver->resolve_gate = true;
        return true;
    } else {
        const reference ref = watch.large.ref;
        return match_lits_ref(solver, marks, values, size, ref);
    }
}

static watch *find_lits_watch(kissat *solver, watch *begin, watch *end, const value *marks, const value *values,
    unsigned size, uint64_t *steps)
{
    assert(begin <= end);
    for (watch *p = begin; p != end; p++) {
        *steps += 1;
        if (match_lits_watch(solver, marks, values, size, *p))
            return p;
    }
    return 0;
}

#define LESS_POINTER(P, Q) ((P) < (Q))

static void sort_watch_pointers(kissat *solver, patches *patches)
{
    SORT_STACK(watch *, *patches, LESS_POINTER);
}

bool kissat_find_xor_gate(kissat *solver, unsigned lit, unsigned negative)
{
    if (!GET_OPTION(xors))
        return false;

    const unsigned size_limit = solver->bounds.xor.clause_size;
    if (size_limit < 3)
        return false;
    assert(size_limit < 32);

    watches *watches0 = &WATCHES(lit);
    watch *begin0 = BEGIN_WATCHES(*watches0);
    watch *end0 = END_WATCHES(*watches0);
    if (begin0 == end0)
        return false;

    uint64_t large_clauses0 = 0;
    for (watch *p = begin0; p != end0; p++)
        if (!p->type.binary && large_clauses0++)
            break;
    if (large_clauses0 < 2)
        return false;

    const unsigned not_lit = NOT(lit);
    watches *watches1 = &WATCHES(not_lit);
    watch *begin1 = BEGIN_WATCHES(*watches1);
    watch *end1 = END_WATCHES(*watches1);

    uint64_t large_clauses1 = 0;
    for (watch *p = begin1; p != end1; p++)
        if (!p->type.binary && large_clauses1++)
            break;
    if (large_clauses1 < 2)
        return false;

    unsigned lits[size_limit];

    const value *values = solver->values;
    value *marks = solver->marks;

    const unsigned steps_limit = solver->bounds.eliminate.occurrences;

    uint64_t steps = 0;

    for (watch *p = begin0; p != end0; p++) {
        if (p->type.binary)
            continue;

        if (steps > steps_limit)
            break;

        if (TERMINATED(26))
            break;

        steps++;
        clause *c = kissat_dereference_clause(solver, p->large.ref);
        if (c->size > size_limit)
            continue;

        unsigned size = copy_literals(solver, lit, values, lits, c);

        assert(size <= 32);
        if (size < 3)
            continue;

        solver->resolve_gate = false;

        assert(EMPTY_STACK(solver->xorted[0]));
        assert(EMPTY_STACK(solver->xorted[1]));
        PUSH_STACK(solver->xorted[0], p);

        unsigned signs = 0;
        mark_literals(solver, marks, size, lits, signs);

        while (steps <= steps_limit && (signs = next_marking(solver, marks, size, lits, signs))) {
            if (marks[lit]) {
                watch *q = find_lits_watch(solver, begin0, end0, marks, values, size, &steps);
                if (!q) {
                    LOGLITS(size, lits, "could not match signs %s of copied", FORMAT_SIGNS(size, signs));
                    break;
                }

                LOGWATCH(lit, *q, "literal %s XOR", LOGLIT(lit));
                PUSH_STACK(solver->xorted[0], q);
            } else {
                watch *q = find_lits_watch(solver, begin1, end1, marks, values, size, &steps);
                if (!q)
                    break;

                LOGWATCH(not_lit, *q, "found %s literal XOR", LOGLIT(not_lit));
                PUSH_STACK(solver->xorted[1], q);
            }
        }

        unmark_literals(solver, marks, size, lits, signs);

        unsigned nsort[2] = {
            SIZE_STACK (solver->xorted[0]), SIZE_STACK (solver->xorted[1])
        };

        if (nsort[0] + nsort[1] == (1u << (size - 1))) {
            assert(nsort[0] == (1u << (size - 2)));
            assert(nsort[1] == (1u << (size - 2)));

            sort_watch_pointers(solver, &solver->xorted[0]);

            const watch *prev = 0;
            for (unsigned i = 0; i < nsort[0]; i++) {
                const watch *p0 = PEEK_STACK(solver->xorted[0], i);
                const watch w0 = *p0;
                if (p0 == prev)
                    LOGWATCH(lit, w0, "dropping repeated");
                else {
                    LOGWATCH(lit, w0, "%s %s XOR", FORMAT_ORDINAL(i + 1), LOGLIT(lit));
                    PUSH_STACK(solver->gates[negative], w0);
                }
                prev = p0;
            }

            sort_watch_pointers(solver, &solver->xorted[1]);
            prev = 0;
            for (unsigned i = 0; i < nsort[1]; i++) {
                const watch *p1 = PEEK_STACK(solver->xorted[1], i);
                const watch w1 = *p1;
                if (p1 == prev)
                    LOGWATCH(not_lit, w1, "dropping repeated");
                else {
                    LOGWATCH(not_lit, w1, "%s %s XOR", FORMAT_ORDINAL(i + 1), LOGLIT(not_lit));
                    PUSH_STACK(solver->gates[!negative], w1);
                }
                prev = p1;
            }

            assert(!EMPTY_STACK(solver->gates[0]));
#ifdef LOGGING
            assert(size > 1);
            assert(lits[size - 1] == lit);
            lits[0] = NOT(lits[0]);
            LOGXOR(lit, size - 1, lits, "found");
            lits[0] = NOT(lits[0]);
#endif
        }

        CLEAR_STACK(solver->xorted[0]);
        CLEAR_STACK(solver->xorted[1]);

        if (EMPTY_STACK(solver->gates[0]))
            continue;

        solver->gate_eliminated = GATE_ELIMINATED(xors);

        return true;
    }

    assert(EMPTY_STACK(solver->xorted[0]));
    assert(EMPTY_STACK(solver->xorted[1]));

    return false;
}
