/*
 * Copyright (C) 2008-2009 GLAD!! (ITO Yoshiichi)
 *
 * 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 jp.sourceforge.glad.util;

import java.io.Serializable;

/**
 * 範囲。
 * 
 * @author GLAD!!
 * @param <T> 値の型
 */
public class Range<T extends Comparable<T>> implements Serializable {

    private static final long serialVersionUID = 5426776030903724459L;

    // ---- fields

    /** 下限 */
    final T lower;

    /** 下限を含むか */
    final boolean containsLower;

    /** 上限 */
    final T upper;

    /** 上限を含むか */
    final boolean containsUpper;

    /** toString のキャッシュ */
    private transient String toString = null;

    /** hashCode のキャッシュ */
    private transient int hashCode = 0;

    // ---- constructors

    /**
     * 範囲 [value, value] を構築します。
     * 
     * @param value 値
     */
    public Range(T value) {
        this(value, true, value, true);
    }

    /**
     * 範囲 [lower, upper) を構築します。
     * 
     * @param lower 下限
     * @param upper 上限
     */
    public Range(T lower, T upper) {
        this(lower, true, upper, false);
    }

    /**
     * 範囲を構築します。
     * 
     * @param lower 下限(範囲に含む)
     * @param upper 上限
     * @param containsUpper 上限を含むか
     */
    public Range(T lower, T upper, boolean containsUpper) {
        this(lower, true, upper, containsUpper);
    }

    /**
     * 範囲を構築します。
     * 
     * @param lower 下限
     * @param containsLower 下限を含むか
     * @param upper 上限
     * @param containsUpper 上限を含むか
     */
    public Range(
            T lower, boolean containsLower,
            T upper, boolean containsUpper) {
        if (lower != null && upper != null && lower.compareTo(upper) > 0) {
            throw new IllegalArgumentException(
                    "lower > upper: lower=" + lower + ", upper=" + upper);
        }
        this.lower = lower;
        this.containsLower = containsLower && (lower != null);
        this.upper = upper;
        this.containsUpper = containsUpper && (upper != null);
    }

    // ---- accessors

    public T getLower() {
        return lower;
    }

    public boolean containsLower() {
        return containsLower;
    }

    public T getUpper() {
        return upper;
    }

    public boolean containsUpper() {
        return containsUpper;
    }

    // ---- other methods

    /**
     * value ∈ this
     */
    public boolean contains(T value) {
        if (value == null) {
            return false;
        }
        return lowerLE(value) && upperGE(value);
    }

    boolean lowerLE(T value) {
        if (lower == null) {
            return true;
        }
        if (containsLower) {
            return lower.compareTo(value) <= 0;
        } else {
            return lower.compareTo(value) < 0;
        }
    }

    boolean upperGE(T value) {
        if (upper == null) {
            return true;
        }
        if (containsUpper) {
            return upper.compareTo(value) >= 0;
        } else {
            return upper.compareTo(value) > 0;
        }
    }

    /**
     * range ⊆ this
     */
    public boolean includes(Range<T> range) {
        if (range == null) {
            return false;
        }
        return lowerIncludes(range) && upperIncludes(range);
    }

    boolean lowerIncludes(Range<T> range) {
        if (lower == null) {
            return true;
        }
        if (range.getLower() == null) {
            return false;
        }
        if (containsLower || !range.containsLower()) {
            return lower.compareTo(range.getLower()) <= 0;
        } else {
            return lower.compareTo(range.getLower()) < 0;
        }
    }

    boolean upperIncludes(Range<T> range) {
        if (upper == null) {
            return true;
        }
        if (range.getUpper() == null) {
            return false;
        }
        if (containsUpper || !range.containsUpper()) {
            return upper.compareTo(range.getUpper()) >= 0;
        } else {
            return upper.compareTo(range.getUpper()) > 0;
        }
    }

    /**
     * this ∩ this ≠ φ
     */
    public boolean overlaps(Range<T> range) {
        if (range == null) {
            return false;
        }
        return lowerOverlaps(range) && upperOverlaps(range);
    }

    boolean lowerOverlaps(Range<T> range) {
        if (lower == null || range.getUpper() == null) {
            return true;
        }
        if (containsLower && range.containsUpper()) {
            return lower.compareTo(range.getUpper()) <= 0;
        } else {
            return lower.compareTo(range.getUpper()) < 0;
        }
    }

    boolean upperOverlaps(Range<T> range) {
        if (upper == null || range.getLower() == null) {
            return true;
        }
        if (containsUpper && range.containsLower()) {
            return upper.compareTo(range.getLower()) >= 0;
        } else {
            return upper.compareTo(range.getLower()) > 0;
        }
    }

    // ---- java.lang.Object

    @Override
    public String toString() {
        if (toString == null) {
            StringBuilder sb = new StringBuilder();
            sb.append(containsLower ? '[' : '(');
            sb.append(lower);
            sb.append(", ");
            sb.append(upper);
            sb.append(containsUpper? ']' : ')');
            toString = sb.toString();
        }
        return toString;
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            int result = 17;
            result = 37 * result + (lower == null ? 0 : lower.hashCode());
            result = 37 * result + (containsLower ? 1 : 0);
            result = 37 * result + (upper == null ? 0 : upper.hashCode());
            result = 37 * result + (containsUpper ? 1 : 0);
            hashCode = result;
        }
        return hashCode;
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object other) {
        if (!(other instanceof Range)) {
            return false;
        }
        return equals((Range<T>) other);
    }

    public boolean equals(Range<T> other) {
        if (other == null) {
            return false;
        }
        return equals(lower, other.getLower())
                && (containsLower == other.containsLower())
                && equals(upper, other.getUpper())
                && (containsUpper == other.containsUpper());
    }

    boolean equals(T o1, T o2) {
        return (o1 == o2 || o1 != null && o1.equals(o2));
    }

}
