package solver

import (
	"fmt"
	"sort"
	"strings"
	"time"
)

const (
	initNbMaxClauses  = 2000  // Maximum # of learned clauses, at first.
	incrNbMaxClauses  = 300   // By how much # of learned clauses is incremented at each conflict.
	incrPostponeNbMax = 1000  // By how much # of learned is increased when lots of good clauses are currently learned.
	clauseDecay       = 0.999 // By how much clauses bumping decays over time.
	defaultVarDecay   = 0.8   // On each var decay, how much the varInc should be decayed at startup
)

// Stats are statistics about the resolution of the problem.
// They are provided for information purpose only.
type Stats struct {
	NbRestarts      int
	NbConflicts     int
	NbDecisions     int
	NbUnitLearned   int // How many unit clauses were learned
	NbBinaryLearned int // How many binary clauses were learned
	NbLearned       int // How many clauses were learned
	NbDeleted       int // How many clauses were deleted
}

// The level a decision was made.
// A negative value means "negative assignement at that level".
// A positive value means "positive assignment at that level".
type decLevel int

// A Model is a binding for several variables.
// It can be totally bound (i.e all vars have a true or false binding)
// or only partially (i.e some vars have no binding yet or their binding has no impact).
// Each var, in order, is associated with a binding. Binding are implemented as
// decision levels:
// - a 0 value means the variable is free,
// - a positive value means the variable was set to true at the given decLevel,
// - a negative value means the variable was set to false at the given decLevel.
type Model []decLevel

func (m Model) String() string {
	bound := make(map[int]decLevel)
	for i := range m {
		if m[i] != 0 {
			bound[i+1] = m[i]
		}
	}
	return fmt.Sprintf("%v", bound)
}

// A Solver solves a given problem. It is the main data structure.
type Solver struct {
	Verbose     bool        // Indicates whether the solver should display information during solving or not. False by default
	Certified   bool        // Indicates whether a certificate should be generated during solving or not, using the RUP notation. This is useful to prove UNSAT instances. False by default.
	CertChan    chan string // Indicates where to write the certificate. If Certified is true but CertChan is nil, the certificate will be written on stdout.
	nbVars      int
	status      Status
	wl          watcherList
	trail       []Lit     // Current assignment stack
	model       Model     // 0 means unbound, other value is a binding
	lastModel   Model     // Placeholder for last model found, useful when looking for several models
	activity    []float64 // How often each var is involved in conflicts
	polarity    []bool    // Preferred sign for each var
	assumptions []bool    // True iff the var's binding is assumed
	// For each var, clause considered when it was unified
	// If the var is not bound yet, or if it was bound by a decision, value is nil.
	reason          []*Clause
	varQueue        queue
	varInc          float64 // On each var bump, how big the increment should be
	clauseInc       float32 // On each var bump, how big the increment should be
	lbdStats        lbdStats
	Stats           Stats   // Statistics about the solving process.
	minLits         []Lit   // Lits to minimize if the problem was an optimization problem.
	minWeights      []int   // Weight of each lit to minimize if the problem was an optimization problem.
	hypothesis      []Lit   // Literals that are, ideally, true. Useful when trying to minimize a function.
	localNbRestarts int     // How many restarts since Solve() was called?
	varDecay        float64 // On each var decay, how much the varInc should be decayed
	trailBuf        []int   // A buffer while cleaning bindings
}

// New makes a solver, given a number of variables and a set of clauses.
// nbVars should be consistent with the content of clauses, i.e.
// the biggest variable in clauses should be >= nbVars.
func New(problem *Problem) *Solver {
	if problem.Status == Unsat {
		return &Solver{status: Unsat}
	}
	nbVars := problem.NbVars

	trailCap := nbVars
	if len(problem.Units) > trailCap {
		trailCap = len(problem.Units)
	}

	s := &Solver{
		nbVars:      nbVars,
		status:      problem.Status,
		trail:       make([]Lit, len(problem.Units), trailCap),
		model:       problem.Model,
		activity:    make([]float64, nbVars),
		polarity:    make([]bool, nbVars),
		assumptions: make([]bool, nbVars),
		reason:      make([]*Clause, nbVars),
		varInc:      1.0,
		clauseInc:   1.0,
		minLits:     problem.minLits,
		minWeights:  problem.minWeights,
		varDecay:    defaultVarDecay,
		trailBuf:    make([]int, nbVars),
	}
	s.resetOptimPolarity()
	s.initOptimActivity()
	s.initWatcherList(problem.Clauses)
	s.varQueue = newQueue(s.activity)
	for i, lit := range problem.Units {
		if lit.IsPositive() {
			s.model[lit.Var()] = 1
		} else {
			s.model[lit.Var()] = -1
		}
		s.trail[i] = lit
	}
	return s
}

