package solver

// computeLbd computes and sets c's LBD (Literal Block Distance).
func (c *Clause) computeLbd(model Model) {
	c.setLbd(1)
	curLvl := abs(model[c.Get(0).Var()])
	for i := 0; i < c.Len(); i++ {
		lit := c.Get(i)
		if lvl := abs(model[lit.Var()]); lvl != curLvl {
			curLvl = lvl
			c.incLbd()
		}
	}
}

// addClauseLits is a helper function for learnClause.
// It deals with lits from the conflict clause.
func (s *Solver) addClauseLits(confl *Clause, lvl decLevel, met, metLvl []bool, lits *[]Lit) int {
	nbLvl := 0
	for i := 0; i < confl.Len(); i++ {
		l := confl.Get(i)
		v := l.Var()
		if s.litStatus(l) != Unsat {
			// In clauses where cardinality > 1, some lits might be true in the conflict clause: ignore them
			continue
		}
		met[v] = true
		s.varBumpActivity(v)
		if abs(s.model[v]) == lvl {
			metLvl[v] = true
			nbLvl++
		} else {
			*lits = append(*lits, l)
		}
	}
	return nbLvl
}

var bufLits = make([]Lit, 10000) // Buffer for lits in learnClause. Used to reduce allocations.

// learnClause creates a conflict clause and returns either:
// - the clause itself, if its len is at least 2,
// - a nil clause and a unit literal, if its len is exactly 1,
// - a nil clause and -1, if the empty clause was learned.
func (s *Solver) learnClause(confl *Clause, lvl decLevel) (learned *Clause, unit Lit) {
	s.clauseBumpActivity(confl)
	lits := bufLits[:1]             // Not 0: make room for asserting literal
	buf := make([]bool, s.nbVars*2) // Buffer for met and metLvl; reduces allocs/deallocs
	met := buf[:s.nbVars]           // List of all vars already met
	metLvl := buf[s.nbVars:]        // List of all vars from current level to deal with
	// nbLvl is the nb of vars in lvl currently used
	nbLvl := s.addClauseLits(confl, lvl, met, metLvl, &lits)
	ptr := len(s.trail) - 1 // Pointer in propagation trail
	for nbLvl > 1 {         // We will stop once we only have one lit from current level.
		for !metLvl[s.trail[ptr].Var()] {
			if abs(s.model[s.trail[ptr].Var()]) == lvl { // This var was deduced afterwards and was not a reason for the conflict
				met[s.trail[ptr].Var()] = true
			}
			ptr--
		}
		v := s.trail[ptr].Var()
		if s.assumptions[v] {
			// Now we only have assumed lits: this is a top-level conflict
			return nil, -1
		}
		ptr--
		nbLvl--
		if reason := s.reason[v]; reason != nil {
			s.clauseBumpActivity(reason)
			for i := 0; i < reason.Len(); i++ {
				lit := reason.Get(i)
				if v2 := lit.Var(); !met[v2] {
					if s.litStatus(lit) != Unsat { // In clauses where cardinality > 1, some lits might be true in the conflict clause: ignore them
						continue
					}
					met[v2] = true
					s.varBumpActivity(v2)
					if abs(s.model[v2]) == lvl {
						metLvl[v2] = true
						nbLvl++
					} else {
						lits = append(lits, lit)
					}
				}
			}
		}
	}
	for _, l := range s.trail { // Look for last lit from lvl and use it as asserting lit
		if metLvl[l.Var()] {
			lits[0] = l.Negation()
			break
		}
	}
	s.varDecayActivity()
	s.clauseDecayActivity()
	sortLiterals(lits, s.model)
	sz := s.minimizeLearned(met, lits)
	if sz == 1 {
		return nil, lits[0]
	}
	learned = NewLearnedClause(alloc.newLits(lits[0:sz]...))
	learned.computeLbd(s.model)
	return learned, -1
}

// minimizeLearned reduces (if possible) the length of the learned clause and returns the size
// of the new list of lits.
func (s *Solver) minimizeLearned(met []bool, learned []Lit) int {
	sz := 1
	for i := 1; i < len(learned); i++ {
		if reason := s.reason[learned[i].Var()]; reason == nil {
			learned[sz] = learned[i]
			sz++
		} else {
			for k := 0; k < reason.Len(); k++ {
				lit := reason.Get(k)
				if !met[lit.Var()] /*&& abs(s.model[lit.Var()]) > 1*/ {
					learned[sz] = learned[i]
					sz++
					break
				}
			}
		}
	}
	return sz
}
