/* Copyright 2009-2013 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.core;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * A solution to an optimization problem, storing the decision variables,
 * objectives, constraints and attributes. Attributes are arbitrary {@code
 * (key, value)} pairs; they are instance-specific and are not carried over in
 * the copy constructor.
 * <p>
 * Solutions should only be constructed in {@link Problem#newSolution()} or 
 * cloned from an existing solution with {@link #copy()}.  This ensures the
 * solutions and configured correctly for the given optimization problem.
 */
public class Solution implements Serializable {

	private static final long serialVersionUID = -1192586435663892479L;

	/**
	 * The decision variables of this solution.
	 */
	private final Variable[] variables;

	/**
	 * The objectives of this solution.
	 */
	private final double[] objectives;

	/**
	 * The constraints of this solution.
	 */
	private final double[] constraints;

	/**
	 * The attributes of this solutions.
	 */
	private final Map<String, Serializable> attributes;

	/**
	 * Constructs a solution with the specified number of variables and 
	 * objectives with no constraints.
	 * 
	 * @param numberOfVariables the number of variables defined by this solution
	 * @param numberOfObjectives the number of objectives defined by this
	 *        solution
	 */
	public Solution(int numberOfVariables, int numberOfObjectives) {
		this(numberOfVariables, numberOfObjectives, 0);
	}

	/**
	 * Constructs a solution with the specified number of variables, objectives
	 * and constraints.
	 * 
	 * @param numberOfVariables the number of variables defined by this solution
	 * @param numberOfObjectives the number of objectives defined by this
	 *        solution
	 * @param numberOfConstraints the number of constraints defined by this
	 *        solution
	 */
	public Solution(int numberOfVariables, int numberOfObjectives,
			int numberOfConstraints) {
		variables = new Variable[numberOfVariables];
		objectives = new double[numberOfObjectives];
		constraints = new double[numberOfConstraints];
		attributes = new HashMap<String, Serializable>();
	}

	/**
	 * Constructs a solution with no variables and the specified objectives.
	 * This is intended for creating reference set solutions.
	 * 
	 * @param objectives the objectives to be stored in this solution
	 */
	public Solution(double[] objectives) {
		this(0, objectives.length, 0);

		for (int i = 0; i < objectives.length; i++) {
			setObjective(i, objectives[i]);
		}
	}

	/**
	 * Copy constructor.
	 * 
	 * @param solution the solution being copied
	 */
	protected Solution(Solution solution) {
		this(solution.getNumberOfVariables(), solution.getNumberOfObjectives(),
				solution.getNumberOfConstraints());

		for (int i = 0; i < solution.getNumberOfVariables(); i++) {
			setVariable(i, solution.getVariable(i).copy());
		}

		for (int i = 0; i < getNumberOfObjectives(); i++) {
			setObjective(i, solution.getObjective(i));
		}

		for (int i = 0; i < getNumberOfConstraints(); i++) {
			setConstraint(i, solution.getConstraint(i));
		}
	}

	/**
	 * Returns an independent copy of this solution. It is required that
	 * {@code x.copy()} is completely independent from {@code x} . This means
	 * any method invoked on {@code x.copy()} in no way alters the state of
	 * {@code x} and vice versa. It is typically the case that
	 * {@code x.copy().getClass() == x.getClass()} and
	 * {@code x.copy().equals(x)}
	 * <p>
	 * Note that a solution's attributes are not copied, as the attributes are
	 * generally specific to each instance.
	 * 
	 * @return an independent copy of this solution
	 */
	public Solution copy() {
        return new Solution(this);
	}

	/**
	 * Returns the number of objectives defined by this solution.
	 * 
	 * @return the number of objectives defined by this solution
	 */
	public int getNumberOfObjectives() {
		return objectives.length;
	}

	/**
	 * Returns the number of variables defined by this solution.
	 * 
	 * @return the number of variables defined by this solution
	 */
	public int getNumberOfVariables() {
		return variables.length;
	}

	/**
	 * Returns the number of constraints defined by this solution.
	 * 
	 * @return the number of constraints defined by this solution
	 */
	public int getNumberOfConstraints() {
		return constraints.length;
	}

	/**
	 * Returns the objective at the specified index.
	 * 
	 * @param index index of the objective to return
	 * @return the objective at the specified index
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfObjectives())}
	 */
	public double getObjective(int index) {
		return objectives[index];
	}

	/**
	 * Returns the variable at the specified index.
	 * 
	 * @param index index of the variable to return
	 * @return the variable at the specified index
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfVariables())}
	 */
	public Variable getVariable(int index) {
		return variables[index];
	}

	/**
	 * Sets the objective at the specified index.
	 * 
	 * @param index index of the objective to set
	 * @param objective the new value of the objective being set
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfObjectives())}
	 */
	public void setObjective(int index, double objective) {
		objectives[index] = objective;
	}