// newVar is used to indicate a new variable must be added to the solver.
// This can be used when new clauses are appended and these clauses contain vars that were unseen so far.
// If the var already existed, nothing will happen.
func (s *Solver) newVar(v Var) {
	if cnfVar := int(v.Int()); cnfVar > s.nbVars {
		// If the var already existed, do nothing
		for i := s.nbVars; i < cnfVar; i++ {
			s.model = append(s.model, 0)
			s.activity = append(s.activity, 0.)
			s.polarity = append(s.polarity, false)
			s.reason = append(s.reason, nil)
			s.trailBuf = append(s.trailBuf, 0)
		}
		s.varQueue = newQueue(s.activity)
		s.addVarWatcherList(v)
		s.nbVars = cnfVar
	}
}

// sets initial activity for optimization variables, if any.
func (s *Solver) initOptimActivity() {
	for i, lit := range s.minLits {
		w := 1
		if s.minWeights != nil {
			w = s.minWeights[i]
		}
		s.activity[lit.Var()] += float64(w)
	}
}

// resets polarity of optimization lits so that they are negated by default.
func (s *Solver) resetOptimPolarity() {
	if s.minLits != nil {
		for _, lit := range s.minLits {
			s.polarity[lit.Var()] = !lit.IsPositive() // Try to make lits from the optimization clause false
		}
	}
}

// Optim returns true iff the underlying problem is an optimization problem (rather than a satisfaction one).
func (s *Solver) Optim() bool {
	return s.minLits != nil
}

// OutputModel outputs the model for the problem on stdout.
func (s *Solver) OutputModel() {
	if s.status == Sat || s.lastModel != nil {
		fmt.Printf("s SATISFIABLE\nv ")
		model := s.model
		if s.lastModel != nil {
			model = s.lastModel
		}
		for i, val := range model {
			if val < 0 {
				fmt.Printf("%d ", -i-1)
			} else {
				fmt.Printf("%d ", i+1)
			}
		}
		fmt.Printf("\n")
	} else if s.status == Unsat {
		fmt.Printf("s UNSATISFIABLE\n")
	} else {
		fmt.Printf("s INDETERMINATE\n")
	}
}

// litStatus returns whether the literal is made true (Sat) or false (Unsat) by the
// current bindings, or if it is unbounded (Indet).
func (s *Solver) litStatus(l Lit) Status {
	assign := s.model[l.Var()]
	if assign == 0 {
		return Indet
	}
	if assign > 0 == l.IsPositive() {
		return Sat
	}
	return Unsat
}

func (s *Solver) varDecayActivity() {
	s.varInc *= 1 / s.varDecay
}

func (s *Solver) varBumpActivity(v Var) {
	s.activity[v] += s.varInc
	if s.activity[v] > 1e100 { // Rescaling is needed to avoid overflowing
		for i := range s.activity {
			s.activity[i] *= 1e-100
		}
		s.varInc *= 1e-100
	}
	if s.varQueue.contains(int(v)) {
		s.varQueue.decrease(int(v))
	}
}

// Decays each clause's activity
func (s *Solver) clauseDecayActivity() {
	s.clauseInc *= 1 / clauseDecay
}

// Bumps the given clause's activity.
func (s *Solver) clauseBumpActivity(c *Clause) {
	if c.Learned() {
		c.activity += s.clauseInc
		if c.activity > 1e30 { // Rescale to avoid overflow
			for _, c2 := range s.wl.learned {
				c2.activity *= 1e-30
			}
			s.clauseInc *= 1e-30
		}
	}
}

// Chooses an unbound literal to be tested, or -1
// if all the variables are already bound.
func (s *Solver) chooseLit() Lit {
	v := Var(-1)
	for v == -1 && !s.varQueue.empty() {
		if v2 := Var(s.varQueue.removeMin()); s.model[v2] == 0 { // Ignore already bound vars
			v = v2
		}
	}
	if v == -1 {
		return Lit(-1)
	}
	s.Stats.NbDecisions++
	return v.SignedLit(!s.polarity[v])
}

