package solver

import (
	"fmt"
)

// A Problem is a list of clauses & a nb of vars.
type Problem struct {
	NbVars     int        // Total nb of vars
	Clauses    []*Clause  // List of non-empty, non-unit clauses
	Status     Status     // Status of the problem. Can be trivially UNSAT (if empty clause was met or inferred by UP) or Indet.
	Units      []Lit      // List of unit literal found in the problem.
	Model      []decLevel // For each var, its inferred binding. 0 means unbound, 1 means bound to true, -1 means bound to false.
	minLits    []Lit      // For an optimisation problem, the list of lits whose sum must be minimized
	minWeights []int      // For an optimisation problem, the weight of each lit.
}

// Optim returns true iff pb is an optimisation problem, ie
// a problem for which we not only want to find a model, but also
// the best possible model according to an optimization constraint.
func (pb *Problem) Optim() bool {
	return pb.minLits != nil
}

// CNF returns a DIMACS CNF representation of the problem.
func (pb *Problem) CNF() string {
	res := fmt.Sprintf("p cnf %d %d\n", pb.NbVars, len(pb.Clauses)+len(pb.Units))
	for _, unit := range pb.Units {
		res += fmt.Sprintf("%d 0\n", unit.Int())
	}
	for _, clause := range pb.Clauses {
		res += fmt.Sprintf("%s\n", clause.CNF())
	}
	return res
}

// PBString returns a representation of the problem as a pseudo-boolean problem.
func (pb *Problem) PBString() string {
	res := pb.costFuncString()
	for _, unit := range pb.Units {
		sign := ""
		if !unit.IsPositive() {
			sign = "~"
			unit = unit.Negation()
		}
		res += fmt.Sprintf("1 %sx%d = 1 ;\n", sign, unit.Int())
	}
	for _, clause := range pb.Clauses {
		res += fmt.Sprintf("%s\n", clause.PBString())
	}
	return res
}

// SetCostFunc sets the function to minimize when optimizing the problem.
// If all weights are 1, weights can be nil.
// In all other cases, len(lits) must be the same as len(weights).
func (pb *Problem) SetCostFunc(lits []Lit, weights []int) {
	if weights != nil && len(lits) != len(weights) {
		panic("length of lits and of weights don't match")
	}
	pb.minLits = lits
	pb.minWeights = weights
}

// costFuncString returns a string representation of the cost function of the problem, if any, followed by a \n.
// If there is no cost function, the empty string will be returned.
func (pb *Problem) costFuncString() string {
	if pb.minLits == nil {
		return ""
	}
	res := "min: "
	for i, lit := range pb.minLits {
		w := 1
		if pb.minWeights != nil {
			w = pb.minWeights[i]
		}
		sign := ""
		if w >= 0 && i != 0 { // No plus sign for the first term or for negative terms.
			sign = "+"
		}
		val := lit.Int()
		neg := ""
		if val < 0 {
			val = -val
			neg = "~"
		}
		res += fmt.Sprintf("%s%d %sx%d", sign, w, neg, val)
	}
	res += " ;\n"
	return res
}

func (pb *Problem) updateStatus(nbClauses int) {
	pb.Clauses = pb.Clauses[:nbClauses]
	if pb.Status == Indet && nbClauses == 0 {
		pb.Status = Sat
	}
}

func (pb *Problem) simplifyClause(idx int, idxClauses [][]int, removed []bool) {
	c := pb.Clauses[idx]
	k := 0
	sat := false
	for j := 0; j < c.Len(); j++ {
		lit := c.Get(j)
		v := lit.Var()
		if pb.Model[v] == 0 {
			c.Set(k, c.Get(j))
			k++
			idxClauses[lit] = append(idxClauses[lit], idx)
		} else if (pb.Model[v] > 0) == lit.IsPositive() {
			sat = true
			break
		}
	}
	if sat {
		removed[idx] = true
		return
	}
	if k == 0 {
		pb.Status = Unsat
		return
	}
	if k == 1 {
		pb.addUnit(c.First())
		if pb.Status == Unsat {
			return
		}
		removed[idx] = true
	}
	c.Shrink(k)
}

// rmClauses removes clauses that are already satisfied after simplification.
func (pb *Problem) rmClauses(removed []bool) {
	j := 0
	for i, rm := range removed {
		if !rm {
			pb.Clauses[j] = pb.Clauses[i]
			j++
		}
	}
	pb.Clauses = pb.Clauses[:j]
}

