package org.limewire.setting;

import java.util.Properties;

/** 
 * Provides a {@link Number} setting value and 
 * ensures any value you set in the future falls within a range. As a subclass 
 * of <code>Setting</code>, the setting has a key. If the value is set outside 
 * the number range, the value is set to the closer value of either the minimum 
 * or maximum range value. For example, if the range is [0,2] and you set the 
 * value to 8, the value is actually set to 2.
 * <p>
 * Additionally, <code>AbstractNumber</code> defines a method 
 * for subclasses to convert a string to a {@link Comparable}. 
 */
public abstract class AbstractNumberSetting<T extends Number & Comparable<T>> extends AbstractSetting<T> {

    /**
     * Adds a safeguard against remote making a setting take a value beyond the
     * reasonable max 
     */
    protected final T MAX_VALUE;

    /**
     * Adds a safeguard against remote making a setting take a value below the
     * reasonable min
     */
    protected final T MIN_VALUE;
    
    /** Whether or not this is a remote setting. */
    private final boolean remote;
    
    protected AbstractNumberSetting(Properties defaultProps, Properties props,
                                    String key, String defaultValue, 
                                    boolean remote, T min, T max) {
        super(defaultProps, props, key, defaultValue);
        this.remote = remote;
        if(max != null && min != null) {//do we need to check max, min?
            if(max.compareTo(min) < 0) //max less than min?
                throw new IllegalArgumentException("max less than min");
        }
        MAX_VALUE = max;
        MIN_VALUE = min;
        setValueInternal(getValueAsString()); // performs normalization
    }

    /**
     * Set new property value
     * @param value new property value
     */
    @Override
    protected void setValueInternal(String value) {
        if(remote) {
            assert MAX_VALUE != null : "remote setting created with no max";
            assert MIN_VALUE != null : "remote setting created with no min";
        }
        value = normalizeValue(value);
        super.setValueInternal(value);
    }
    
    /**
     * Returns the minimum value for this setting, may return null if no minimum is set. 
     */
   public T getMinValue() {
       return MIN_VALUE;
   }

   /**
    * Returns the minimum value for this setting, may return null if no maximum is set. 
    */
   public T getMaxValue() {
       return MAX_VALUE;
   }

    /**
     * Normalizes a value to an acceptable value for this setting.
     */
    protected String normalizeValue(String value) {
        Comparable<T> comparableValue;
        try {
            comparableValue = convertToComparable(value);
        } catch (NumberFormatException e) {
            return DEFAULT_VALUE;
        }
        if (MAX_VALUE != null && comparableValue.compareTo(MAX_VALUE) > 0) {
            return MAX_VALUE.toString();
        } else if (MIN_VALUE != null && comparableValue.compareTo(MIN_VALUE) < 0) {
            return MIN_VALUE.toString();
        }
        return value;
    }
    
    /** Converts a String to a Comparable of the same type as MAX_VALUE and MIN_VALUE.     */
    abstract protected Comparable<T> convertToComparable(String value);
    
}