func abs(val decLevel) decLevel {
	if val < 0 {
		return -val
	}
	return val
}

// Reinitializes bindings (both model & reason) for all variables bound at a decLevel >= lvl.
// TODO: check this method as it has a weird behavior regarding performance.
// TODO: clean-up commented-out code and understand underlying performance pattern.
func (s *Solver) cleanupBindings(lvl decLevel) {
	i := 0
	for i < len(s.trail) && abs(s.model[s.trail[i].Var()]) <= lvl {
		i++
	}
	/*
		for j := len(s.trail) - 1; j >= i; j-- {
				lit2 := s.trail[j]
				v := lit2.Var()
				s.model[v] = 0
				if s.reason[v] != nil {
					s.reason[v].unlock()
					s.reason[v] = nil
				}
				s.polarity[v] = lit2.IsPositive()
				if !s.varQueue.contains(int(v)) {
					s.varQueue.insert(int(v))
				}
			}
			s.trail = s.trail[:i]
	*/
	toInsert := s.trailBuf[:0] // make([]int, 0, len(s.trail)-i)
	for j := i; j < len(s.trail); j++ {
		lit2 := s.trail[j]
		v := lit2.Var()
		s.model[v] = 0
		if s.reason[v] != nil {
			s.reason[v].unlock()
			s.reason[v] = nil
		}
		s.polarity[v] = lit2.IsPositive()
		if !s.varQueue.contains(int(v)) {
			toInsert = append(toInsert, int(v))
			s.varQueue.insert(int(v))
		}
	}
	s.trail = s.trail[:i]
	for i := len(toInsert) - 1; i >= 0; i-- {
		s.varQueue.insert(toInsert[i])
	}
	/*for i := len(s.trail) - 1; i >= 0; i-- {
		lit := s.trail[i]
		v := lit.Var()
		if abs(s.model[v]) <= lvl { // All lits in trail before here must keep their status.
			s.trail = s.trail[:i+1]
			break
		}
		s.model[v] = 0
		if s.reason[v] != nil {
			s.reason[v].unlock()
			s.reason[v] = nil
		}
		s.polarity[v] = lit.IsPositive()
		if !s.varQueue.contains(int(v)) {
			s.varQueue.insert(int(v))
		}
	}*/
	s.resetOptimPolarity()
}

// Given the last learnt clause and the levels at which vars were bound,
// Returns the level to bt to and the literal to bind
func backtrackData(c *Clause, model []decLevel) (btLevel decLevel, lit Lit) {
	btLevel = abs(model[c.Get(1).Var()])
	return btLevel, c.Get(0)
}

func (s *Solver) rebuildOrderHeap() {
	ints := make([]int, s.nbVars)
	for v := 0; v < s.nbVars; v++ {
		if s.model[v] == 0 {
			ints = append(ints, int(v))
		}
	}
	s.varQueue.build(ints)
}

// propagate binds the given lit, propagates it and searches for a solution,
// until it is found or a restart is needed.
func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status {
	for lit != -1 {
		// log.Printf("picked %d at lvl %d", lit.Int(), lvl)
		if conflict := s.unifyLiteral(lit, lvl); conflict == nil { // Pick new branch or restart
			if s.lbdStats.mustRestart() {
				s.lbdStats.clear()
				s.cleanupBindings(1)
				return Indet
			}
			if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax {
				s.wl.idxReduce = s.Stats.NbConflicts/s.wl.nbMax + 1
				s.reduceLearned()
				s.bumpNbMax()
			}
			lvl++
			lit = s.chooseLit()
		} else { // Deal with conflict
			s.Stats.NbConflicts++
			if s.Stats.NbConflicts%5000 == 0 && s.varDecay < 0.95 {
				s.varDecay += 0.01
			}
			s.lbdStats.addConflict(len(s.trail))
			learnt, unit := s.learnClause(conflict, lvl)
			if learnt == nil { // Unit clause was learned: this lit is known for sure
				if unit == -1 || (abs(s.model[unit.Var()]) == 1 && s.litStatus(unit) == Unsat) { // Top-level conflict
					return s.setUnsat()
				}
				s.Stats.NbUnitLearned++
				s.lbdStats.addLbd(1)
				s.cleanupBindings(1)
				s.addLearnedUnit(unit)
				s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
				if conflict = s.unifyLiteral(unit, 1); conflict != nil { // top-level conflict
					return s.setUnsat()
				}
				s.rebuildOrderHeap()
				lit = s.chooseLit()
				lvl = 2
			} else {
				if learnt.Len() == 2 {
					s.Stats.NbBinaryLearned++
				}
				s.Stats.NbLearned++
				s.lbdStats.addLbd(learnt.lbd())
				s.addLearned(learnt)
				lvl, lit = backtrackData(learnt, s.model)
				s.cleanupBindings(lvl)
				s.reason[lit.Var()] = learnt
				learnt.lock()
			}
		}
	}
	return Sat
}

