package solver

import (
	"fmt"
	"sort"
)

type watcher struct {
	other  Lit // Another lit from the clause
	clause *Clause
}

// A watcherList is a structure used to store clauses and propagate unit literals efficiently.
type watcherList struct {
	nbMax     int         // Max # of learned clauses at current moment
	idxReduce int         // # of calls to reduce + 1
	wlistBin  [][]watcher // For each literal, a list of binary clauses where its negation appears
	wlist     [][]watcher // For each literal, a list of non-binary clauses where its negation appears atposition 1 or 2
	wlistPb   [][]*Clause // For each literal a list of PB or cardinality constraints.
	pbClauses []*Clause   // All the problem clauses.
	learned   []*Clause
}

// initWatcherList makes a new watcherList for the solver.
func (s *Solver) initWatcherList(clauses []*Clause) {
	nbMax := initNbMaxClauses
	newClauses := make([]*Clause, len(clauses))
	copy(newClauses, clauses)
	s.wl = watcherList{
		nbMax:     nbMax,
		idxReduce: 1,
		wlistBin:  make([][]watcher, s.nbVars*2),
		wlist:     make([][]watcher, s.nbVars*2),
		wlistPb:   make([][]*Clause, s.nbVars*2),
		pbClauses: newClauses,
	}
	for _, c := range clauses {
		s.watchClause(c)
	}
}

// Should be called when new vars are added to the problem (see Solver.newVar)
func (s *Solver) addVarWatcherList(v Var) {
	cnfVar := int(v.Int())
	for i := s.nbVars; i < cnfVar; i++ {
		s.wl.wlistBin = append(s.wl.wlistBin, nil, nil)
		s.wl.wlist = append(s.wl.wlist, nil, nil)
		s.wl.wlistPb = append(s.wl.wlistPb, nil, nil)
	}
}

// appendClause appends the clause without checking whether the clause is already satisfiable, unit, or unsatisfiable.
// To perform those checks, call s.AppendClause.
// clause is supposed to be a problem clause, not a learned one.
func (s *Solver) appendClause(clause *Clause) {
	s.wl.pbClauses = append(s.wl.pbClauses, clause)
	s.watchClause(clause)
}

// bumpNbMax increases the max nb of clauses used.
// It is typically called after a restart.
func (s *Solver) bumpNbMax() {
	s.wl.nbMax += incrNbMaxClauses
}

// postponeNbMax increases the max nb of clauses used.
// It is typically called when too many good clauses were learned and a cleaning was expected.
func (s *Solver) postponeNbMax() {
	s.wl.nbMax += incrPostponeNbMax
}

// Utilities for sorting according to clauses' LBD and activities.
func (wl *watcherList) Len() int      { return len(wl.learned) }
func (wl *watcherList) Swap(i, j int) { wl.learned[i], wl.learned[j] = wl.learned[j], wl.learned[i] }

func (wl *watcherList) Less(i, j int) bool {
	ci := wl.learned[i]
	cj := wl.learned[j]
	lbdI := ci.lbd()
	lbdJ := cj.lbd()
	// Sort by lbd, break ties by activity
	return lbdI > lbdJ || (lbdI == lbdJ && wl.learned[i].activity < wl.learned[j].activity)
}

// Watches the provided clause.
func (s *Solver) watchClause(c *Clause) {
	if c.Len() == 2 {
		first := c.First()
		second := c.Second()
		neg0 := first.Negation()
		neg1 := second.Negation()
		s.wl.wlistBin[neg0] = append(s.wl.wlistBin[neg0], watcher{clause: c, other: second})
		s.wl.wlistBin[neg1] = append(s.wl.wlistBin[neg1], watcher{clause: c, other: first})
	} else if c.PseudoBoolean() {
		w := 0
		i := 0
		for i < 2 || w <= c.Cardinality() {
			lit := c.Get(i)
			neg := lit.Negation()
			s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
			c.pbData.watched[i] = true
			w += c.Weight(i)
			i++
		}
	} else if c.Cardinality() > 1 {
		for i := 0; i < c.Cardinality()+1; i++ {
			lit := c.Get(i)
			neg := lit.Negation()
			s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
		}
	} else { // Regular, propositional clause
		first := c.First()
		second := c.Second()
		neg0 := first.Negation()
		neg1 := second.Negation()
		s.wl.wlist[neg0] = append(s.wl.wlist[neg0], watcher{clause: c, other: second})
		s.wl.wlist[neg1] = append(s.wl.wlist[neg1], watcher{clause: c, other: first})
	}
}

