package geniusweb.issuevalue;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * Contains a (possibly partial) bid. Basically a map. immutable, thread safe.
 */
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class Bid {
	// not immutable and explicit HashMap for better serializability, must be
	// kept immutable.
	private final HashMap<String, Value> issuevalues;

	/**
	 * 
	 * @param issuevalues a map of issue, Value pairs. The String is the issue
	 *                    name.
	 */
	@JsonCreator
	public Bid(@JsonProperty("issuevalues") Map<String, Value> issuevalues) {
		if (issuevalues == null) {
			throw new NullPointerException("issuevalues=null");
		}
		for (String issue : issuevalues.keySet()) {
			if (issuevalues.get(issue) == null) {
				throw new IllegalArgumentException(
						"value of issue " + issue + " can not be null");
			}
		}
		this.issuevalues = new HashMap<String, Value>(issuevalues);
	}

	/**
	 * Makes partial bid with just 1 issue and value.
	 * 
	 * @param issue the issue name
	 * @param value the {@link Value}
	 */
	public Bid(String issue, Value value) {
		if (issue == null || value == null) {
			throw new NullPointerException("issue and value must be not =null");
		}

		issuevalues = new HashMap<>();
		issuevalues.put(issue, value);
	}

	/**
	 * @param issue name of the issue
	 * @return the value for the given issue, or null if there is no value for
	 *         the given issue.
	 */
	public Value getValue(String issue) {
		return issuevalues.get(issue);
	}

	/**
	 * @param issue name of the issue
	 * @return true iff the bid contains a value for the given issue.
	 */
	public boolean containsIssue(String issue) {
		return issuevalues.containsKey(issue);
	}

	public Set<String> getIssues() {
		return Collections.unmodifiableSet(issuevalues.keySet());
	}

	/**
	 * Merges this partial bid with another partial bid.
	 * 
	 * @param otherbid another partial bid.
	 * @return a bid with the combined values of both partial bids.
	 * @throws IllegalArgumentException if issues overlap.
	 */
	public Bid merge(Bid otherbid) {
		Set<String> ourissues = new HashSet<>(issuevalues.keySet());
		ourissues.retainAll(otherbid.getIssues());
		if (!ourissues.isEmpty()) {
			throw new IllegalArgumentException(
					"Otherbid contains issues that are already set:"
							+ ourissues);
		}
		HashMap<String, Value> newvalues = new HashMap<>(issuevalues);
		newvalues.putAll(otherbid.issuevalues);
		return new Bid(newvalues);

	}

	/**
	 * 
	 * @return (unmodifyable) map with all issues and values in the bid.
	 */
	public Map<String, Value> getIssueValues() {
		return Collections.unmodifiableMap(issuevalues);
	}

	@Override
	public String toString() {
		return "Bid" + issuevalues.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((issuevalues == null) ? 0 : issuevalues.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Bid other = (Bid) obj;
		if (issuevalues == null) {
			if (other.issuevalues != null)
				return false;
		} else if (!issuevalues.equals(other.issuevalues))
			return false;
		return true;
	}

}