package explain

import (
	"fmt"

	"github.com/crillab/gophersat/solver"
)

// MUSMaxSat returns a Minimal Unsatisfiable Subset for the problem using the MaxSat strategy.
// A MUS is an unsatisfiable subset such that, if any of its clause is removed,
// the problem becomes satisfiable.
// A MUS can be useful to understand why a problem is UNSAT, but MUSes are expensive to compute since
// a SAT solver must be called several times on parts of the original problem to find them.
// With the MaxSat strategy, the function computes the MUS through several calls to MaxSat.
func (pb *Problem) MUSMaxSat() (mus *Problem, err error) {
	pb2 := pb.clone()
	nbVars := pb2.NbVars
	NbClauses := pb2.NbClauses
	weights := make([]int, NbClauses)          // Weights of each clause
	relaxLits := make([]solver.Lit, NbClauses) // Set of all relax lits
	relaxLit := nbVars + 1                     // Index of last used relax lit
	for i, clause := range pb2.Clauses {
		pb2.Clauses[i] = append(clause, relaxLit)
		relaxLits[i] = solver.IntToLit(int32(relaxLit))
		weights[i] = 1
		relaxLit++
	}
	prob := solver.ParseSlice(pb2.Clauses)
	prob.SetCostFunc(relaxLits, weights)
	s := solver.New(prob)
	s.Verbose = pb.Options.Verbose
	var musClauses [][]int
	done := make([]bool, NbClauses) // Indicates whether a clause is already part of MUS or not yet
	for {
		cost := s.Minimize()
		if cost == -1 {
			return makeMus(nbVars, musClauses), nil
		}
		if cost == 0 {
			return nil, fmt.Errorf("cannot extract MUS from satisfiable problem")
		}
		model := s.Model()
		for i, clause := range pb.Clauses {
			if !done[i] && !satClause(clause, model) {
				// The clause is part of the MUS
				pb2.Clauses = append(pb2.Clauses, []int{-(nbVars + i + 1)}) // Now, relax lit has to be false
				pb2.NbClauses++
				musClauses = append(musClauses, clause)
				done[i] = true
				// Make it a hard clause before restarting solver
				lits := make([]solver.Lit, len(clause))
				for j, lit := range clause {
					lits[j] = solver.IntToLit(int32(lit))
				}
				s.AppendClause(solver.NewClause(lits))
			}
		}
		if pb.Options.Verbose {
			fmt.Printf("c Currently %d/%d clauses in MUS\n", len(musClauses), NbClauses)
		}
		prob = solver.ParseSlice(pb2.Clauses)
		prob.SetCostFunc(relaxLits, weights)
		s = solver.New(prob)
		s.Verbose = pb.Options.Verbose
	}
}

// true iff the clause is satisfied by the model
func satClause(clause []int, model []bool) bool {
	for _, lit := range clause {
		if (lit > 0 && model[lit-1]) || (lit < 0 && !model[-lit-1]) {
			return true
		}
	}
	return false
}

func makeMus(nbVars int, clauses [][]int) *Problem {
	mus := &Problem{
		Clauses:   clauses,
		NbVars:    nbVars,
		NbClauses: len(clauses),
		units:     make([]int, nbVars),
	}
	for _, clause := range clauses {
		if len(clause) == 1 {
			lit := clause[0]
			if lit > 0 {
				mus.units[lit-1] = 1
			} else {
				mus.units[-lit-1] = -1
			}
		}
	}
	return mus
}

