/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
*/
package openbook.domain;


/**
 * A simple numeric range.
 * Minimum value is included, maximum value is excluded.
 *
 * @author Pinaki Poddar
 *
 */
public class Range<N extends Number> {
    private N min;
    private N max;
    private final Class<N> type;

    @SuppressWarnings("unchecked")
    public Range(Object min, Object max) {
        this((N)min, (N)max);
    }

    /**
     * Creates a range. Empty range i.e. where minimum equals maximum is allowed.
     *
     * @param min non-null minimum value.
     * @param max non-null maximum value.
     */
    @SuppressWarnings("unchecked")
    public Range(N min, N max) {
        if (min == null || max == null)
            throw new IllegalArgumentException("Supplied Min or Max is null");
        if (max.doubleValue() < min.doubleValue())
            throw new IllegalArgumentException("Invalid range (" + min + "," + max + ")");
        this.min = min;
        this.max = max;
        type = (Class<N>)min.getClass();
    }

    public Class<N> type() {
        return type;
    }

    /**
     * Affirms if the given value is within this range.
     * Minimum is included, maximum is excluded.
     *
     * @param x a non-null value
     * @return true if the given value is greater than or equals to minimum and less than the maximum.
     */
    public boolean contains(Number x) {
        return x != null && x.doubleValue() >= min.doubleValue() && x.doubleValue() < max.doubleValue();
    }

    /**
     * Affirms if the given range is within this range.
     * Minimum is included, maximum is excluded.
     *
     * @param x a non-null value
     * @return true if the given value is greater than or equals to minimum and less than the maximum.
     */
    public <X extends Number> boolean contains(Range<X> r) {
        return r != null && r.getMinimum().doubleValue() >= min.doubleValue() &&
            r.getMaximum().doubleValue() <= max.doubleValue();
    }

    /**
     * Gets the minimum value.
     */
    public N getMinimum() {
        return min;
    }

    /**
     * Gets the maximum value.
     */
    public N getMaximum() {
        return max;
    }

    /**
     * Adjusts this range by the given number.
     *
     * @param x a non-null value.
     *
     * @return if this range is adjusted by this value.
     */
    public boolean adjust(N x) {
        if (x == null)
            return false;
        if (x.doubleValue() < min.doubleValue()) {
            min = x;
            return true;
        }
        if (x.doubleValue() > max.doubleValue()) {
            max = x;
            return true;
        }
        return false;
    }


    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((max == null) ? 0 : max.hashCode());
        result = prime * result + ((min == null) ? 0 : min.hashCode());
        result = prime * result + ((type == null) ? 0 : type.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;
        Range other = (Range) obj;
        if (max == null) {
            if (other.max != null)
                return false;
        } else if (!max.equals(other.max))
            return false;
        if (min == null) {
            if (other.min != null)
                return false;
        } else if (!min.equals(other.min))
            return false;
        if (type == null) {
            if (other.type != null)
                return false;
        } else if (!type.equals(other.type))
            return false;
        return true;
    }
}