// simplify simplifies the pure SAT problem, i.e runs unit propagation if possible.
func (pb *Problem) simplify2() {
	nbClauses := len(pb.Clauses)
	restart := true
	for restart {
		restart = false
		i := 0
		for i < nbClauses {
			c := pb.Clauses[i]
			nbLits := c.Len()
			clauseSat := false
			j := 0
			for j < nbLits {
				lit := c.Get(j)
				if pb.Model[lit.Var()] == 0 {
					j++
				} else if (pb.Model[lit.Var()] == 1) == lit.IsPositive() {
					clauseSat = true
					break
				} else {
					nbLits--
					c.Set(j, c.Get(nbLits))
				}
			}
			if clauseSat {
				nbClauses--
				pb.Clauses[i] = pb.Clauses[nbClauses]
			} else if nbLits == 0 {
				pb.Status = Unsat
				return
			} else if nbLits == 1 { // UP
				pb.addUnit(c.First())
				if pb.Status == Unsat {
					return
				}
				nbClauses--
				pb.Clauses[i] = pb.Clauses[nbClauses]
				restart = true // Must restart, since this lit might have made one more clause Unit or SAT.
			} else { // nb lits unbound > cardinality
				if c.Len() != nbLits {
					c.Shrink(nbLits)
				}
				i++
			}
		}
	}
	pb.updateStatus(nbClauses)
}

// simplifyCard simplifies the problem, i.e runs unit propagation if possible.
func (pb *Problem) simplifyCard() {
	nbClauses := len(pb.Clauses)
	restart := true
	for restart {
		restart = false
		i := 0
		for i < nbClauses {
			c := pb.Clauses[i]
			nbLits := c.Len()
			card := c.Cardinality()
			clauseSat := false
			nbSat := 0
			j := 0
			for j < nbLits {
				lit := c.Get(j)
				if pb.Model[lit.Var()] == 0 {
					j++
				} else if (pb.Model[lit.Var()] == 1) == lit.IsPositive() {
					nbSat++
					if nbSat == card {
						clauseSat = true
						break
					}
				} else {
					nbLits--
					c.Set(j, c.Get(nbLits))
				}
			}
			if clauseSat {
				nbClauses--
				pb.Clauses[i] = pb.Clauses[nbClauses]
			} else if nbLits < card {
				pb.Status = Unsat
				return
			} else if nbLits == card { // UP
				pb.addUnits(c, nbLits)
				if pb.Status == Unsat {
					return
				}
				nbClauses--
				pb.Clauses[i] = pb.Clauses[nbClauses]
				restart = true // Must restart, since this lit might have made one more clause Unit or SAT.
			} else { // nb lits unbound > cardinality
				if c.Len() != nbLits {
					c.Shrink(nbLits)
				}
				i++
			}
		}
	}
	pb.updateStatus(nbClauses)
}

func (pb *Problem) simplifyPB() {
	modified := true
	for modified {
		modified = false
		i := 0
		for i < len(pb.Clauses) {
			c := pb.Clauses[i]
			j := 0
			card := c.Cardinality()
			wSum := c.WeightSum()
			for j < c.Len() {
				lit := c.Get(j)
				v := lit.Var()
				w := c.Weight(j)
				if pb.Model[v] == 0 { // Literal not assigned: is it unit?
					if wSum-w < card { // Lit must be true for the clause to be satisfiable
						pb.addUnit(lit)
						if pb.Status == Unsat {
							return
						}
						c.removeLit(j)
						card -= w
						c.updateCardinality(-w)
						wSum -= w
						modified = true
					} else {
						j++
					}
				} else { // Bound literal: remove it and update, if needed, cardinality
					wSum -= w
					if (pb.Model[v] == 1) == lit.IsPositive() {
						card -= w
						c.updateCardinality(-w)
					}
					c.removeLit(j)
					modified = true
				}
			}
			if card <= 0 { // Clause is Sat
				pb.Clauses[i] = pb.Clauses[len(pb.Clauses)-1]
				pb.Clauses = pb.Clauses[:len(pb.Clauses)-1]
				modified = true
			} else if wSum < card {
				pb.Clauses = nil
				pb.Status = Unsat
				return
			} else {
				i++
			}
		}
	}
	if pb.Status == Indet && len(pb.Clauses) == 0 {
		pb.Status = Sat
	}
}

func (pb *Problem) addUnit(lit Lit) {
	if lit.IsPositive() {
		if pb.Model[lit.Var()] == -1 {
			pb.Status = Unsat
			return
		}
		pb.Model[lit.Var()] = 1
	} else {
		if pb.Model[lit.Var()] == 1 {
			pb.Status = Unsat
			return
		}
		pb.Model[lit.Var()] = -1
	}
	pb.Units = append(pb.Units, lit)
}

func (pb *Problem) addUnits(c *Clause, nbLits int) {
	for i := 0; i < nbLits; i++ {
		lit := c.Get(i)
		pb.addUnit(lit)
	}
}