// MUSInsertion returns a Minimal Unsatisfiable Subset for the problem using the insertion method.
// A MUS is an unsatisfiable subset such that, if any of its clause is removed,
// the problem becomes satisfiable.
// A MUS can be useful to understand why a problem is UNSAT, but MUSes are expensive to compute since
// a SAT solver must be called several times on parts of the original problem to find them.
// The insertion algorithm is efficient is many cases, as it calls the same solver several times in a row.
// However, in some cases, the number of calls will be higher than using other methods.
// For instance, if  called on a formula that is already a MUS, it will perform n*(n-1) calls to SAT, where
// n is the number of clauses of the problem.
func (pb *Problem) MUSInsertion() (mus *Problem, err error) {
	pb2, err := pb.UnsatSubset()
	if err != nil {
		return nil, fmt.Errorf("could not extract MUS: %v", err)
	}
	mus = &Problem{NbVars: pb2.NbVars}
	clauses := pb2.Clauses
	for {
		if pb.Options.Verbose {
			fmt.Printf("c mus currently contains %d clauses\n", mus.NbClauses)
		}
		s := solver.New(solver.ParseSliceNb(mus.Clauses, mus.NbVars))
		s.Verbose = pb.Options.Verbose
		st := s.Solve()
		if st == solver.Unsat { // Found the MUS
			return mus, nil
		}
		// Add clauses until the problem becomes UNSAT
		idx := 0
		for st == solver.Sat {
			clause := clauses[idx]
			lits := make([]solver.Lit, len(clause))
			for i, lit := range clause {
				lits[i] = solver.IntToLit(int32(lit))
			}
			cl := solver.NewClause(lits)
			s.AppendClause(cl)
			idx++
			st = s.Solve()
		}
		idx--                                           // We went one step too far, go back
		mus.Clauses = append(mus.Clauses, clauses[idx]) // Last clause is part of the MUS
		mus.NbClauses++
		if pb.Options.Verbose {
			fmt.Printf("c removing %d/%d clause(s)\n", len(clauses)-idx, len(clauses))
		}
		clauses = clauses[:idx] // Remaining clauses are not part of the MUS
	}
}

// MUSDeletion returns a Minimal Unsatisfiable Subset for the problem using the insertion method.
// A MUS is an unsatisfiable subset such that, if any of its clause is removed,
// the problem becomes satisfiable.
// A MUS can be useful to understand why a problem is UNSAT, but MUSes are expensive to compute since
// a SAT solver must be called several times on parts of the original problem to find them.
// The deletion algorithm is guaranteed to call exactly n SAT solvers, where n is the number of clauses in the problem.
// It can be quite efficient, but each time the solver is called, it is starting from scratch.
// Other methods keep the solver "hot", so despite requiring more calls, these methods can be more efficient in practice.
func (pb *Problem) MUSDeletion() (mus *Problem, err error) {
	pb2, err := pb.UnsatSubset()
	if err != nil {
		if err == ErrNotUnsat {
			return nil, err
		}
		return nil, fmt.Errorf("could not extract MUS: %v", err)
	}
	pb2.NbVars += pb2.NbClauses          // Add one relax var for each clause
	for i, clause := range pb2.Clauses { // Add relax lit to each clause
		newClause := make([]int, len(clause)+1)
		copy(newClause, clause)
		newClause[len(clause)] = pb.NbVars + i + 1 // Add relax lit to the clause
		pb2.Clauses[i] = newClause
	}
	s := solver.New(solver.ParseSlice(pb2.Clauses))
	asumptions := make([]solver.Lit, pb2.NbClauses)
	for i := 0; i < pb2.NbClauses; i++ {
		asumptions[i] = solver.IntToLit(int32(-(pb.NbVars + i + 1))) // At first, all asumptions are false
	}
	for i := range pb2.Clauses {
		// Relax current clause
		asumptions[i] = asumptions[i].Negation()
		s.Assume(asumptions)
		if s.Solve() == solver.Sat {
			// It is now sat; reinsert the clause, i.e re-falsify the relax lit
			asumptions[i] = asumptions[i].Negation()
			if pb.Options.Verbose {
				fmt.Printf("c clause %d/%d: kept\n", i+1, pb2.NbClauses)
			}
		} else if pb.Options.Verbose {
			fmt.Printf("c clause %d/%d: removed\n", i+1, pb2.NbClauses)
		}
	}
	mus = &Problem{
		NbVars: pb.NbVars,
	}
	for i, val := range asumptions {
		if !val.IsPositive() {
			// Lit is not relaxed, meaning the clause is part of the MUS
			clause := pb2.Clauses[i]
			clause = clause[:len(clause)-1] // Remove relax lit
			mus.Clauses = append(mus.Clauses, clause)
		}
		mus.NbClauses = len(mus.Clauses)
	}
	return mus, nil
}

// MUS returns a Minimal Unsatisfiable Subset for the problem.
// A MUS is an unsatisfiable subset such that, if any of its clause is removed,
// the problem becomes satisfiable.
// A MUS can be useful to understand why a problem is UNSAT, but MUSes are expensive to compute since
// a SAT solver must be called several times on parts of the original problem to find them.
// The exact algorithm used to compute the MUS is not guaranteed. If you want to use a given algorithm,
// use the relevant functions.
func (pb *Problem) MUS() (mus *Problem, err error) {
	return pb.MUSDeletion()
}
