package aima.core.search.csp;

import java.util.ArrayList;
import java.util.List;

import aima.core.util.Util;

/**
 * Artificial Intelligence A Modern Approach (3rd Ed.): Figure 6.8, Page 221.<br>
 * <br>
 * 
 * <pre>
 * <code>
 * function MIN-CONFLICTS(csp, max-steps) returns a solution or failure
 *    inputs: csp, a constraint satisfaction problem
 *            max-steps, the number of steps allowed before giving up
 *    current = an initial complete assignment for csp
 *    for i = 1 to max steps do
 *       if current is a solution for csp then return current
 *       var = a randomly chosen conflicted variable from csp.VARIABLES
 *       value = the value v for var that minimizes CONFLICTS(var, v, current, csp)
 *       set var = value in current
 *    return failure
 * </code>
 * </pre>
 * 
 * Figure 6.8 The MIN-CONFLICTS algorithm for solving CSPs by local search. The
 * initial state may be chosen randomly or by a greedy assignment process that
 * chooses a minimal-conflict value for each variable in turn. The CONFLICTS
 * function counts the number of constraints violated by a particular value,
 * given the rest of the current assignment.
 * 
 * @author Ruediger Lunde
 * @author Mike Stampone
 */
public class MinConflictsStrategy extends SolutionStrategy {
	private int maxSteps;

	/**
	 * Constructs a min-conflicts strategy with a given number of steps allowed
	 * before giving up.
	 * 
	 * @param maxSteps
	 *            the number of steps allowed before giving up
	 */
	public MinConflictsStrategy(int maxSteps) {
		this.maxSteps = maxSteps;
	}

	public Assignment solve(CSP csp) {
		Assignment assignment = generateRandomAssignment(csp);
		fireStateChanged(assignment, csp);
		for (int i = 0; i < maxSteps; i++) {
			if (assignment.isSolution(csp)) {
				return assignment;
			} else {
				List<Variable> vars = getConflictedVariables(assignment, csp);
				Variable var = Util.selectRandomlyFromList(vars);
				Object value = getMinConflictValueFor(var, assignment, csp);
				assignment.setAssignment(var, value);
				fireStateChanged(assignment, csp);
			}
		}
		return null;
	}

	private Assignment generateRandomAssignment(CSP csp) {
		Assignment assignment = new Assignment();
		for (Variable var : csp.getVariables()) {
			Object randomValue = Util.selectRandomlyFromList(csp.getDomain(var)
					.asList());
			assignment.setAssignment(var, randomValue);
		}
		return assignment;
	}

	private List<Variable> getConflictedVariables(Assignment assignment, CSP csp) {
		List<Variable> result = new ArrayList<Variable>();
		for (Constraint constraint : csp.getConstraints()) {
			if (!constraint.isSatisfiedWith(assignment))
				for (Variable var : constraint.getScope())
					if (!result.contains(var))
						result.add(var);
		}
		return result;
	}

	private Object getMinConflictValueFor(Variable var, Assignment assignment,
			CSP csp) {
		List<Constraint> constraints = csp.getConstraints(var);
		Assignment duplicate = assignment.copy();
		int minConflict = Integer.MAX_VALUE;
		List<Object> resultCandidates = new ArrayList<Object>();
		for (Object value : csp.getDomain(var)) {
			duplicate.setAssignment(var, value);
			int currConflict = countConflicts(duplicate, constraints);
			if (currConflict <= minConflict) {
				if (currConflict < minConflict) {
					resultCandidates.clear();
					minConflict = currConflict;
				}
				resultCandidates.add(value);
			}
		}
		if (!resultCandidates.isEmpty())
			return Util.selectRandomlyFromList(resultCandidates);
		else
			return null;
	}

	private int countConflicts(Assignment assignment,
			List<Constraint> constraints) {
		int result = 0;
		for (Constraint constraint : constraints)
			if (!constraint.isSatisfiedWith(assignment))
				result++;
		return result;
	}
}
