/******************************************
Copyright (C) 2009-2020 Authors of CryptoMiniSat, see AUTHORS file

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
***********************************************/

#include "constants.h"
#include "cms_breakid.h"
#include "solver.h"
#include "clausecleaner.h"
#include "breakid/breakid.hpp"
#include "varupdatehelper.h"
#include "varreplacer.h"
#include "occsimplifier.h"
#include "subsumeimplicit.h"
#include "sqlstats.h"
#include "completedetachreattacher.h"

using namespace CMSat;

BreakID::BreakID(Solver* _solver):
    solver(_solver)
{
}

void BreakID::updateVars(
    const vector<uint32_t>& outerToInter
    , const vector<uint32_t>& /*interToOuter*/)
{
    if (symm_var != var_Undef) {
        symm_var = getUpdatedVar(symm_var, outerToInter);
    }
}

template<class T>
BreakID::add_cl_ret BreakID::add_this_clause(const T& cl)
{
    uint32_t sz = 0;
    bool sat = false;
    brkid_lits.clear();
    for(size_t i3 = 0; i3 < cl.size(); i3++) {
        Lit lit = cl[i3];
        assert(solver->varData[lit.var()].removed == Removed::none);
        lbool val = l_Undef;
        if (solver->value(lit) != l_Undef) {
            val = solver->value(lit);
        } else {
            val = solver->lit_inside_assumptions(lit);
        }

        if (val == l_True) {
            //clause is SAT, skip!
            sat = true;
            continue;
        } else if (val == l_False) {
            continue;
        }
        brkid_lits.push_back(lit);
        sz++;
    }
    if (sat) {
        return add_cl_ret::skipped_cl;
    }
    if (sz == 0) {
        //it's unsat because of assumptions
        if (solver->conf.verbosity) {
            cout << "c [breakid] UNSAT because of assumptions in clause: " << cl << endl;
        }
        return add_cl_ret::unsat;
    }

    num_lits_in_graph += brkid_lits.size();
    breakid->add_clause((BID::BLit*)brkid_lits.data(), brkid_lits.size());
    brkid_lits.clear();

    return add_cl_ret::added_cl;
}

struct EqCls {
    EqCls(ClauseAllocator& _alloc) :
        cl_alloc(_alloc)
    {}

    bool operator()(ClOffset off1, ClOffset off2) {
        Clause* cl1 = cl_alloc.ptr(off1);
        Clause* cl2 = cl_alloc.ptr(off2);

        if (cl1->stats.hash_val != cl2->stats.hash_val) {
            return cl1->stats.hash_val < cl2->stats.hash_val;
        }

        if (cl1->size() != cl2->size()) {
            return cl1->size() < cl2->size();
        }

        //same hash, same size
        for(uint32_t i = 0; i < cl1->size(); i++) {
            if (cl1->getData()[i] != cl2->getData()[i]) {
                return (cl1->getData()[i] < cl2->getData()[i]);
            }
        }

        //they are equivalent
        return false;
    }

    ClauseAllocator& cl_alloc;
};

static bool equiv(Clause* cl1, Clause* cl2) {
    if (cl1->stats.hash_val != cl2->stats.hash_val) {
        return false;
    }

    if (cl1->size() != cl2->size()) {
        return false;
    }

    for(uint32_t i = 0; i < cl1->size(); i++) {
        if (cl1->getData()[i] != cl2->getData()[i]) {
            return false;
        }
    }

    return true;
}

void BreakID::set_up_time_lim()
{
    set_time_lim = solver->conf.breakid_time_limit_K;
    if (solver->nVars() < 5000) {
        set_time_lim*=2;
    }
    if (num_lits_in_graph < 100000) {
        set_time_lim*=2;
    }

    set_time_lim *= 1000LL;
    if (solver->conf.verbosity) {
        cout << "c [breakid] set time lim: " << set_time_lim << endl;
    }

    breakid->set_steps_lim(set_time_lim);
}

bool BreakID::add_clauses()
{
    //Add binary clauses
    vector<Lit> this_clause;
    for(size_t i2 = 0; i2 < solver->nVars()*2; i2++) {
        Lit lit = Lit::toLit(i2);
        for(const Watched& w: solver->watches[lit]) {
            if (w.isBin() && !w.red() && lit < w.lit2()) {
                this_clause.clear();
                this_clause.push_back(lit);
                this_clause.push_back(w.lit2());

                if (add_this_clause(this_clause) == add_cl_ret::unsat) {
                    return false;
                }
            }
        }
    }

    //Add long clauses
    for(ClOffset offs: dedup_cls) {
        const Clause* cl = solver->cl_alloc.ptr(offs);
        assert(!cl->freed());
        assert(!cl->getRemoved());

        if (add_this_clause(*cl) == add_cl_ret::unsat) {
            return false;
        }
    }

    return true;
}