// Sets the status to unsat and do cleanup tasks.
func (s *Solver) setUnsat() Status {
	if s.Certified {
		if s.CertChan == nil {
			fmt.Printf("0\n")
		} else {
			s.CertChan <- "0"
		}
	}
	s.status = Unsat
	return Unsat
}

// Searches until a restart is needed.
func (s *Solver) search() Status {
	s.localNbRestarts++
	lvl := decLevel(2) // Level starts at 2, for implementation reasons : 1 is for top-level bindings; 0 means "no level assigned yet"
	s.status = s.propagateAndSearch(s.chooseLit(), lvl)
	return s.status
}

// Solve solves the problem associated with the solver and returns the appropriate status.
func (s *Solver) Solve() Status {
	if s.status == Unsat {
		return s.status
	}
	s.status = Indet
	//s.lbdStats.clear()
	s.localNbRestarts = 0
	var end chan struct{}
	if s.Verbose {
		end = make(chan struct{})
		defer close(end)
		go func() { // Function displaying stats during resolution
			fmt.Printf("c ======================================================================================\n")
			fmt.Printf("c | Restarts |  Conflicts  |  Learned  |  Deleted  | Del%% | Reduce |   Units learned   |\n")
			fmt.Printf("c ======================================================================================\n")
			ticker := time.NewTicker(3 * time.Second)
			defer ticker.Stop()
			for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
				select {
				case <-ticker.C:
				case <-end:
					return
				}
				if s.status == Indet {
					iter := s.Stats.NbRestarts + 1
					nbConfl := s.Stats.NbConflicts
					nbReduce := s.wl.idxReduce - 1
					nbLearned := len(s.wl.learned)
					nbDel := s.Stats.NbDeleted
					pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
					nbUnit := s.Stats.NbUnitLearned
					fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
				}
			}
		}()
	}
	for s.status == Indet {
		s.search()
		if s.status == Indet {
			s.Stats.NbRestarts++
			s.rebuildOrderHeap()
		}
	}
	if s.status == Sat {
		s.lastModel = make(Model, len(s.model))
		copy(s.lastModel, s.model)
	}
	if s.Verbose {
		end <- struct{}{}
		fmt.Printf("c ======================================================================================\n")
	}
	return s.status
}

// Assume adds unit literals to the solver.
// This is useful when calling the solver several times, e.g to keep it "hot" while removing clauses.
func (s *Solver) Assume(lits []Lit) Status {
	s.cleanupBindings(0)
	s.trail = s.trail[:0]
	s.assumptions = make([]bool, s.nbVars)

	for _, lit := range lits {

		s.addLearnedUnit(lit)
		s.assumptions[lit.Var()] = true
		s.trail = append(s.trail, lit)
	}
	s.status = Indet
	if confl := s.propagate(0, 1); confl != nil {
		// Conflict after unit propagation
		s.status = Unsat
		return s.status
	}
	return s.status
}

