package edu.uky.ai.csp.kr;

import java.util.Iterator;
import java.util.LinkedHashSet;

/**
 * A domain is the set of values that a variable might possibly be assigned.
 * 
 * @author Stephen G. Ware
 */
public class Domain implements Iterable<Object>, Cloneable {

	/** The variable in question */
	public final Variable variable;
	
	/** An ordered set of values that might be assigned to the variable */
	private final LinkedHashSet<Object> values;
	
	/**
	 * Constructs a new domain object.
	 * 
	 * @param variable the variable to be assigned a value
	 * @param values a set of possible values
	 */
	private Domain(Variable variable, LinkedHashSet<Object> values) {
		this.variable = variable;
		this.values = values;
	}
	
	/**
	 * Constructs a new domain object with a given set of possible values.
	 * 
	 * @param variable the variable to be assigned a value
	 * @param values a set of possible values that variable might be assigned
	 */
	public Domain(Variable variable, Object[] values) {
		this(variable, new LinkedHashSet<>());
		for(Object value : values)
			add(value);
	}
	
	@Override
	public String toString() {
		String str = variable + "={";
		boolean first = true;
		for(Object value : values) {
			if(first)
				first = false;
			else
				str += ",";
			str += value;
		}
		return str + "}";
	}

	@Override
	@SuppressWarnings("unchecked")
	public Iterator<Object> iterator() {
		return ((Iterable<Object>) values.clone()).iterator();
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public Domain clone() {
		return new Domain(variable, (LinkedHashSet<Object>) values.clone());
	}
	
	/**
	 * Returns the number of possible values this variable might be assigned.
	 * 
	 * @return the number of values
	 */
	public int size() {
		return values.size();
	}
	
	/**
	 * Checks if a given value is one of the possible values this variable
	 * might be assigned.
	 * 
	 * @param value the value to test
	 * @return true if this domain contains the given value, false otherwise
	 */
	public boolean contains(Object value) {
		return values.contains(value);
	}
	
	/**
	 * If this domain has only 1 possible value, this method returns it.
	 * 
	 * @return the value assigned to the variable
	 * @throws IllegalStateException if the domain has 0 or more than 1 possible value
	 */
	public Object getValue() {
		if(size() == 1)
			return values.iterator().next();
		else
			throw new IllegalStateException("Domain has " + values.size() + " values");
	}
	
	/**
	 * Adds a potential value to the domain.
	 * 
	 * @param value the value to add to the set of possible values
	 */
	public void add(Object value) {
		values.add(value);
	}
	
	/**
	 * Removes a potential value from the domain.
	 * 
	 * @param value the value to remove from the set of possible values
	 */
	public void remove(Object value) {
		values.remove(value);
	}
	
	/**
	 * Removes all values except for a given value from the domain.  This
	 * effectively sets the variable's value to the given value.
	 * 
	 * @param value the value to be retained
	 */
	public void set(Object value) {
		if(!values.contains(value))
			throw new IllegalArgumentException(value + " not in domain");
		values.clear();
		values.add(value);
	}
}