bool BreakID::doit()
{
    assert(solver->okay());
    assert(solver->decisionLevel() == 0);
    assert(!solver->frat->enabled());
    num_lits_in_graph = 0;

    if (!solver->conf.doStrSubImplicit) {
        verb_print(1, "[breakid] cannot run BreakID without implicit submsumption, it would find too many (bad) symmetries");
        return solver->okay();
    }

    if (solver->check_assumptions_contradict_foced_assignment()) {
        verb_print(1, "[breakid] forced assignments contradicted by assumptions, cannot run");
        return solver->okay();
    }

    if (!check_limits()) return solver->okay();
    if (!solver->clauseCleaner->remove_and_clean_all()) return solver->okay();

    // Clean up solver state so it's easier to find symmetries
    solver->subsumeImplicit->subsume_implicit(false, "-breakid");
    CompleteDetachReatacher reattacher(solver);
    reattacher.detach_nonbins();
    remove_duplicates();
    double myTime = cpuTime();
    assert(breakid == NULL);
    breakid = new BID::BreakID;
    breakid->set_verbosity(0);
    breakid->set_useMatrixDetection(solver->conf.breakid_matrix_detect);
    breakid->set_symBreakingFormLength(solver->conf.breakid_max_constr_per_permut);
    breakid->start_dynamic_cnf(solver->nVars());
    verb_print(1, "[breakid] version " << breakid->get_sha1_version());

    // We can fail adding clauses if they are UNSAT under the current assumptions
    if (!add_clauses()) {
        delete breakid;
        breakid = NULL;

        bool ok = reattacher.reattachLongs();
        assert(ok);
        return solver->okay();
    }

    // Detect symmetries, detect subgroups
    set_up_time_lim();
    breakid->end_dynamic_cnf();
    verb_print(1, "[breakid] Generators: " << breakid->get_num_generators());
    if (solver->conf.verbosity > 3) breakid->print_generators(std::cout);
    verb_print(2, "[breakid] Detecting subgroups...");
    breakid->detect_subgroups();
    if (solver->conf.verbosity > 3) breakid->print_subgroups(cout);

    // Break symmetries, in BreakID
    breakid->break_symm();

    //reattach clauses
    bool ok = reattacher.reattachLongs();
    assert(ok);

    // Break symmetries in CMS, given clauses given by BreakID
    if (breakid->get_num_break_cls() != 0) break_symms_in_cms();

    // Not needed actually, only for debug
    //get_outer_permutations();

    // Finish up
    double time_used = cpuTime() - myTime;
    int64_t remain = breakid->get_steps_remain();
    bool time_out = remain <= 0;
    double time_remain = float_div(remain, set_time_lim);
    if (solver->conf.verbosity) {
        cout << "c [breakid] finished "
        << solver->conf.print_times(time_used, time_out, time_remain)
        << endl;
    }
    if (solver->sqlStats) {
        solver->sqlStats->time_passed(
            solver
            , "breakid"
            , time_used
            , time_out
            , time_remain
        );
    }

    delete breakid;
    breakid = NULL;

    return solver->okay();
}

void BreakID::get_outer_permutations()
{
    vector<unordered_map<BID::BLit, BID::BLit>> perms_inter;
    breakid->get_perms(&perms_inter);
    for(const auto& p: perms_inter) {
        unordered_map<Lit, Lit> outer;
        for(const auto& mymap: p) {
            Lit from = Lit::toLit(mymap.first.toInt());
            Lit to = Lit::toLit(mymap.second.toInt());

            from = solver->map_inter_to_outer(from);
            to = solver->map_inter_to_outer(to);

            outer[from] = to;
        }
        perms_outer.push_back(outer);
    }
}

bool BreakID::check_limits()
{
    uint64_t tot_num_cls = solver->longIrredCls.size()+solver->binTri.irredBins;
    uint64_t tot_num_lits = solver->litStats.irredLits + solver->binTri.irredBins*2;
    if (solver->nVars() > solver->conf.breakid_vars_limit_K*1000ULL) {
        if (solver->conf.verbosity) {
            cout
            << "c [breakid] max var limit exceeded, not running."
            << " Num vars: " << print_value_kilo_mega(solver->nVars(), false)
            << endl;
        }
        return false;
    }

    if (tot_num_cls > solver->conf.breakid_cls_limit_K*1000ULL) {
        if (solver->conf.verbosity) {
            cout
            << "c [breakid] max clause limit exceeded, not running."
            << " Num clauses: " << print_value_kilo_mega(tot_num_cls, false)
            << endl;
        }
        return false;
    }
    if (tot_num_lits > solver->conf.breakid_lits_limit_K*1000ULL) {
        if (solver->conf.verbosity) {
            cout
            << "c [breakid] max literals limit exceeded, not running."
            << " Num lits: " << print_value_kilo_mega(tot_num_lits, false)
            << endl;
        }
        return false;
    }

    return true;
}