	/**
	 * Sets all objectives of this solution.
	 * 
	 * @param objectives the new objectives for this solution
	 * @throws IllegalArgumentException if {@code objectives.length !=
	 *         getNumberOfObjectives()}
	 */
	public void setObjectives(double[] objectives) {
		if (objectives.length != this.objectives.length) {
			throw new IllegalArgumentException("invalid number of objectives");
		}

		for (int i = 0; i < objectives.length; i++) {
			this.objectives[i] = objectives[i];
		}
	}

	/**
	 * Returns an array containing the objectives of this solution. Modifying
	 * the returned array will not modify the internal state of this solution.
	 * 
	 * @return an array containing the objectives of this solution
	 */
	public double[] getObjectives() {
		return objectives.clone();
	}

	/**
	 * Sets the variable at the specified index.
	 * 
	 * @param index index of the variable being set
	 * @param variable the new value of the variable being set
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfVariables())}
	 */
	public void setVariable(int index, Variable variable) {
		variables[index] = variable;
	}

	/**
	 * Returns {@code true} if any of the constraints are violated;
	 * {@code false} otherwise.
	 * 
	 * @return {@code true} if any of the constraints are violated;
	 *         {@code false} otherwise
	 */
	public boolean violatesConstraints() {
		for (int i = 0; i < constraints.length; i++) {
			if (constraints[i] != 0.0) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns the constraint at the specified index.
	 * 
	 * @param index index of the variable to be returned
	 * @return the constraint at the specified index
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfConstraints())}
	 */
	public double getConstraint(int index) {
		return constraints[index];
	}

	/**
	 * Sets all constraints of this solution.
	 * 
	 * @param constraints the new constraints for this solution
	 * @throws IllegalArgumentException if {@code constraints.length !=
	 *         getNumberOfConstraints()}
	 */
	public void setConstraints(double[] constraints) {
		if (constraints.length != this.constraints.length) {
			throw new IllegalArgumentException("invalid number of constraints");
		}

		for (int i = 0; i < constraints.length; i++) {
			this.constraints[i] = constraints[i];
		}
	}

	/**
	 * Returns an array containing the constraints of this solution. Modifying
	 * the returned array will not modify the internal state of this solution.
	 * 
	 * @return an array containing the constraints of this solution
	 */
	public double[] getConstraints() {
		return constraints.clone();
	}

	/**
	 * Sets the constraint at the specified index.
	 * 
	 * @param index the index of the constraint being set
	 * @param constraint the new value of the constraint being set
	 * @throws IndexOutOfBoundsException if the index is out of range
	 *         {@code (index < 0) || (index >= getNumberOfConstraints())}
	 */
	public void setConstraint(int index, double constraint) {
		constraints[index] = constraint;
	}

	/**
	 * Returns the value of the attribute that is associated with the specified
	 * key, or {@code null} if no value has been associated with the key.
	 * 
	 * @param key the key whose associated value is to be returned
	 * @return the value associated with the specified key, or {@code null} if
	 *         no value has been associated with the key
	 */
	public Object getAttribute(String key) {
		return attributes.get(key);
	}

	/**
	 * Associates the specified value with the specified key. Returns the old
	 * value associated with the key, or {@code null} if no prior value has been
	 * associated with the key.
	 * 
	 * @param key the key with which the specified value is to be associated
	 * @param value the value to be associated with the specified key
	 * @return the old value associated with the key, or {@code null} if no
	 *         prior value has been associated with the key
	 */
	public Object setAttribute(String key, Serializable value) {
		return attributes.put(key, value);
	}

	/**
	 * Removes the specified key and its associated value from this solution.
	 * Returns the old value associated with the key, or {@code null} if no
	 * prior value has been associated with the key.
	 * 
	 * @param key the key to be removed
	 * @return the old value associated with the key, or {@code null} if no
	 *         prior value has been associated with the key
	 */
	public Object removeAttribute(String key) {
		return attributes.remove(key);
	}

	/**
	 * Returns {@code true} if the specified key exists in this solution's
	 * attributes; {@code false} otherwise.
	 * 
	 * @param key the key whose presence is being tested
	 * @return {@code true} if the specified key exists in this solution's
	 *         attributes; {@code false} otherwise
	 */
	public boolean hasAttribute(String key) {
		return attributes.containsKey(key);
	}

	/**
	 * Returns the {@link Map} containing this solution's attributes.
	 * 
	 * @return the {@code Map} containing this solution's attributes
	 */
	public Map<String, Serializable> getAttributes() {
		return attributes;
	}

	/**
	 * Adds all attributes to this solution in the specified {@link Map}.
	 * 
	 * @param attributes the {@code Map} containing the attributes to be added
	 *        to this solution
	 */
	public void addAttributes(Map<String, Object> attributes) {
		attributes.putAll(attributes);
	}

	/**
	 * Removes all keys and values from this solution's attributes.
	 */
	public void clearAttributes() {
		attributes.clear();
	}

}