// Enumerate returns the total number of models for the given problems.
// if "models" is non-nil, it will write models on it as soon as it discovers them.
// models will be closed at the end of the method.
func (s *Solver) Enumerate(models chan []bool, stop chan struct{}) int {
	if models != nil {
		defer close(models)
	}
	s.lastModel = make(Model, len(s.model))
	nb := 0
	lit := s.chooseLit()
	var lvl decLevel
	for s.status != Unsat {
		for s.status == Indet {
			s.search()
			if s.status == Indet {
				s.Stats.NbRestarts++
			}
		}
		if s.status == Sat {
			copy(s.lastModel, s.model)
			if models != nil {
				nb += s.addCurrentModels(models)
			} else {
				nb += s.countCurrentModels()
			}
			s.status = Indet
			lits := s.decisionLits()
			switch len(lits) {
			case 0:
				s.status = Unsat
			case 1:
				s.propagateUnits(lits)
			default:
				c := NewClause(lits)
				s.appendClause(c)
				lit = lits[len(lits)-1]
				v := lit.Var()
				lvl = abs(s.model[v]) - 1
				s.cleanupBindings(lvl)
				s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
				s.propagateAndSearch(lit, lvl)
			}
		}
	}
	return nb
}

// CountModels returns the total number of models for the given problem.
func (s *Solver) CountModels() int {
	var end chan struct{}
	if s.Verbose {
		end = make(chan struct{})
		defer close(end)
		go func() { // Function displaying stats during resolution
			fmt.Printf("c ======================================================================================\n")
			fmt.Printf("c | Restarts |  Conflicts  |  Learned  |  Deleted  | Del%% | Reduce |   Units learned   |\n")
			fmt.Printf("c ======================================================================================\n")
			ticker := time.NewTicker(3 * time.Second)
			defer ticker.Stop()
			for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
				select {
				case <-ticker.C:
				case <-end:
					return
				}
				if s.status == Indet {
					iter := s.Stats.NbRestarts + 1
					nbConfl := s.Stats.NbConflicts
					nbReduce := s.wl.idxReduce - 1
					nbLearned := len(s.wl.learned)
					nbDel := s.Stats.NbDeleted
					pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
					nbUnit := s.Stats.NbUnitLearned
					fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
				}
			}
		}()
	}
	nb := 0
	lit := s.chooseLit()
	var lvl decLevel
	for s.status != Unsat {
		for s.status == Indet {
			s.search()
			if s.status == Indet {
				s.Stats.NbRestarts++
			}
		}
		if s.status == Sat {
			s.lastModel = s.model
			nb += s.countCurrentModels()
			if s.Verbose {
				fmt.Printf("c found %d model(s)\n", nb)
			}
			s.status = Indet
			lits := s.decisionLits()
			switch len(lits) {
			case 0:
				s.status = Unsat
			case 1:
				s.propagateUnits(lits)
			default:
				c := NewClause(lits)
				s.appendClause(c)
				lit = lits[len(lits)-1]
				v := lit.Var()
				lvl = abs(s.model[v]) - 1
				s.cleanupBindings(lvl)
				s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
				s.propagateAndSearch(lit, lvl)
			}
		}
	}
	if s.Verbose {
		end <- struct{}{}
		fmt.Printf("c ======================================================================================\n")
	}
	return nb
}

// decisionLits returns the negation of all decision values once a model was found, ordered by decision levels.
// This will allow for searching other models.
func (s *Solver) decisionLits() []Lit {
	lastLit := s.trail[len(s.trail)-1]
	lvls := abs(s.model[lastLit.Var()])
	lits := make([]Lit, lvls-1)
	for i, r := range s.reason {
		if lvl := abs(s.model[i]); r == nil && lvl > 1 {
			if s.model[i] < 0 {
				// lvl-2 : levels beside unit clauses start at 2, not 0 or 1!
				lits[lvl-2] = IntToLit(int32(i + 1))
			} else {
				lits[lvl-2] = IntToLit(int32(-i - 1))
			}
		}
	}
	return lits
}

func (s *Solver) propagateUnits(units []Lit) {
	for _, unit := range units {
		s.lbdStats.addLbd(1)
		s.Stats.NbUnitLearned++
		s.cleanupBindings(1)
		s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
		if s.unifyLiteral(unit, 1) != nil {
			s.status = Unsat
			return
		}
		s.rebuildOrderHeap()
	}
}

