/* Copyright 2000 by Basil Vandegriend.  All rights reserved. */

package com.basilv.core;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;

/**
 * Represents a modifiable integer value. Invariants:
 * <OL>
 * <LI>maxValue > minValue</LI>
 * <LI>value >= minValue</LI>
 * <LI>value <= maxValue</LI>
 * </OL>
 */
public class IntegerValue implements Serializable
{
	public static final String CURRENT_VALUE_PROPERTY = "CurrentValueProperty";

	public static final String MAX_VALUE_PROPERTY = "MaxValueProperty";

	public static final String MIN_VALUE_PROPERTY = "MinValueProperty";

	private static final long serialVersionUID = 1L;

	private static void assertClassInvariants(int value, int minValue, int maxValue) {
		Assert.isTrue("maxValue > minValue", maxValue > minValue);
		Assert.isTrue("value >= minValue", value >= minValue);
		Assert.isTrue("value <= maxValue", value <= maxValue);
	}

	private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

	private int value = 0;

	private int minValue = 0;

	private int maxValue = Integer.MAX_VALUE;

	/**
	 * Default constructor which sets minValue to 0, maxValue to MAX_INT, and
	 * value to 0.
	 */
	public IntegerValue() {
		this(0);
	}

	/**
	 * Create an instance with the specified value. minValue defaults to 0 and
	 * maxValue defaults to MAX_INT
	 * 
	 * @param newValue  Must be >= minValue.
	 */
	public IntegerValue(int newValue) {
		this(newValue, 0, Integer.MAX_VALUE);
	}

	/**
	 * Main constructor.
	 * 
	 * @param newValue  The current value. Must be >= minValue and <= maxValue.
	 * @param newMinValue  Must be >= 0.
	 * @param newMaxValue  Must be > minValue.
	 */
	public IntegerValue(int newValue, int newMinValue, int newMaxValue) {
		assertClassInvariants(newValue, newMinValue, newMaxValue);

		value = newValue;
		minValue = newMinValue;
		maxValue = newMaxValue;
	}

	/**
	 * Return the current value. Will be >= minValue and <= maxValue.
	 */
	public int getValue() {
		return value;
	}

	/**
	 * Set the value to the specified value which is adjusted to fit within the
	 * min and max values if necessary.
	 * 
	 * @param newValue
	 */
	public void setValueEnforcingMinAndMax(int newValue) {
		int valueToSet = newValue;
		if (newValue < minValue) {
			valueToSet = minValue;
		} else if (newValue > maxValue) {
			valueToSet = maxValue;
		}
		setValue(valueToSet);
	}

	public void setValue(int newValue) {
		Assert.isTrue("newValue >= minValue", newValue >= minValue);
		Assert.isTrue("newValue <= maxValue", newValue <= maxValue);

		int oldValue = value;
		value = newValue;
		propertyChangeSupport.firePropertyChange(CURRENT_VALUE_PROPERTY, oldValue, newValue);
	}

	/**
	 * Return the maximum value possible for this value. Will be > minimum
	 * value.
	 */
	public int getMaximumValue() {
		return maxValue;
	}

	/**
	 * Set the maximum value. If the current value is larger than the new
	 * maximum then it is set to the maximum.
	 * 
	 * @param newMaxValue  Must be > minValue.
	 */
	public void setMaximumValue(int newMaxValue) {
		Assert.isTrue("newMaxValue > minValue", newMaxValue > minValue);

		int oldMaxValue = maxValue;
		maxValue = newMaxValue;

		setValueEnforcingMinAndMax(getValue());
		propertyChangeSupport.firePropertyChange(MAX_VALUE_PROPERTY, oldMaxValue, newMaxValue);
	}

	/**
	 * Return the minimum value possible for this value.
	 */
	public int getMinimumValue() {
		return minValue;
	}

	/**
	 * Set the minimum value. If the current value is less than the new minimum
	 * then it is set to the minimum. If the maximum value is less than or equal
	 * to the new minimum, it is changed to be equal to the new minimum plus
	 * one.
	 * 
	 * @param newMinValue 
	 */
	public void setMinimumValue(int newMinValue) {
		int oldMinValue = minValue;
		minValue = newMinValue;

		if (maxValue <= newMinValue) {
			setMaximumValue(newMinValue + 1);
		}
		setValueEnforcingMinAndMax(getValue());

		propertyChangeSupport.firePropertyChange(MIN_VALUE_PROPERTY, oldMinValue, newMinValue);
	}

	/**
	 * Simultaneously set the min, max and current values. This method is
	 * provided to allow for situations when more than one value needs to be
	 * changed at the same time in order to maintain the class invariants. See
	 * class invariants for preconditions on parameters.
	 * 
	 * @param newMinValue
	 * @param newMaxValue
	 * @param newCurrentValue
	 */
	protected void setAllValues(int newCurrentValue, int newMinValue, int newMaxValue) {
		assertClassInvariants(newCurrentValue, newMinValue, newMaxValue);

		int oldMinValue = minValue;
		int oldMaxValue = maxValue;
		int oldCurrentValue = value;

		minValue = newMinValue;
		maxValue = newMaxValue;
		value = newCurrentValue;

		propertyChangeSupport.firePropertyChange(CURRENT_VALUE_PROPERTY, oldCurrentValue, newCurrentValue);
		propertyChangeSupport.firePropertyChange(MAX_VALUE_PROPERTY, oldMaxValue, newMaxValue);
		propertyChangeSupport.firePropertyChange(MIN_VALUE_PROPERTY, oldMinValue, newMinValue);
	}

	/**
	 * Returns true if the value equals the maximum.
	 */
	public boolean isValueAtMax() {
		return (value == maxValue);
	}

	/**
	 * Returns true if the value equals the minimum.
	 */
	public boolean isValueAtMin() {
		return (value == minValue);
	}

	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		Assert.notNull("propertyName", propertyName);
		Assert.notNull("listener", listener);
		propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		Assert.notNull("propertyName", propertyName);
		Assert.notNull("listener", listener);
		propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
	}
	
	protected PropertyChangeSupport getPropertyChangeSupport() {
		return propertyChangeSupport;
	}

	@Override
	public String toString() {
		return getClass().getName() + " value = " + getValue();
	}

	// TO DO @@: Implement equals, hashcode - maybe?

} // class IntegerValue