// unwatch the given learned clause.
// NOTE: since it is only called when c.lbd() > 2, we know for sure
// that c is not a binary clause.
// We also know for sure this is a propositional clause, since only those are learned.
func (s *Solver) unwatchClause(c *Clause) {
	for i := 0; i < 2; i++ {
		neg := c.Get(i).Negation()
		j := 0
		length := len(s.wl.wlist[neg])
		// We're looking for the index of the clause.
		// This will panic if c is not in wlist[neg], but this shouldn't happen.
		for s.wl.wlist[neg][j].clause != c {
			j++
		}
		s.wl.wlist[neg][j] = s.wl.wlist[neg][length-1]
		s.wl.wlist[neg] = s.wl.wlist[neg][:length-1]
	}
}

// reduceLearned removes a few learned clauses that are deemed useless.
func (s *Solver) reduceLearned() {
	sort.Sort(&s.wl)
	nbLearned := len(s.wl.learned)
	length := nbLearned / 2
	if s.wl.learned[length].lbd() <= 3 { // Lots of good clauses, postpone reduction
		s.postponeNbMax()
	}
	nbRemoved := 0
	for i := 0; i < length; i++ {
		c := s.wl.learned[i]
		if c.lbd() <= 2 || c.isLocked() {
			continue
		}
		nbRemoved++
		s.Stats.NbDeleted++
		s.wl.learned[i] = s.wl.learned[nbLearned-nbRemoved]
		s.unwatchClause(c)
	}
	nbLearned -= nbRemoved
	s.wl.learned = s.wl.learned[:nbLearned]
}

// Adds the given learned clause and updates watchers.
// If too many clauses have been learned yet, one will be removed.
func (s *Solver) addLearned(c *Clause) {
	s.wl.learned = append(s.wl.learned, c)
	s.watchClause(c)
	s.clauseBumpActivity(c)
	if s.Certified {
		if s.CertChan == nil {
			fmt.Printf("%s\n", c.CNF())
		} else {
			s.CertChan <- c.CNF()
		}
	}
}

// Adds the given unit literal to the model at the top level.
func (s *Solver) addLearnedUnit(unit Lit) {

	s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
	if s.Certified {
		if s.CertChan == nil {
			fmt.Printf("%d 0\n", unit.Int())
		} else {
			s.CertChan <- fmt.Sprintf("%d 0", unit.Int())
		}
	}
}

// If l is negative, -lvl is returned. Else, lvl is returned.
func lvlToSignedLvl(l Lit, lvl decLevel) decLevel {
	if l.IsPositive() {
		return lvl
	}
	return -lvl
}

// Removes the first occurrence of c from lst.
// The element *must* be present into lst.
func removeFrom(lst []*Clause, c *Clause) []*Clause {
	i := 0
	for lst[i] != c {
		i++
	}
	last := len(lst) - 1
	lst[i] = lst[last]
	return lst[:last]
}