// PBString returns a representation of the solver's state as a pseudo-boolean problem.
func (s *Solver) PBString() string {
	meta := fmt.Sprintf("* #variable= %d #constraint= %d #learned= %d\n", s.nbVars, len(s.wl.pbClauses), len(s.wl.learned))
	minLine := ""
	if s.minLits != nil {
		terms := make([]string, len(s.minLits))
		for i, lit := range s.minLits {
			weight := 1
			if s.minWeights != nil {
				weight = s.minWeights[i]
			}
			val := lit.Int()
			sign := ""
			if val < 0 {
				val = -val
				sign = "~"
			}
			terms[i] = fmt.Sprintf("%d %sx%d", weight, sign, val)
		}
		minLine = fmt.Sprintf("min: %s ;\n", strings.Join(terms, " +"))
	}
	clauses := make([]string, len(s.wl.pbClauses)+len(s.wl.learned))
	for i, c := range s.wl.pbClauses {
		clauses[i] = c.PBString()
	}
	for i, c := range s.wl.learned {
		clauses[i+len(s.wl.pbClauses)] = c.PBString()
	}
	for i := 0; i < len(s.model); i++ {
		if s.model[i] == 1 {
			clauses = append(clauses, fmt.Sprintf("1 x%d = 1 ;", i+1))
		} else if s.model[i] == -1 {
			clauses = append(clauses, fmt.Sprintf("1 x%d = 0 ;", i+1))
		}
	}
	return meta + minLine + strings.Join(clauses, "\n")
}

// AppendClause appends a new clause to the set of clauses.
// This is not a learned clause, but a clause that is part of the problem added afterwards (during model counting, for instance).
func (s *Solver) AppendClause(clause *Clause) {
	s.cleanupBindings(1)
	card := clause.Cardinality()
	minW := 0
	maxW := 0
	i := 0
	for i < clause.Len() {
		lit := clause.Get(i)
		s.newVar(lit.Var())
		switch s.litStatus(lit) {
		case Sat:
			w := clause.Weight(i)
			minW += w
			maxW += w
			clause.removeLit(i)
			clause.updateCardinality(-w)
		case Unsat:
			clause.removeLit(i)
		default:
			maxW += clause.Weight(i)
			i++
		}
	}
	if minW >= card { // clause is already sat
		return
	}
	if maxW < card { // clause cannot be satisfied
		s.status = Unsat
		return
	}
	if maxW == card { // Unit
		s.propagateUnits(clause.lits)
	} else {
		s.appendClause(clause)
	}
}

// Model returns a slice that associates, to each variable, its binding.
// If s's status is not Sat, the method will panic.
func (s *Solver) Model() []bool {
	if s.lastModel == nil {
		panic("cannot call Model() from a non-Sat solver")
	}
	res := make([]bool, s.nbVars)
	for i, lvl := range s.lastModel {
		res[i] = lvl > 0
	}
	return res
}

// addCurrentModels is called when a model was found.
// It returns the total number of models from this point, and sends all models on ch.
// The number can be different of 1 if there are unbound variables.
// For instance, if there are 4 variables in the problem and only 1, 3 and 4 are bound,
// there are actually 2 models currently: one with 2 set to true, the other with 2 set to false.
func (s *Solver) addCurrentModels(ch chan []bool) int {
	unbound := make([]int, 0, s.nbVars) // indices of unbound variables
	var nb uint64 = 1                   // total number of models found
	model := make([]bool, s.nbVars)     // partial model
	for i, lvl := range s.lastModel {
		if lvl == 0 {
			unbound = append(unbound, i)
			nb *= 2
		} else {
			model[i] = lvl > 0
		}
	}
	for i := uint64(0); i < nb; i++ {
		for j := range unbound {
			mask := uint64(1 << j)
			cur := i & mask
			idx := unbound[j]
			model[idx] = cur != 0
		}
		model2 := make([]bool, len(model))
		copy(model2, model)
		ch <- model2
	}
	return int(nb)
}

// countCurrentModels is called when a model was found.
// It returns the total number of models from this point.
// The number can be different of 1 if there are unbound variables.
// For instance, if there are 4 variables in the problem and only 1, 3 and 4 are bound,
// there are actually 2 models currently: one with 2 set to true, the other with 2 set to false.
func (s *Solver) countCurrentModels() int {
	var nb uint64 = 1 // total number of models found
	for _, lvl := range s.lastModel {
		if lvl == 0 {
			nb *= 2
		}
	}
	return int(nb)
}