void BreakID::remove_duplicates()
{
    double myTime = cpuTime();
    dedup_cls.clear();

    for(ClOffset offs: solver->longIrredCls) {
        Clause* cl = solver->cl_alloc.ptr(offs);
        assert(!cl->freed());
        assert(!cl->getRemoved());
        assert(!cl->red());
        std::sort(cl->begin(), cl->end());
        cl->stats.hash_val = hash_clause(cl->getData(), cl->size());
        dedup_cls.push_back(offs);
    }

    std::sort(dedup_cls.begin(), dedup_cls.end(), EqCls(solver->cl_alloc));

    size_t old_size = dedup_cls.size();
    if (dedup_cls.size() > 1 && true) {
        vector<ClOffset>::iterator prev = dedup_cls.begin();
        vector<ClOffset>::iterator i = dedup_cls.begin();
        ++i;
        Clause* prevcl = solver->cl_alloc.ptr(*prev);
        for(vector<ClOffset>::iterator end = dedup_cls.end(); i != end; ++i) {
            Clause* cl = solver->cl_alloc.ptr(*i);
            if (!equiv(cl, prevcl)) {
                ++prev;
                *prev = *i;
                prevcl = cl;
            }
        }
        ++prev;
        dedup_cls.resize(prev-dedup_cls.begin());
    }

    double time_used = cpuTime() - myTime;
    if (solver->conf.verbosity >= 1) {
        cout << "c [breakid] tmp-rem-dup cls"
        << " dupl: " << print_value_kilo_mega(old_size-dedup_cls.size(), false)
        << solver->conf.print_times(time_used)
        <<  endl;
    }
    if (solver->sqlStats) {
        solver->sqlStats->time_passed_min(
            solver
            , "breakid-rem-dup"
            , time_used
        );
    }
}

void BreakID::break_symms_in_cms()
{
    if (solver->conf.verbosity) {
        cout << "c [breakid] Breaking cls: "<< breakid->get_num_break_cls() << endl;
        cout << "c [breakid] Aux vars: "<< breakid->get_num_aux_vars() << endl;
    }
    for(uint32_t i = 0; i < breakid->get_num_aux_vars(); i++) solver->new_var(true);
    if (solver->conf.breakid_use_assump) {
        if (symm_var == var_Undef) {
            solver->new_var(true);
            symm_var = solver->nVars()-1;
            solver->add_assumption(Lit(symm_var, true));
        }
        assert(solver->varData[symm_var].removed == Removed::none);
    }

    auto brk = breakid->get_brk_cls();
    for (const auto& cl: brk) {
        vector<Lit>* cl2 = (vector<Lit>*)&cl;
        if (solver->conf.breakid_use_assump) {
            cl2->push_back(Lit(symm_var, false));
        }
        for(const Lit& l: *cl2) {
            assert(l.var() < solver->nVars());
            if (solver->conf.breakid_use_assump) {
                assert(solver->value(l) == l_Undef);
            }
        }
        Clause* newcl = solver->add_clause_int(*cl2
            , false //redundant
            , NULL //stats
            , true //attach
            , NULL //return simplified
            , true
            , Lit(symm_var, false)
        );
        if (newcl != NULL) {
            ClOffset offset = solver->cl_alloc.get_offset(newcl);
            solver->longIrredCls.push_back(offset);
        }
    }
}

void BreakID::finished_solving()
{
    //Nothing actually
}

void BreakID::start_new_solving()
{
    assert(solver->decisionLevel() == 0);
    assert(solver->okay());
    if (symm_var == var_Undef) {
        return;
    }

    assert(solver->varData[symm_var].removed == Removed::none);
    assert(solver->value(symm_var) != l_False
        && "The symm var can never be foreced to FALSE, logic error");

    //In certain conditions, in particular when the problem is UNSAT
    //the symmetry assumption var can be forced to TRUE at level 0
    if (solver->value(symm_var) == l_True) {
        symm_var = var_Undef;
        return;
    }

    assert(solver->value(symm_var) == l_Undef);
    solver->enqueue<false>(Lit(symm_var, false));
    PropBy ret = solver->propagate<true>();
    assert(ret == PropBy() && "Must not fail on resetting symmetry var");
    symm_var = var_Undef;
}


void BreakID::update_var_after_varreplace()
{
    if (symm_var != var_Undef) {
        symm_var = solver->varReplacer->get_var_replaced_with(symm_var);
    }
}

Lit BreakID::get_assumed_lit() const
{
    if (symm_var == var_Undef) {
        return lit_Undef;
    } else {
        return Lit(symm_var, true);
    }
}