// Propagates literals in the trail starting from the ptrth, and returns a conflict clause, or nil if none arose.
func (s *Solver) propagate(ptr int, lvl decLevel) *Clause {
	for ptr < len(s.trail) {
		lit := s.trail[ptr]
		for _, w := range s.wl.wlistBin[lit] {
			v2 := w.other.Var()
			if assign := s.model[v2]; assign == 0 { // Other was unbounded: propagate
				s.reason[v2] = w.clause
				w.clause.lock()
				s.model[v2] = lvlToSignedLvl(w.other, lvl)
				s.trail = append(s.trail, w.other)
			} else if (assign > 0) != w.other.IsPositive() { // Conflict here
				return w.clause
			}
		}
		if confl := s.simplifyPropClauses(lit, lvl); confl != nil {
			return confl
		}
		for _, c := range s.wl.wlistPb[lit] {
			if c.PseudoBoolean() {
				if !s.simplifyPseudoBool(c, lvl) {
					return c
				}
			} else {
				if !s.simplifyCardClause(c, lvl) {
					return c
				}
			}
		}
		ptr++
	}
	// No unsat clause was met
	return nil
}

// Unifies the given literal and returns a conflict clause, or nil if no conflict arose.
func (s *Solver) unifyLiteral(lit Lit, lvl decLevel) *Clause {
	s.model[lit.Var()] = lvlToSignedLvl(lit, lvl)
	s.trail = append(s.trail, lit)
	return s.propagate(len(s.trail)-1, lvl)
}

func (s *Solver) propagateUnit(c *Clause, lvl decLevel, unit Lit) {
	v := unit.Var()
	s.reason[v] = c
	c.lock()
	s.model[v] = lvlToSignedLvl(unit, lvl)
	s.trail = append(s.trail, unit)
}

func (s *Solver) simplifyPropClauses(lit Lit, lvl decLevel) *Clause {
	wl := s.wl.wlist[lit]
	j := 0
	for i, w := range wl {
		if s.litStatus(w.other) == Sat { // blocking literal is SAT? Don't explore clause!
			wl[j] = w
			j++
			continue
		}
		c := w.clause
		// make sure c.Second() is lit
		if c.First() == lit.Negation() {
			c.swap(0, 1)
		}
		w2 := watcher{clause: c, other: c.First()}
		firstStatus := s.litStatus(c.First())
		if firstStatus == Sat { // Clause is already sat
			wl[j] = w2
			j++
		} else {
			found := false
			for k := 2; k < c.Len(); k++ {
				if litK := c.Get(k); s.litStatus(litK) != Unsat {
					c.swap(1, k)
					neg := litK.Negation()
					s.wl.wlist[neg] = append(s.wl.wlist[neg], w2)
					found = true
					break
				}
			}
			if !found { // No free lit found: unit propagation or UNSAT
				wl[j] = w2
				j++
				if firstStatus == Unsat {
					copy(wl[j:], wl[i+1:]) // Copy remaining clauses
					s.wl.wlist[lit] = wl[:len(wl)-((i+1)-j)]
					return c
				}
				s.propagateUnit(c, lvl, c.First())
			}
		}
	}
	s.wl.wlist[lit] = wl[:j]
	return nil
}

// simplifyCardClauses simplifies a clause of cardinality > 1, but with all weights = 1.
// returns false iff the clause cannot be satisfied.
func (s *Solver) simplifyCardClause(clause *Clause, lvl decLevel) bool {
	length := clause.Len()
	card := clause.Cardinality()
	nbTrue := 0
	nbFalse := 0
	nbUnb := 0
	done := false
	for i := 0; i < length && !done; i++ {
		lit := clause.Get(i)
		switch s.litStatus(lit) {
		case Indet:
			nbUnb++
			if nbUnb+nbTrue > card {
				done = true
			}
		case Sat:
			nbTrue++
			if nbTrue == card {
				return true
			}
			if nbUnb+nbTrue > card {
				done = true
			}
		case Unsat:
			nbFalse++
			if length-nbFalse < card {
				return false
			}
		}
	}
	if nbTrue >= card {
		return true
	}
	if nbUnb+nbTrue == card {
		// All unbounded lits must be bound to make the clause true
		i := 0
		for nbUnb > 0 {
			lit := clause.Get(i)
			if s.model[lit.Var()] == 0 {
				s.propagateUnit(clause, lvl, lit)
				nbUnb--
			} else {
				i++
			}
		}
		return true
	}
	s.swapFalse(clause)
	return true
}