// Optimal returns the optimal solution, if any.
// If results is non-nil, all solutions will be written to it.
// In any case, results will be closed at the end of the call.
func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) {
	if results != nil {
		defer close(results)
	}
	status := s.Solve()
	if status == Unsat { // Problem cannot be satisfied at all
		res.Status = Unsat
		if results != nil {
			results <- res
		}
		return res
	}
	if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
		s.lastModel = make(Model, len(s.model))
		copy(s.lastModel, s.model)
		res := Result{
			Status: Sat,
			Model:  s.Model(),
			Weight: 0,
		}
		if results != nil {
			results <- res
		}
		return res
	}
	maxCost := 0
	if s.minWeights == nil {
		maxCost = len(s.minLits)
	} else {
		for _, w := range s.minWeights {
			maxCost += w
		}
	}
	s.hypothesis = make([]Lit, len(s.minLits))
	for i, lit := range s.minLits {
		s.hypothesis[i] = lit.Negation()
	}
	weights := make([]int, len(s.minWeights))
	copy(weights, s.minWeights)
	sort.Sort(wLits{lits: s.hypothesis, weights: weights})
	s.lastModel = make(Model, len(s.model))
	var cost int
	for status == Sat {
		copy(s.lastModel, s.model) // Save this model: it might be the last one
		cost = 0
		for i, lit := range s.minLits {
			if s.model[lit.Var()] > 0 == lit.IsPositive() {
				if s.minWeights == nil {
					cost++
				} else {
					cost += s.minWeights[i]
				}
			}
		}
		res = Result{
			Status: Sat,
			Model:  s.Model(),
			Weight: cost,
		}
		if results != nil {
			results <- res
		}
		if cost == 0 {
			break
		}
		// Add a constraint incrementing current best cost
		lits2 := make([]Lit, len(s.minLits))
		weights2 := make([]int, len(s.minWeights))
		copy(lits2, s.hypothesis)
		copy(weights2, weights)
		s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
		s.rebuildOrderHeap()
		status = s.Solve()
	}
	return res
}

// Minimize tries to find a model that minimizes the weight of the clause defined as the optimisation clause in the problem.
// If no model can be found, it will return a cost of -1.
// Otherwise, calling s.Model() afterwards will return the model that satisfy the formula, such that no other model with a smaller cost exists.
// If this function is called on a non-optimization problem, it will either return -1, or a cost of 0 associated with a
// satisfying model (ie any model is an optimal model).
func (s *Solver) Minimize() int {
	status := s.Solve()
	if status == Unsat { // Problem cannot be satisfied at all
		return -1
	}
	if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
		return 0
	}
	maxCost := 0
	if s.minWeights == nil {
		maxCost = len(s.minLits)
	} else {
		for _, w := range s.minWeights {
			maxCost += w
		}
	}
	s.hypothesis = make([]Lit, len(s.minLits))
	for i, lit := range s.minLits {
		s.hypothesis[i] = lit.Negation()
	}
	weights := make([]int, len(s.minWeights))
	copy(weights, s.minWeights)
	sort.Sort(wLits{lits: s.hypothesis, weights: weights})
	s.lastModel = make(Model, len(s.model))
	var cost int
	for status == Sat {
		copy(s.lastModel, s.model) // Save this model: it might be the last one
		cost = 0
		for i, lit := range s.minLits {
			if s.model[lit.Var()] > 0 == lit.IsPositive() {
				if s.minWeights == nil {
					cost++
				} else {
					cost += s.minWeights[i]
				}
			}
		}
		if cost == 0 {
			return 0
		}
		if s.Verbose {
			fmt.Printf("o %d\n", cost)
		}
		// Add a constraint incrementing current best cost
		lits2 := make([]Lit, len(s.minLits))
		weights2 := make([]int, len(s.minWeights))
		copy(lits2, s.hypothesis)
		copy(weights2, weights)
		s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
		s.rebuildOrderHeap()
		status = s.Solve()
	}
	return cost
}

// functions to sort hypothesis for pseudo-boolean minimization clause.
type wLits struct {
	lits    []Lit
	weights []int
}

func (wl wLits) Len() int           { return len(wl.lits) }
func (wl wLits) Less(i, j int) bool { return wl.weights[i] > wl.weights[j] }

func (wl wLits) Swap(i, j int) {
	wl.lits[i], wl.lits[j] = wl.lits[j], wl.lits[i]
	wl.weights[i], wl.weights[j] = wl.weights[j], wl.weights[i]
}