// swapFalse swaps enough literals from the clause so that all watching literals are either true or unbounded lits.
// Must only be called when there a at least cardinality + 1 true and unbounded lits.
func (s *Solver) swapFalse(clause *Clause) {
	card := clause.Cardinality()
	i := 0
	j := card + 1
	for i < card+1 {
		lit := clause.Get(i)
		for s.litStatus(lit) != Unsat {
			i++
			if i == card+1 {
				return
			}
			lit = clause.Get(i)
		}
		lit = clause.Get(j)
		for s.litStatus(lit) == Unsat {
			j++
			lit = clause.Get(j)
		}
		ni := &s.wl.wlistPb[clause.Get(i).Negation()]
		nj := &s.wl.wlistPb[clause.Get(j).Negation()]
		clause.swap(i, j)
		*ni = removeFrom(*ni, clause)
		*nj = append(*nj, clause)
		i++
		j++
	}
}

// sumWeights returns the sum of weights of the given PB clause, if the clause is not SAT yet.
// If the clause is already SAT, it returns true and the given sum value
// is meaningless.
func (s *Solver) sumWeights(c *Clause) (sum int, sat bool) {
	sum = 0
	sumSat := 0
	card := c.Cardinality()
	for i, v := range c.pbData.weights {
		if status := s.litStatus(c.Get(i)); status == Indet {
			sum += v
		} else if status == Sat {
			sum += v
			sumSat += v
			if sumSat >= card {
				return sum, true
			}
		}
	}
	return sum, false
}

// propagateAll propagates all unbounded literals from c as unit literals
func (s *Solver) propagateAll(c *Clause, lvl decLevel) {
	for i := 0; i < c.Len(); i++ {
		if lit := c.Get(i); s.litStatus(lit) == Indet {
			s.propagateUnit(c, lvl, lit)
		}
	}
}

// simplifyPseudoBool simplifies a pseudo-boolean constraint.
// propagates unit literals, if any.
// returns false if the clause cannot be satisfied.
func (s *Solver) simplifyPseudoBool(clause *Clause, lvl decLevel) bool {
	card := clause.Cardinality()
	foundUnit := true
	for foundUnit {
		sumW, sat := s.sumWeights(clause)
		if sat {
			return true
		}
		if sumW < card {
			return false
		}
		if sumW == card {
			s.propagateAll(clause, lvl)
			return true
		}
		foundUnit = false
		for i := 0; i < clause.Len(); i++ {
			lit := clause.Get(i)
			if s.litStatus(lit) == Indet && sumW-clause.Weight(i) < card { // lit can't be falsified
				s.propagateUnit(clause, lvl, lit)
				foundUnit = true
			}
		}
	}
	s.updateWatchPB(clause)
	return true
}

func (s *Solver) updateWatchPB(clause *Clause) {
	weightWatched := 0
	i := 0
	card := clause.Cardinality()
	for weightWatched <= card && i < clause.Len() {
		lit := clause.Get(i)
		if s.litStatus(lit) == Unsat {
			if clause.pbData.watched[i] {
				ni := &s.wl.wlistPb[lit.Negation()]
				*ni = removeFrom(*ni, clause)
				clause.pbData.watched[i] = false
			}
		} else {
			weightWatched += clause.Weight(i)
			if !clause.pbData.watched[i] {
				ni := &s.wl.wlistPb[lit.Negation()]
				*ni = append(*ni, clause)
				clause.pbData.watched[i] = true
			}
		}
		i++
	}
	// If there are some more watched literals, they are now useless
	for i := i; i < clause.Len(); i++ {
		if clause.pbData.watched[i] {
			ni := &s.wl.wlistPb[clause.Get(i).Negation()]
			*ni = removeFrom(*ni, clause)
			clause.pbData.watched[i] = false
		}
	}
}
